From d1805332c888aed77a7f918f0baa9047bd72b57d Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Tue, 15 Mar 2022 22:45:01 +0300 Subject: [PATCH 1/3] Adding tests that show errors in deserialization ### What's done: - test for invalid custom serialization - test for invalid inline serialization --- .../ktoml/decoders/CustomSerializerTest.kt | 45 +++++++++++++++++++ .../ktoml/decoders/InlineDecoderTest.kt | 22 +++++++++ 2 files changed, 67 insertions(+) create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/InlineDecoderTest.kt diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt new file mode 100644 index 00000000..b8535a18 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt @@ -0,0 +1,45 @@ +package com.akuleshov7.ktoml.decoders + +import kotlinx.serialization.decodeFromString +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.test.Test + +class CustomSerializerTest { + object ColorAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Color) { + TODO() + } + + override fun deserialize(decoder: Decoder): Color { + val value = decoder.decodeString() + return Color(value.toLong()) + } + } + + @Serializable(with = ColorAsStringSerializer::class) + data class Color(val rgb: Long) + + @Serializable + data class Settings(val background: Color, val foreground: Color) + + @Test + fun testDecodingWithCustomSerializer() { + println(Toml.decodeFromString( + """ + [background] + rgb = 0 + [foreground] + rgb = 0 + """.trimIndent() + )) + } +} diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/InlineDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/InlineDecoderTest.kt new file mode 100644 index 00000000..b959b8e0 --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/InlineDecoderTest.kt @@ -0,0 +1,22 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlin.jvm.JvmInline +import kotlin.test.Test + +class InlineDecoderTest { + @JvmInline + @Serializable + value class Color(val rgb: Long) + + @Test + fun testDecodingWithCustomSerializer() { + Toml.decodeFromString( + """ + rgb = 0 + """.trimIndent() + ) + } +} From 0d1b84828791d68693f78d7aa8f4c578b5afc904 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Thu, 28 Apr 2022 22:32:45 +0300 Subject: [PATCH 2/3] Adding the support of custom serializers for simple cases --- CustomSerializers.md | 71 +++++++++++++++++++ .../ktoml/decoders/TomlMainDecoder.kt | 48 ++++++++----- .../ktoml/decoders/CustomSerializerTest.kt | 69 ++++++++++++++---- .../ktoml/decoders/SurrogateSerializerTest.kt | 48 +++++++++++++ 4 files changed, 204 insertions(+), 32 deletions(-) create mode 100644 CustomSerializers.md create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt diff --git a/CustomSerializers.md b/CustomSerializers.md new file mode 100644 index 00000000..50457b6b --- /dev/null +++ b/CustomSerializers.md @@ -0,0 +1,71 @@ +### Customizing ktoml serialization +This page will be useful ONLY for those who plan to customize the serialization and deserialization logic of ktoml. + +### Custom Deserializer +We suggest to use custom deserializer only for **very primitive cases**. +In case you **really** need proper custom serializer, you will need to have something like this (this is a real generated code for the serialization): + +```kotlin +override fun deserialize(decoder: Decoder): Color { + val serialDescriptor = descriptor + var bl = true + var n = 0 + var l = 0L + // to read TOML AST properly - you need to run beginStructure first + val compositeDecoder = decoder.beginStructure(serialDescriptor) + block4@ while (bl) { + val n2 = compositeDecoder.decodeElementIndex(serialDescriptor) + when (n2) { + // decoding done: + -1 -> { + bl = false + continue@block4 + } + // element index + 0 -> { + l = compositeDecoder.decodeLongElement(serialDescriptor, 0) + n = n or 1 + continue@block4 + } + } + throw IllegalArgumentException() + } + compositeDecoder.endStructure(serialDescriptor) + return Color(l) +} +``` + +for the following data class: + +```kotlin + @Serializable(with = ColorAsStringSerializer::class) + data class Color(val rgb: Long) +``` + +### Simple cases for Deserialization +Imaging you have your class Color: +```kotlin + @Serializable(with = ColorAsStringSerializer::class) + data class Color(val rgb: Long) +``` + +We have several small workarounds, that would let you override deserializers for your class and using ktoml-native parser and AST: + +```kotlin + object ColorAsStringSerializer : KSerializer { + override fun deserialize(decoder: Decoder): Color { + // please note, that the decoder should match with the real type of a variable: + // for string in the input - decodeString, for long - decodeLong, etc. + val string = decoder.decodeString() + return Color(string.toLong() + 15) + } + } +``` + +```kotlin + Toml.decodeFromString( + """ + rgb = "0" + """.trimIndent() + ) +``` diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt index dfd5f4cd..a1b781fc 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt @@ -25,7 +25,7 @@ import kotlinx.serialization.modules.SerializersModule */ @ExperimentalSerializationApi public class TomlMainDecoder( - private val rootNode: TomlNode, + private var rootNode: TomlNode, private val config: TomlConfig, private var elementIndex: Int = 0 ) : TomlAbstractDecoder() { @@ -74,15 +74,23 @@ public class TomlMainDecoder( * real value for decoding. Other types of nodes are more technical * */ - override fun decodeKeyValue(): TomlKeyValue = when (val node = getCurrentNode()) { - is TomlKeyValuePrimitive -> node - is TomlKeyValueArray -> node - // empty nodes will be filtered by iterateUntilWillFindAnyKnownName() method, but in case we came into this - // branch, we should throw an exception as it is not expected at all and we should catch this in tests - else -> - throw InternalDecodingException( - "This kind of node should not be processed in TomlDecoder.decodeValue(): ${node.content}" - ) + override fun decodeKeyValue(): TomlKeyValue { + // this is a very important workaround for people who plan to write their own CUSTOM serializers + if (rootNode is TomlFile) { + rootNode = getFirstChild(rootNode) + elementIndex = 1 + } + + return when (val node = getCurrentNode()) { + is TomlKeyValuePrimitive -> node + is TomlKeyValueArray -> node + // empty nodes will be filtered by iterateUntilWillFindAnyKnownName() method, but in case we came into this + // branch, we should throw an exception as it is not expected at all and we should catch this in tests + else -> + throw InternalDecodingException( + "Node of type [${node::class}] should not be processed in TomlDecoder.decodeValue(): <${node.content}>" + ) + } } override fun decodeElementIndex(descriptor: SerialDescriptor): Int { @@ -194,14 +202,8 @@ public class TomlMainDecoder( private fun iterateOverStructure(descriptor: SerialDescriptor, inlineFunc: Boolean): TomlAbstractDecoder = if (rootNode is TomlFile) { checkMissingRequiredProperties(rootNode.children, descriptor) - val firstFileChild = rootNode.getFirstChild() ?: if (!config.allowEmptyToml) { - throw InternalDecodingException( - "Missing child nodes (tables, key-values) for TomlFile." + - " Was empty toml provided to the input?" - ) - } else { - rootNode - } + val firstFileChild = getFirstChild(rootNode) + // inline structures has a very specific logic for decoding. Kotlinx.serialization plugin generates specific code: // 'decoder.decodeInline(this.getDescriptor()).decodeLong())'. So we need simply to increment // our element index by 1 (0 is the default value), because value/inline classes are always a wrapper over some SINGLE value. @@ -232,6 +234,16 @@ public class TomlMainDecoder( } } + private fun getFirstChild(node: TomlNode) = + node.getFirstChild() ?: if (!config.allowEmptyToml) { + throw InternalDecodingException( + "Missing child nodes (tables, key-values) for TomlFile." + + " Was empty toml provided to the input?" + ) + } else { + node + } + public companion object { /** * @param deserializer - deserializer provided by Kotlin compiler diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt index b8535a18..9c200ed9 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt @@ -1,44 +1,85 @@ package com.akuleshov7.ktoml.decoders -import kotlinx.serialization.decodeFromString import com.akuleshov7.ktoml.Toml -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable +import kotlinx.serialization.* import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.test.Ignore import kotlin.test.Test +import kotlin.test.assertEquals class CustomSerializerTest { - object ColorAsStringSerializer : KSerializer { + object SinglePropertyAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SingleProperty", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: SingleProperty) { + } + + override fun deserialize(decoder: Decoder): SingleProperty { + val string = decoder.decodeString() + return SingleProperty(string.toLong() + 15) + } + } + + @Serializable(with = SinglePropertyAsStringSerializer::class) + data class SingleProperty(val rgb: Long) + + @Test + fun testDecodingWithCustomSerializerSingleProperty() { + assertEquals( + SingleProperty(15), + Toml.decodeFromString( + """ + rgb = "0" + """.trimIndent() + ) + ) + } + + @Serializable(with = SeveralPropertiesAsStringSerializer::class) + data class SeveralProperties(val rgb: Long, val brg: Long) + + object SeveralPropertiesAsStringSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: Color) { - TODO() + override fun serialize(encoder: Encoder, value: SeveralProperties) { } - override fun deserialize(decoder: Decoder): Color { - val value = decoder.decodeString() - return Color(value.toLong()) + override fun deserialize(decoder: Decoder): SeveralProperties { + val string = decoder.decodeString() + return SeveralProperties(string.toLong() + 15, string.toLong()) } } - @Serializable(with = ColorAsStringSerializer::class) - data class Color(val rgb: Long) + @Test + @Ignore + fun testDecodingWithCustomSerializerSeveralProperties() { + assertEquals( + SeveralProperties(15, 1), + Toml.decodeFromString( + """ + rgb = "0" + brg = "1" + """.trimIndent() + ) + ) + } @Serializable - data class Settings(val background: Color, val foreground: Color) + data class Settings(val background: SingleProperty, val foreground: SingleProperty) @Test + @Ignore fun testDecodingWithCustomSerializer() { println(Toml.decodeFromString( """ [background] - rgb = 0 + rgb = "0" [foreground] - rgb = 0 + rgb = "0" """.trimIndent() )) } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt new file mode 100644 index 00000000..61dd567e --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt @@ -0,0 +1,48 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.test.Ignore +import kotlin.test.Test + +class SurrogateTest { + object ColorSerializer : KSerializer { + override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor + + override fun serialize(encoder: Encoder, value: Color) { + val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) + encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) + } + + override fun deserialize(decoder: Decoder): Color { + val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) + return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) + } + } + + @Serializable + @SerialName("Color") + private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { + init { + require(r in 0..255 && g in 0..255 && b in 0..255) + } + } + + @Serializable(with = ColorSerializer::class) + class Color(val rgb: Int) + + @Test + @Ignore + fun testDecodingWithCustomSerializer() { + println(Toml.decodeFromString( + """ + r = 5 + g = 6 + b = 7 + """.trimIndent() + )) + } +} From 4bb2a9f9206d8b06c603a7350645718a806ebc37 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Thu, 28 Apr 2022 22:32:45 +0300 Subject: [PATCH 3/3] Adding the support of custom serializers for simple cases --- CustomSerializers.md | 71 +++++++++++++++++++ .../ktoml/decoders/TomlMainDecoder.kt | 48 ++++++++----- .../ktoml/decoders/CustomSerializerTest.kt | 69 ++++++++++++++---- .../ktoml/decoders/SurrogateSerializerTest.kt | 48 +++++++++++++ 4 files changed, 204 insertions(+), 32 deletions(-) create mode 100644 CustomSerializers.md create mode 100644 ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt diff --git a/CustomSerializers.md b/CustomSerializers.md new file mode 100644 index 00000000..50457b6b --- /dev/null +++ b/CustomSerializers.md @@ -0,0 +1,71 @@ +### Customizing ktoml serialization +This page will be useful ONLY for those who plan to customize the serialization and deserialization logic of ktoml. + +### Custom Deserializer +We suggest to use custom deserializer only for **very primitive cases**. +In case you **really** need proper custom serializer, you will need to have something like this (this is a real generated code for the serialization): + +```kotlin +override fun deserialize(decoder: Decoder): Color { + val serialDescriptor = descriptor + var bl = true + var n = 0 + var l = 0L + // to read TOML AST properly - you need to run beginStructure first + val compositeDecoder = decoder.beginStructure(serialDescriptor) + block4@ while (bl) { + val n2 = compositeDecoder.decodeElementIndex(serialDescriptor) + when (n2) { + // decoding done: + -1 -> { + bl = false + continue@block4 + } + // element index + 0 -> { + l = compositeDecoder.decodeLongElement(serialDescriptor, 0) + n = n or 1 + continue@block4 + } + } + throw IllegalArgumentException() + } + compositeDecoder.endStructure(serialDescriptor) + return Color(l) +} +``` + +for the following data class: + +```kotlin + @Serializable(with = ColorAsStringSerializer::class) + data class Color(val rgb: Long) +``` + +### Simple cases for Deserialization +Imaging you have your class Color: +```kotlin + @Serializable(with = ColorAsStringSerializer::class) + data class Color(val rgb: Long) +``` + +We have several small workarounds, that would let you override deserializers for your class and using ktoml-native parser and AST: + +```kotlin + object ColorAsStringSerializer : KSerializer { + override fun deserialize(decoder: Decoder): Color { + // please note, that the decoder should match with the real type of a variable: + // for string in the input - decodeString, for long - decodeLong, etc. + val string = decoder.decodeString() + return Color(string.toLong() + 15) + } + } +``` + +```kotlin + Toml.decodeFromString( + """ + rgb = "0" + """.trimIndent() + ) +``` diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt index dfd5f4cd..13d96b5e 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt @@ -25,7 +25,7 @@ import kotlinx.serialization.modules.SerializersModule */ @ExperimentalSerializationApi public class TomlMainDecoder( - private val rootNode: TomlNode, + private var rootNode: TomlNode, private val config: TomlConfig, private var elementIndex: Int = 0 ) : TomlAbstractDecoder() { @@ -74,15 +74,23 @@ public class TomlMainDecoder( * real value for decoding. Other types of nodes are more technical * */ - override fun decodeKeyValue(): TomlKeyValue = when (val node = getCurrentNode()) { - is TomlKeyValuePrimitive -> node - is TomlKeyValueArray -> node - // empty nodes will be filtered by iterateUntilWillFindAnyKnownName() method, but in case we came into this - // branch, we should throw an exception as it is not expected at all and we should catch this in tests - else -> - throw InternalDecodingException( - "This kind of node should not be processed in TomlDecoder.decodeValue(): ${node.content}" - ) + override fun decodeKeyValue(): TomlKeyValue { + // this is a very important workaround for people who plan to write their own CUSTOM serializers + if (rootNode is TomlFile) { + rootNode = getFirstChild(rootNode) + elementIndex = 1 + } + + return when (val node = getCurrentNode()) { + is TomlKeyValuePrimitive -> node + is TomlKeyValueArray -> node + // empty nodes will be filtered by iterateUntilWillFindAnyKnownName() method, but in case we came into this + // branch, we should throw an exception as it is not expected at all and we should catch this in tests + else -> + throw InternalDecodingException( + "Node of type [${node::class}] should not be processed in TomlDecoder.decodeValue(): <${node.content}>" + ) + } } override fun decodeElementIndex(descriptor: SerialDescriptor): Int { @@ -194,14 +202,8 @@ public class TomlMainDecoder( private fun iterateOverStructure(descriptor: SerialDescriptor, inlineFunc: Boolean): TomlAbstractDecoder = if (rootNode is TomlFile) { checkMissingRequiredProperties(rootNode.children, descriptor) - val firstFileChild = rootNode.getFirstChild() ?: if (!config.allowEmptyToml) { - throw InternalDecodingException( - "Missing child nodes (tables, key-values) for TomlFile." + - " Was empty toml provided to the input?" - ) - } else { - rootNode - } + val firstFileChild = getFirstChild(rootNode) + // inline structures has a very specific logic for decoding. Kotlinx.serialization plugin generates specific code: // 'decoder.decodeInline(this.getDescriptor()).decodeLong())'. So we need simply to increment // our element index by 1 (0 is the default value), because value/inline classes are always a wrapper over some SINGLE value. @@ -232,6 +234,16 @@ public class TomlMainDecoder( } } + private fun getFirstChild(node: TomlNode) = + node.getFirstChild() ?: if (!config.allowEmptyToml) { + throw InternalDecodingException( + "Missing child nodes (tables, key-values) for TomlFile." + + " Was empty toml provided to the input?" + ) + } else { + node + } + public companion object { /** * @param deserializer - deserializer provided by Kotlin compiler diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt index b8535a18..9c200ed9 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/CustomSerializerTest.kt @@ -1,44 +1,85 @@ package com.akuleshov7.ktoml.decoders -import kotlinx.serialization.decodeFromString import com.akuleshov7.ktoml.Toml -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable +import kotlinx.serialization.* import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.test.Ignore import kotlin.test.Test +import kotlin.test.assertEquals class CustomSerializerTest { - object ColorAsStringSerializer : KSerializer { + object SinglePropertyAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SingleProperty", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: SingleProperty) { + } + + override fun deserialize(decoder: Decoder): SingleProperty { + val string = decoder.decodeString() + return SingleProperty(string.toLong() + 15) + } + } + + @Serializable(with = SinglePropertyAsStringSerializer::class) + data class SingleProperty(val rgb: Long) + + @Test + fun testDecodingWithCustomSerializerSingleProperty() { + assertEquals( + SingleProperty(15), + Toml.decodeFromString( + """ + rgb = "0" + """.trimIndent() + ) + ) + } + + @Serializable(with = SeveralPropertiesAsStringSerializer::class) + data class SeveralProperties(val rgb: Long, val brg: Long) + + object SeveralPropertiesAsStringSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: Color) { - TODO() + override fun serialize(encoder: Encoder, value: SeveralProperties) { } - override fun deserialize(decoder: Decoder): Color { - val value = decoder.decodeString() - return Color(value.toLong()) + override fun deserialize(decoder: Decoder): SeveralProperties { + val string = decoder.decodeString() + return SeveralProperties(string.toLong() + 15, string.toLong()) } } - @Serializable(with = ColorAsStringSerializer::class) - data class Color(val rgb: Long) + @Test + @Ignore + fun testDecodingWithCustomSerializerSeveralProperties() { + assertEquals( + SeveralProperties(15, 1), + Toml.decodeFromString( + """ + rgb = "0" + brg = "1" + """.trimIndent() + ) + ) + } @Serializable - data class Settings(val background: Color, val foreground: Color) + data class Settings(val background: SingleProperty, val foreground: SingleProperty) @Test + @Ignore fun testDecodingWithCustomSerializer() { println(Toml.decodeFromString( """ [background] - rgb = 0 + rgb = "0" [foreground] - rgb = 0 + rgb = "0" """.trimIndent() )) } diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt new file mode 100644 index 00000000..61dd567e --- /dev/null +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/SurrogateSerializerTest.kt @@ -0,0 +1,48 @@ +package com.akuleshov7.ktoml.decoders + +import com.akuleshov7.ktoml.Toml +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.test.Ignore +import kotlin.test.Test + +class SurrogateTest { + object ColorSerializer : KSerializer { + override val descriptor: SerialDescriptor = ColorSurrogate.serializer().descriptor + + override fun serialize(encoder: Encoder, value: Color) { + val surrogate = ColorSurrogate((value.rgb shr 16) and 0xff, (value.rgb shr 8) and 0xff, value.rgb and 0xff) + encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate) + } + + override fun deserialize(decoder: Decoder): Color { + val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer()) + return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b) + } + } + + @Serializable + @SerialName("Color") + private class ColorSurrogate(val r: Int, val g: Int, val b: Int) { + init { + require(r in 0..255 && g in 0..255 && b in 0..255) + } + } + + @Serializable(with = ColorSerializer::class) + class Color(val rgb: Int) + + @Test + @Ignore + fun testDecodingWithCustomSerializer() { + println(Toml.decodeFromString( + """ + r = 5 + g = 6 + b = 7 + """.trimIndent() + )) + } +}