mirror of
https://github.com/InsanusMokrassar/SDI.git
synced 2025-03-13 18:12:31 +00:00
rewrite deserialization
This commit is contained in:
parent
853b9b6de3
commit
b9e24b0d12
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
@ -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<*>>
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user