diff --git a/CHANGELOG.md b/CHANGELOG.md index cee3104dea0..4a48bea34b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * `Versions`: * `Kotlin Exposed`: `0.30.1` -> `0.30.2` +* `Serialization`: + * `TypedSerializer`: + * Project has been inited ## 0.4.34 diff --git a/serialization/typed_serializer/build.gradle b/serialization/typed_serializer/build.gradle new file mode 100644 index 00000000000..7c54502f100 --- /dev/null +++ b/serialization/typed_serializer/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppProjectWithSerializationPresetPath" diff --git a/serialization/typed_serializer/src/commonMain/kotlin/dev/inmo/micro_utils/serialization/typed_serializer/TypedSerializer.kt b/serialization/typed_serializer/src/commonMain/kotlin/dev/inmo/micro_utils/serialization/typed_serializer/TypedSerializer.kt new file mode 100644 index 00000000000..02a486a957e --- /dev/null +++ b/serialization/typed_serializer/src/commonMain/kotlin/dev/inmo/micro_utils/serialization/typed_serializer/TypedSerializer.kt @@ -0,0 +1,76 @@ +package dev.inmo.micro_utils.serialization.typed_serializer + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlin.reflect.KClass + +class TypedSerializer( + kClass: KClass, + presetSerializers: Map> = emptyMap() +) : KSerializer { + private val serializers = presetSerializers.toMutableMap() + @InternalSerializationApi + override val descriptor: SerialDescriptor = buildSerialDescriptor( + "TextSourceSerializer", + SerialKind.CONTEXTUAL + ) { + element("type", String.serializer().descriptor) + element("value", ContextualSerializer(kClass).descriptor) + } + + @InternalSerializationApi + override fun deserialize(decoder: Decoder): T { + return decoder.decodeStructure(descriptor) { + var type: String? = null + lateinit var result: T + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> type = decodeStringElement(descriptor, 0) + 1 -> { + require(type != null) { "Type is null, but it is expected that was inited already" } + result = decodeSerializableElement( + descriptor, + 1, + serializers.getValue(type) + ) + } + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + result + } + } + + @InternalSerializationApi + private fun CompositeEncoder.encode(value: O) { + encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer, value) + } + + @InternalSerializationApi + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeStructure(descriptor) { + val valueSerializer = value::class.serializer() + val type = serializers.keys.first { serializers[it] == valueSerializer } + encodeStringElement(descriptor, 0, type) + encode(value) + } + } + + + fun include(type: String, serializer: KSerializer) { + require(type !in serializers.keys) + serializers[type] = serializer + } + + fun exclude(type: String) { + require(type !in serializers.keys) + serializers.remove(type) + } +} + +inline fun TypedSerializer( + presetSerializers: Map> = emptyMap() +) = TypedSerializer(T::class, presetSerializers) diff --git a/serialization/typed_serializer/src/commonTest/kotlin/dev/inmo/micro_utils/serialization/typed_serializer/TypedSerializerTests.kt b/serialization/typed_serializer/src/commonTest/kotlin/dev/inmo/micro_utils/serialization/typed_serializer/TypedSerializerTests.kt new file mode 100644 index 00000000000..cf14169be80 --- /dev/null +++ b/serialization/typed_serializer/src/commonTest/kotlin/dev/inmo/micro_utils/serialization/typed_serializer/TypedSerializerTests.kt @@ -0,0 +1,40 @@ +package dev.inmo.micro_utils.serialization.typed_serializer + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.Json +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals + +class TypedSerializerTests { + interface Example { + val number: Number + } + val serialFormat = Json { } + + @Serializable + data class Example1(override val number: Long) : Example + + @Serializable + data class Example2(override val number: Double) : Example + + @Test + fun testThatSerializerWorksCorrectly() { + val serializer = TypedSerializer( + mapOf( + "long" to Example1.serializer(), + "double" to Example2.serializer() + ) + ) + + val value1 = Example1(Random.nextLong()) + val value2 = Example2(Random.nextDouble()) + + val list = listOf(value1, value2) + val serialized = serialFormat.encodeToString(ListSerializer(serializer), list) + val deserialized = serialFormat.decodeFromString(ListSerializer(serializer), serialized) + + assertEquals(list, deserialized) + } +} \ No newline at end of file diff --git a/serialization/typed_serializer/src/main/AndroidManifest.xml b/serialization/typed_serializer/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..7af0107d187 --- /dev/null +++ b/serialization/typed_serializer/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 5d0afc1c814..1cdf43a6d64 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ String[] includes = [ ":android:alerts:recyclerview", ":serialization:base64", ":serialization:encapsulator", + ":serialization:typed_serializer", ":dokka" ]