Last active
September 24, 2020 06:41
-
-
Save alxgrk/e672b031206481bc462d21788a321804 to your computer and use it in GitHub Desktop.
Code for the post on dev.to:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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