rewrite deserialization

This commit is contained in:
InsanusMokrassar 2019-11-27 13:05:18 +06:00
parent 853b9b6de3
commit b9e24b0d12
8 changed files with 160 additions and 183 deletions

View File

@ -1,5 +1,5 @@
kotlin.code.style=official
kotlin_version=1.3.60
kotlin_version=1.3.61
kotlin_serialisation_runtime_version=0.14.0
gradle_bintray_plugin_version=1.8.4

View File

@ -7,167 +7,25 @@ import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.reflect.KClass
class Module(vararg pairs: Pair<String, Any>) : Map<String, Any> by mutableMapOf(*pairs)
private class SubDependencyResolver<T : Any>(
val kclass: KClass<T>,
private val parentResolver: DependencyResolver<*>
) : KSerializer<T> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("SubDependencyResolver")
private val dependencies
get() = parentResolver.dependencies
override fun deserialize(decoder: Decoder): T = parentResolver.deserialize(decoder) as T
override fun serialize(encoder: Encoder, obj: T) {
val key = dependencies.keys.first { key ->
parentResolver.dependencies[key] === obj
}
encoder.encodeString(key)
}
fun registerInModuleBuilder(builder: SerializersModuleBuilder) = builder.contextual(kclass, this)
}
private class DependencyResolver<T : Any>(
val kclass: KClass<T>,
private val externalDependencyResolver: (String) -> Any?
) : KSerializer<T> {
private val mutDependencies = mutableMapOf<String, T>()
val dependencies: Map<String, T>
get() = mutDependencies
override val descriptor: SerialDescriptor = StringDescriptor.withName("DependencyResolver")
val subResolvers = kclass.allSubclasses.map {
SubDependencyResolver(it, this)
}
fun registerNewDependency(name: String, value: T) {
mutDependencies[name] = value
}
fun tryRegisterNewDependency(name: String, value: Any): Boolean {
return (value as? T) ?.also { registerNewDependency(name, value) } != null
}
override fun deserialize(decoder: Decoder): T {
val dependencyName = decoder.decodeString()
return mutDependencies[dependencyName]
?: (externalDependencyResolver(dependencyName) as? T) ?.also { registerNewDependency(dependencyName, it) }
?: throw IllegalArgumentException("Can't resolve dependency for dependency name $dependencyName")
}
override fun serialize(encoder: Encoder, obj: T) {
val key = mutDependencies.keys.first { key ->
mutDependencies[key] === obj
}
encoder.encodeString(key)
}
}
@ImplicitReflectionSerializer
@Serializable(ModuleSerializer::class)
class Module(base: Map<String, @ContextualSerialization Any>) : Map<String, Any> by base
@ImplicitReflectionSerializer
private class DependenciesHolderAndResolver(
sourceMap: Map<String, Any>
) : KSerializer<Any> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("DependenciesHolderAndResolver")
private val typesSerializers = mutableMapOf<KClass<*>, DependencyResolver<*>>()
private var mapper = Mapper()
private var module = SerializersModule {}
set(value) {
field = value
mapper = Mapper(module)
}
private fun <T: Any> addModuleSerialization(dependencyResolver: DependencyResolver<T>) {
module += SerializersModule {
dependencyResolver.subResolvers.forEach { resolver ->
module.getContextual(resolver.kclass) ?: resolver.registerInModuleBuilder(this)
}
contextual(dependencyResolver.kclass, dependencyResolver)
}
}
val dependenciesMap = sourceMap.map { (k, v) ->
var resolved = false
lateinit var actualValue: Any
k to {
if (!resolved) {
actualValue = resolveDependency(k, v)
resolved = true
}
actualValue
}
}.toMap()
private fun Map<String, Any>.initDependencies() {
(keys).forEach { paramKey ->
val paramValue = get(paramKey)
if (paramValue is String) {
dependenciesMap[paramValue] ?.invoke()
}
}
}
private fun resolveDependency(
dependencyName: String,
value: Any
): Any {
val v = when (value) {
is Map<*, *> -> {
mapper.unmap(
resolveSerializerByPackageName(dependencyName),
value.toCommonMap().also { it.initDependencies() }
) as Any
}
is List<*> -> {
val packageName = value.firstOrNull() as? String ?: return value
val arguments = (value.getOrNull(1) as? Map<*, *>) ?.toCommonMap() ?: emptyMap()
mapper.unmap(
resolveSerializerByPackageName(packageName),
arguments.also { it.initDependencies() }
) as Any
}
is String -> dependenciesMap[value] ?.invoke() ?: return value
else -> value
}
(typesSerializers[v::class] ?:let {
val kclass = v::class
val resolver = DependencyResolver(kclass) { dependencyName ->
dependenciesMap[dependencyName]
}
typesSerializers[kclass] = resolver
addModuleSerialization(resolver)
resolver
}).also {
it.tryRegisterNewDependency(dependencyName, v)
}
return v
}
override fun deserialize(decoder: Decoder): Any {
val dependencyName = decoder.decodeString()
return dependenciesMap[dependencyName] ?.invoke() ?: throw IllegalArgumentException("Can't resolve unknown dependency $dependencyName")
}
override fun serialize(encoder: Encoder, obj: Any) {
val key = dependenciesMap.keys.first {
dependenciesMap[it] ?.invoke() === obj
}
encoder.encodeString(key)
}
}
@Serializer(Module::class)
@ImplicitReflectionSerializer
object ModuleSerializer : KSerializer<Module> {
private val internalSerializer = HashMapSerializer(StringSerializer, PolymorphicSerializer(Any::class))
private val internalSerializer = HashMapSerializer(StringSerializer, ContextSerializer(Any::class))
override val descriptor: SerialDescriptor
get() = internalSerializer.descriptor
override fun deserialize(decoder: Decoder): Module {
val json = JsonObjectSerializer.deserialize(decoder)
val sourceMap = json.adaptForMap()
val deserializer = DependenciesHolderAndResolver(sourceMap)
val dependencies = deserializer.dependenciesMap.map { (key, valueGetter) -> key to valueGetter() }
return Module(*dependencies.toTypedArray())
val jsonSerialFormat = createModuleBasedOnConfigRoot(json)
val resultJson = JsonObject(
json.keys.associateWith { JsonPrimitive(it) }
)
val map = jsonSerialFormat.fromJson(internalSerializer, resultJson)
return Module(map)
}
override fun serialize(encoder: Encoder, obj: Module) {

View File

@ -0,0 +1,54 @@
package com.insanusmokrassar.sdi.utils
import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.modules.SerializerAlreadyRegisteredException
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlin.reflect.KClass
@ImplicitReflectionSerializer
internal class DependencyResolver<T : Any>(
serialModuleBuilder: SerializersModuleBuilder,
kClass: KClass<T>,
private val dependencyGetter: (String) -> Any
) : KSerializer<T> {
private val originalSerializer: KSerializer<T>? = try {
resolveSerializerByKClass(kClass)
} catch (e: Exception) {
null
}
private val objectsCache = mutableMapOf<String, T>()
override val descriptor: SerialDescriptor = originalSerializer ?.descriptor ?: StringDescriptor.withName("DependencyResolver")
init {
serialModuleBuilder.apply {
contextual(kClass, this@DependencyResolver)
kClass.allSubclasses.forEach { kClass ->
try {
DependencyResolver(serialModuleBuilder, kClass, dependencyGetter)
} catch (e: SerializerAlreadyRegisteredException) {
// ok
}
}
}
}
override fun deserialize(decoder: Decoder): T {
return try {
val dependencyName = decoder.decodeString()
(dependencyGetter(dependencyName) as T).also {
objectsCache[dependencyName] = it
}
} catch (e: Exception) {
originalSerializer ?.deserialize(decoder) ?: throw IllegalStateException("Can't resolve dependency", e)
}
}
override fun serialize(encoder: Encoder, obj: T) {
objectsCache.keys.firstOrNull {
objectsCache[it] === obj
} ?.also { dependencyName ->
encoder.encodeString(dependencyName)
} ?: originalSerializer ?.serialize(encoder, obj) ?: throw IllegalStateException("Can't resolve dependency")
}
}

View File

@ -1,24 +1,83 @@
package com.insanusmokrassar.sdi.utils
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
fun JsonPrimitive.adaptForMap(): Any = doubleOrNull ?: floatOrNull ?: longOrNull ?: intOrNull ?: booleanOrNull ?: content
fun JsonArray.adaptForMap(): List<Any> {
return mapNotNull {
when (it) {
is JsonObject -> it.adaptForMap()
is JsonArray -> it.adaptForMap()
is JsonPrimitive -> it.adaptForMap()
}
private typealias PackageOrOtherDependencyNamePair = Pair<String?, String?>
private fun JsonElement.resolvePackageName(currentKey: String, otherDependenciesKeys: Set<String>): PackageOrOtherDependencyNamePair {
return when (this) {
is JsonPrimitive -> contentOrNull ?.let {
if (it in otherDependenciesKeys) {
null to it
} else {
it to null
}
} ?: throw IllegalArgumentException("Value on dependency name \"$currentKey\" is invalid: provided $this, but expected package name or other dependency name string")
is JsonObject -> return currentKey to null
is JsonArray -> return getPrimitive(0).contentOrNull ?.let { it to null } ?: throw IllegalArgumentException("Value on first argument of dependency value must be its package as a string, but was provided ${get(0)}")
}
}
fun JsonObject.adaptForMap(): Map<String, Any> {
return keys.mapNotNull {
val value = (
getArrayOrNull(it) ?.adaptForMap()
?: getObjectOrNull(it) ?.adaptForMap()
?: getPrimitiveOrNull(it) ?.adaptForMap()
) ?: return@mapNotNull null
it to value
@ImplicitReflectionSerializer
internal fun createModuleBasedOnConfigRoot(jsonObject: JsonObject): Json {
lateinit var caches: Map<String, () -> Any>
lateinit var jsonStringFormat: Json
caches = jsonObject.keys.map { key ->
key to callback@{
val elemValue = jsonObject.get(key) ?: throw IllegalStateException("Value for key $key must be provided, but was not")
val packageName: String = elemValue.resolvePackageName(key, jsonObject.keys).let { (packageName, otherDependencyName) ->
when {
packageName != null -> packageName
otherDependencyName != null -> return@callback caches.getValue(otherDependencyName).invoke()
else -> throw IllegalStateException("Internal error: can't resolve other dependency name and package name for key $key")
}
}
val argumentsObject = when (elemValue) {
is JsonPrimitive -> {
elemValue.contentOrNull ?.let { _ ->
JsonObject(emptyMap())
} ?: throw IllegalArgumentException("Value on dependency name \"$key\" is invalid: provided $elemValue, but expected package name or other dependency name string")
}
is JsonObject -> {
elemValue
}
is JsonArray -> {
if (elemValue.size > 1) {
elemValue.getObject(1)
} else {
JsonObject(emptyMap())
}
}
}
val serializer = resolveSerializerByPackageName(packageName)
return@callback jsonStringFormat.fromJson(serializer, argumentsObject) as Any
}
}.toMap()
val keysToPackages: Map<String, String> = jsonObject.mapNotNull { (key, element) ->
val packageName = element.resolvePackageName(key, jsonObject.keys).first ?: return@mapNotNull null
key to packageName
}.toMap()
return Json(
context = SerializersModule {
keysToPackages.values.forEach {
val kclass = resolveKClassByPackageName(it)
try {
DependencyResolver(this, kclass) {
caches.getValue(it).invoke()
}
} catch (e: SerializationException) {
// here we are thinking that already registered
}
}
}
).also {
jsonStringFormat = it
}
}

View File

@ -1,5 +0,0 @@
package com.insanusmokrassar.sdi.utils
fun Map<*, *>.toCommonMap() = mapNotNull { (k, v) ->
(k as? String ?: return@mapNotNull null) to (v ?: return@mapNotNull null)
}.toMap()

View File

@ -8,6 +8,11 @@ import kotlin.reflect.KType
@ImplicitReflectionSerializer
expect fun resolveSerializerByPackageName(packageName: String): KSerializer<*>
@ImplicitReflectionSerializer
expect fun <T : Any> resolveSerializerByKClass(kClass: KClass<T>): KSerializer<T>
expect fun resolveKClassByPackageName(packageName: String): KClass<*>
expect val KClass<*>.currentSupertypes: List<KType>
val KClass<*>.allSubclasses: Set<KClass<*>>

View File

@ -9,20 +9,17 @@ interface ControllerAPI {
fun showUp()
}
interface ServiceAPI {
val name: String
val names: List<String>
}
@Serializable
class Controller(@ContextualSerialization private val service : ServiceAPI) : ControllerAPI {
class Controller(@ContextualSerialization private val service: ServiceAPI) : ControllerAPI {
override fun showUp() {
println("Inited with name \"${service.name}\"")
println("Inited with name \"${service.names}\"")
}
}
@Serializable
class BusinessService : ServiceAPI {
@Transient
override val name = "Example of business name"
}
class BusinessService(override val names: List<String>) : ServiceAPI
@ImplicitReflectionSerializer
class DeserializationTest {
@ -31,7 +28,10 @@ class DeserializationTest {
val input = """
{
"service": [
"com.insanusmokrassar.sdi.BusinessService"
"com.insanusmokrassar.sdi.BusinessService",
{
"names": ["nameOne", "nameTwo"]
}
],
"controller": [
"com.insanusmokrassar.sdi.Controller",

View File

@ -1,6 +1,12 @@
package com.insanusmokrassar.sdi.utils
import kotlinx.serialization.*
import kotlin.reflect.KClass
@ImplicitReflectionSerializer
actual fun resolveSerializerByPackageName(packageName: String): KSerializer<*> = Class.forName(packageName).kotlin.serializer()
@ImplicitReflectionSerializer
actual fun <T : Any> resolveSerializerByKClass(kClass: KClass<T>): KSerializer<T> = kClass.serializer()
actual fun resolveKClassByPackageName(packageName: String): KClass<*> = Class.forName(packageName).kotlin