mirror of
https://github.com/InsanusMokrassar/SDI.git
synced 2025-09-08 17:49:17 +00:00
rewrite deserialization
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin_version=1.3.60
|
kotlin_version=1.3.61
|
||||||
kotlin_serialisation_runtime_version=0.14.0
|
kotlin_serialisation_runtime_version=0.14.0
|
||||||
|
|
||||||
gradle_bintray_plugin_version=1.8.4
|
gradle_bintray_plugin_version=1.8.4
|
||||||
|
@@ -7,167 +7,25 @@ import kotlinx.serialization.json.*
|
|||||||
import kotlinx.serialization.modules.*
|
import kotlinx.serialization.modules.*
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class Module(vararg pairs: Pair<String, Any>) : Map<String, Any> by mutableMapOf(*pairs)
|
@ImplicitReflectionSerializer
|
||||||
|
@Serializable(ModuleSerializer::class)
|
||||||
private class SubDependencyResolver<T : Any>(
|
class Module(base: Map<String, @ContextualSerialization Any>) : Map<String, Any> by base
|
||||||
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
|
@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)
|
@Serializer(Module::class)
|
||||||
@ImplicitReflectionSerializer
|
|
||||||
object ModuleSerializer : KSerializer<Module> {
|
object ModuleSerializer : KSerializer<Module> {
|
||||||
private val internalSerializer = HashMapSerializer(StringSerializer, PolymorphicSerializer(Any::class))
|
private val internalSerializer = HashMapSerializer(StringSerializer, ContextSerializer(Any::class))
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = internalSerializer.descriptor
|
get() = internalSerializer.descriptor
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Module {
|
override fun deserialize(decoder: Decoder): Module {
|
||||||
val json = JsonObjectSerializer.deserialize(decoder)
|
val json = JsonObjectSerializer.deserialize(decoder)
|
||||||
val sourceMap = json.adaptForMap()
|
val jsonSerialFormat = createModuleBasedOnConfigRoot(json)
|
||||||
val deserializer = DependenciesHolderAndResolver(sourceMap)
|
val resultJson = JsonObject(
|
||||||
val dependencies = deserializer.dependenciesMap.map { (key, valueGetter) -> key to valueGetter() }
|
json.keys.associateWith { JsonPrimitive(it) }
|
||||||
return Module(*dependencies.toTypedArray())
|
)
|
||||||
|
val map = jsonSerialFormat.fromJson(internalSerializer, resultJson)
|
||||||
|
return Module(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Module) {
|
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
|
package com.insanusmokrassar.sdi.utils
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
import kotlinx.serialization.modules.*
|
||||||
|
|
||||||
fun JsonPrimitive.adaptForMap(): Any = doubleOrNull ?: floatOrNull ?: longOrNull ?: intOrNull ?: booleanOrNull ?: content
|
private typealias PackageOrOtherDependencyNamePair = Pair<String?, String?>
|
||||||
fun JsonArray.adaptForMap(): List<Any> {
|
|
||||||
return mapNotNull {
|
private fun JsonElement.resolvePackageName(currentKey: String, otherDependenciesKeys: Set<String>): PackageOrOtherDependencyNamePair {
|
||||||
when (it) {
|
return when (this) {
|
||||||
is JsonObject -> it.adaptForMap()
|
is JsonPrimitive -> contentOrNull ?.let {
|
||||||
is JsonArray -> it.adaptForMap()
|
if (it in otherDependenciesKeys) {
|
||||||
is JsonPrimitive -> it.adaptForMap()
|
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)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun JsonObject.adaptForMap(): Map<String, Any> {
|
|
||||||
return keys.mapNotNull {
|
val serializer = resolveSerializerByPackageName(packageName)
|
||||||
val value = (
|
return@callback jsonStringFormat.fromJson(serializer, argumentsObject) as Any
|
||||||
getArrayOrNull(it) ?.adaptForMap()
|
}
|
||||||
?: getObjectOrNull(it) ?.adaptForMap()
|
|
||||||
?: getPrimitiveOrNull(it) ?.adaptForMap()
|
|
||||||
) ?: return@mapNotNull null
|
|
||||||
it to value
|
|
||||||
}.toMap()
|
}.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
|
@ImplicitReflectionSerializer
|
||||||
expect fun resolveSerializerByPackageName(packageName: String): KSerializer<*>
|
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>
|
expect val KClass<*>.currentSupertypes: List<KType>
|
||||||
|
|
||||||
val KClass<*>.allSubclasses: Set<KClass<*>>
|
val KClass<*>.allSubclasses: Set<KClass<*>>
|
||||||
|
@@ -9,20 +9,17 @@ interface ControllerAPI {
|
|||||||
fun showUp()
|
fun showUp()
|
||||||
}
|
}
|
||||||
interface ServiceAPI {
|
interface ServiceAPI {
|
||||||
val name: String
|
val names: List<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Controller(@ContextualSerialization private val service: ServiceAPI) : ControllerAPI {
|
class Controller(@ContextualSerialization private val service: ServiceAPI) : ControllerAPI {
|
||||||
override fun showUp() {
|
override fun showUp() {
|
||||||
println("Inited with name \"${service.name}\"")
|
println("Inited with name \"${service.names}\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Serializable
|
@Serializable
|
||||||
class BusinessService : ServiceAPI {
|
class BusinessService(override val names: List<String>) : ServiceAPI
|
||||||
@Transient
|
|
||||||
override val name = "Example of business name"
|
|
||||||
}
|
|
||||||
|
|
||||||
@ImplicitReflectionSerializer
|
@ImplicitReflectionSerializer
|
||||||
class DeserializationTest {
|
class DeserializationTest {
|
||||||
@@ -31,7 +28,10 @@ class DeserializationTest {
|
|||||||
val input = """
|
val input = """
|
||||||
{
|
{
|
||||||
"service": [
|
"service": [
|
||||||
"com.insanusmokrassar.sdi.BusinessService"
|
"com.insanusmokrassar.sdi.BusinessService",
|
||||||
|
{
|
||||||
|
"names": ["nameOne", "nameTwo"]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"controller": [
|
"controller": [
|
||||||
"com.insanusmokrassar.sdi.Controller",
|
"com.insanusmokrassar.sdi.Controller",
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
package com.insanusmokrassar.sdi.utils
|
package com.insanusmokrassar.sdi.utils
|
||||||
|
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ImplicitReflectionSerializer
|
@ImplicitReflectionSerializer
|
||||||
actual fun resolveSerializerByPackageName(packageName: String): KSerializer<*> = Class.forName(packageName).kotlin.serializer()
|
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
|
||||||
|
Reference in New Issue
Block a user