Skip to content

Instantly share code, notes, and snippets.

@alxgrk
Last active September 24, 2020 06:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alxgrk/e672b031206481bc462d21788a321804 to your computer and use it in GitHub Desktop.
Save alxgrk/e672b031206481bc462d21788a321804 to your computer and use it in GitHub Desktop.
Code for the post on dev.to:
#!/usr/bin/env kscript
//DEPS org.jetbrains.kotlin:kotlin-reflect:1.3.72,com.fasterxml.jackson.module:jackson-module-kotlin:2.11.2,com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.2
import Config_sealed_classes.Configuration.Greeting.SayHello
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.readValue
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
/**
* SAMPLE
*/
val yamlInput = """
Configuration:
Greeting:
SayHello: 70
WaveAt: 80
Hug: 5
Talking:
DoSmallTalk: 90
Insult: 1
""".trimIndent()
/**
* SEALED CLASSES IN ACTION
*/
sealed class Configuration {
sealed class Greeting : Configuration() {
object SayHello : Greeting()
object WaveAt : Greeting()
object Hug : Greeting()
}
sealed class Talking : Configuration() {
object DoSmallTalk : Talking()
object Insult : Talking()
}
}
/**
* DIGRESSION
*/
class Probabilities private constructor(
private val backingMap: MutableMap<Configuration, Int> = mutableMapOf()
) : Map<Configuration, Int> by backingMap {
constructor(configuration: Probabilities.() -> Unit) : this() {
configuration()
}
infix fun Configuration.withProbabilityOf(percent: Int) = backingMap.put(this, percent)
override fun toString(): String = backingMap.entries.joinToString { "${it.key::class.simpleName} = ${it.value}" }
}
/**
* PARSING
*/
fun fromMap(configuration: Map<String, Any>) = Probabilities {
@Suppress("UNCHECKED_CAST")
fun parseFor(
configuration: Map<String, Any>,
parent: KClass<out Configuration>?,
clazz: KClass<out Configuration>
) {
if (configuration.containsKey(clazz.simpleName) && parent?.isSuperclassOf(clazz) != false) {
when (val value = configuration[clazz.simpleName]) {
is Map<*, *> -> {
clazz.sealedSubclasses.forEach { subclass ->
parseFor(value as Map<String, Any>, clazz, subclass)
}
}
is Int -> {
if (clazz.objectInstance == null)
throw RuntimeException("${clazz.simpleName} should be an object")
clazz.objectInstance!! withProbabilityOf value
}
else -> throw RuntimeException("unknown property ${clazz.simpleName}")
}
}
}
parseFor(configuration, null, Configuration::class)
}
val result = fromMap(mapOf("Configuration" to mapOf("Greeting" to mapOf("SayHello" to 1))))
println(result)
/**
* JACKSON
*/
inner class ProbabilitiesDeserializer : StdDeserializer<Probabilities>(Probabilities::class.java) {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?) =
p?.readValueAs<Map<String, Any>>(object : TypeReference<Map<String, Any>>() {})
?.let { fromMap(it) }
}
val mapper = ObjectMapper(YAMLFactory()).apply {
registerModule(SimpleModule().addDeserializer(Probabilities::class.java, ProbabilitiesDeserializer()))
registerModule(KotlinModule())
}
val probabilities = mapper.readValue<Probabilities>(yamlInput)
println(probabilities.toString())
println("Say hello with probability of ${probabilities[SayHello]} percent.")
/**
* BONUS: ENSURING ALL PROPERTIES SET
*/
val leaves = leavesOf(Configuration::class)
fun leavesOf(baseClass: KClass<out Configuration>): List<KClass<out Configuration>> =
if (!baseClass.isSealed) {
listOf(baseClass)
} else {
baseClass.sealedSubclasses.flatMap(::leavesOf)
}
fun Probabilities.ensureAllActionsCovered() {
val keys = keys.map { it::class }
val unconfigured = leaves.filter { leaf -> !keys.contains(leaf) }
if (unconfigured.isNotEmpty())
throw RuntimeException("Unconfigured leaves: ${unconfigured.joinToString()}")
}
probabilities.ensureAllActionsCovered()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment