From 2d31fbb2f504f2ba8136f09a9049e1b574d13e55 Mon Sep 17 00:00:00 2001 From: JojoIV Date: Mon, 16 Sep 2024 09:50:18 +0200 Subject: [PATCH 1/2] Fix Decoder not representing the current node correctly --- .../kotlin/com/charleskorn/kaml/YamlListInput.kt | 8 ++++++++ .../com/charleskorn/kaml/YamlMapLikeInputBase.kt | 10 ++++++++++ .../kotlin/com/charleskorn/kaml/YamlReadingTest.kt | 5 +---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/com/charleskorn/kaml/YamlListInput.kt b/src/commonMain/kotlin/com/charleskorn/kaml/YamlListInput.kt index 9675c986..1c5509e7 100644 --- a/src/commonMain/kotlin/com/charleskorn/kaml/YamlListInput.kt +++ b/src/commonMain/kotlin/com/charleskorn/kaml/YamlListInput.kt @@ -18,6 +18,7 @@ package com.charleskorn.kaml +import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder @@ -65,6 +66,13 @@ internal class YamlListInput(val list: YamlList, yaml: Yaml, context: Serializer override fun decodeChar(): Char = currentElementDecoder.decodeChar() override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = currentElementDecoder.decodeEnum(enumDescriptor) + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { + if (!haveStartedReadingElements) { + return super.decodeSerializableValue(deserializer) + } + return currentElementDecoder.decodeSerializableValue(deserializer) + } + private val haveStartedReadingElements: Boolean get() = nextElementIndex > 0 diff --git a/src/commonMain/kotlin/com/charleskorn/kaml/YamlMapLikeInputBase.kt b/src/commonMain/kotlin/com/charleskorn/kaml/YamlMapLikeInputBase.kt index 4bd2b292..bfabb190 100644 --- a/src/commonMain/kotlin/com/charleskorn/kaml/YamlMapLikeInputBase.kt +++ b/src/commonMain/kotlin/com/charleskorn/kaml/YamlMapLikeInputBase.kt @@ -18,6 +18,7 @@ package com.charleskorn.kaml +import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.modules.SerializersModule @@ -45,6 +46,15 @@ internal sealed class YamlMapLikeInputBase(map: YamlMap, yaml: Yaml, context: Se override fun decodeChar(): Char = fromCurrentValue { decodeChar() } override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = fromCurrentValue { decodeEnum(enumDescriptor) } + override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { + if (!haveStartedReadingEntries) { + return super.decodeSerializableValue(deserializer) + } + return fromCurrentValue { + decodeSerializableValue(deserializer) + } + } + protected fun fromCurrentValue(action: YamlInput.() -> T): T { try { return action(currentValueDecoder) diff --git a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt index 5709999d..0e0e6db3 100644 --- a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt +++ b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt @@ -2724,10 +2724,7 @@ private object DecodingFromYamlNodeSerializer : KSerializer { override fun deserialize(decoder: Decoder): DatabaseListing { check(decoder is YamlInput) - val currentMap = decoder.node.yamlMap.get("databaseListing") - checkNotNull(currentMap) - - val list = currentMap.entries.map { (_, value) -> + val list = decoder.node.yamlMap.entries.map { (_, value) -> decoder.yaml.decodeFromYamlNode(Database.serializer(), value) } From 288863e4ce44695f3f908f30c9ce91e19186457b Mon Sep 17 00:00:00 2001 From: JojoIV Date: Mon, 16 Sep 2024 10:02:20 +0200 Subject: [PATCH 2/2] Prepare fix for contextual deserialization of polymorphic scalars --- .../charleskorn/kaml/YamlContextualInput.kt | 6 +++- .../com/charleskorn/kaml/YamlReadingTest.kt | 32 ++++++++++++++----- .../com/charleskorn/kaml/YamlWritingTest.kt | 26 +++++++++++++++ .../testobjects/PolymorphicTestObjects.kt | 12 +++++-- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/commonMain/kotlin/com/charleskorn/kaml/YamlContextualInput.kt b/src/commonMain/kotlin/com/charleskorn/kaml/YamlContextualInput.kt index 6e80a01d..627b3f5f 100644 --- a/src/commonMain/kotlin/com/charleskorn/kaml/YamlContextualInput.kt +++ b/src/commonMain/kotlin/com/charleskorn/kaml/YamlContextualInput.kt @@ -24,7 +24,11 @@ import kotlinx.serialization.modules.SerializersModule internal class YamlContextualInput(node: YamlNode, yaml: Yaml, context: SerializersModule, configuration: YamlConfiguration) : YamlInput(node, yaml, context, configuration) { override fun decodeElementIndex(descriptor: SerialDescriptor): Int = throw IllegalStateException("Must call beginStructure() and use returned Decoder") - override fun decodeValue(): Any = throw IllegalStateException("Must call beginStructure() and use returned Decoder") + override fun decodeValue(): Any = when (node) { + is YamlScalar -> node.content + is YamlNull -> throw UnexpectedNullValueException(node.path) + else -> throw IllegalStateException("Must call beginStructure() and use returned Decoder") + } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = createFor(node, yaml, serializersModule, configuration, descriptor) diff --git a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt index 0e0e6db3..2b2d618c 100644 --- a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt +++ b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt @@ -1470,6 +1470,20 @@ class YamlReadingTest : FlatFunSpec({ } } + context("given some input for an object where the property value should be a sealed class (inline)") { + val input = """ + element: ! "abcdef" + """.trimIndent() + + context("parsing that input") { + val result = polymorphicYaml.decodeFromString(SealedWrapper.serializer(), input) + + test("deserializes it to a Kotlin object") { + result shouldBe SealedWrapper(TestSealedStructure.InlineSealedString("abcdef")) + } + } + } + context("given some input for an object where the property value is a literal") { val input = """ test: ! 42 @@ -1492,6 +1506,7 @@ class YamlReadingTest : FlatFunSpec({ value: -987 - ! value: 654 + - ! "testing" - ! value: "tests" """.trimIndent() @@ -1505,6 +1520,7 @@ class YamlReadingTest : FlatFunSpec({ TestSealedStructure.SimpleSealedString(null), TestSealedStructure.SimpleSealedInt(-987), TestSealedStructure.SimpleSealedInt(654), + TestSealedStructure.InlineSealedString("testing"), TestSealedStructure.SimpleSealedString("tests"), ) } @@ -1603,11 +1619,11 @@ class YamlReadingTest : FlatFunSpec({ val exception = shouldThrow { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) } exception.asClue { - it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString" + it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString" it.line shouldBe 1 it.column shouldBe 1 it.typeName shouldBe "someOtherType" - it.validTypeNames shouldBe setOf("sealedInt", "sealedString") + it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString") it.path shouldBe YamlPath.root } } @@ -1624,11 +1640,11 @@ class YamlReadingTest : FlatFunSpec({ val exception = shouldThrow { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) } exception.asClue { - it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString" + it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString" it.line shouldBe 1 it.column shouldBe 1 it.typeName shouldBe "someOtherType" - it.validTypeNames shouldBe setOf("sealedInt", "sealedString") + it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString") it.path shouldBe YamlPath.root } } @@ -1818,11 +1834,11 @@ class YamlReadingTest : FlatFunSpec({ val exception = shouldThrow { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) } exception.asClue { - it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString" + it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString" it.line shouldBe 1 it.column shouldBe 7 it.typeName shouldBe "someOtherType" - it.validTypeNames shouldBe setOf("sealedInt", "sealedString") + it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString") it.path shouldBe YamlPath.root.withMapElementKey("type", Location(1, 1)).withMapElementValue(Location(1, 7)) } } @@ -2028,11 +2044,11 @@ class YamlReadingTest : FlatFunSpec({ val exception = shouldThrow { polymorphicYaml.decodeFromString(TestSealedStructure.serializer(), input) } exception.asClue { - it.message shouldBe "Unknown type 'someOtherType'. Known types are: sealedInt, sealedString" + it.message shouldBe "Unknown type 'someOtherType'. Known types are: inlineString, sealedInt, sealedString" it.line shouldBe 1 it.column shouldBe 7 it.typeName shouldBe "someOtherType" - it.validTypeNames shouldBe setOf("sealedInt", "sealedString") + it.validTypeNames shouldBe setOf("inlineString", "sealedInt", "sealedString") it.path shouldBe YamlPath.root.withMapElementKey("kind", Location(1, 1)).withMapElementValue(Location(1, 7)) } } diff --git a/src/commonTest/kotlin/com/charleskorn/kaml/YamlWritingTest.kt b/src/commonTest/kotlin/com/charleskorn/kaml/YamlWritingTest.kt index 22c8e5f9..2ed733d6 100644 --- a/src/commonTest/kotlin/com/charleskorn/kaml/YamlWritingTest.kt +++ b/src/commonTest/kotlin/com/charleskorn/kaml/YamlWritingTest.kt @@ -845,6 +845,18 @@ class YamlWritingTest : FlatFunSpec({ } } + context("serializing a sealed type (inline)") { + val input = TestSealedStructure.InlineSealedString("abc") + val output = polymorphicYaml.encodeToString(TestSealedStructure.serializer(), input) + val expectedYaml = """ + ! "abc" + """.trimIndent() + + test("returns the value serialized in the expected YAML form") { + output shouldBe expectedYaml + } + } + context("serializing an unsealed type") { val input = UnsealedString("blah") val output = polymorphicYaml.encodeToString(PolymorphicSerializer(UnsealedClass::class), input) @@ -883,11 +895,24 @@ class YamlWritingTest : FlatFunSpec({ } } + context("serializing a polymorphic value (inline) as a property value") { + val input = SealedWrapper(TestSealedStructure.InlineSealedString("abc")) + val output = polymorphicYaml.encodeToString(SealedWrapper.serializer(), input) + val expectedYaml = """ + element: ! "abc" + """.trimIndent() + + test("returns the value serialized in the expected YAML form") { + output shouldBe expectedYaml + } + } + context("serializing a list of polymorphic values") { val input = listOf( TestSealedStructure.SimpleSealedInt(5), TestSealedStructure.SimpleSealedString("some test"), TestSealedStructure.SimpleSealedInt(-20), + TestSealedStructure.InlineSealedString("more test"), TestSealedStructure.SimpleSealedString(null), null, ) @@ -901,6 +926,7 @@ class YamlWritingTest : FlatFunSpec({ value: "some test" - ! value: -20 + - ! "more test" - ! value: null - null diff --git a/src/commonTest/kotlin/com/charleskorn/kaml/testobjects/PolymorphicTestObjects.kt b/src/commonTest/kotlin/com/charleskorn/kaml/testobjects/PolymorphicTestObjects.kt index 72786a6a..b38af980 100644 --- a/src/commonTest/kotlin/com/charleskorn/kaml/testobjects/PolymorphicTestObjects.kt +++ b/src/commonTest/kotlin/com/charleskorn/kaml/testobjects/PolymorphicTestObjects.kt @@ -28,16 +28,22 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule +import kotlin.jvm.JvmInline @Serializable -sealed class TestSealedStructure { +sealed interface TestSealedStructure { @Serializable @SerialName("sealedInt") - data class SimpleSealedInt(val value: Int) : TestSealedStructure() + data class SimpleSealedInt(val value: Int) : TestSealedStructure @Serializable @SerialName("sealedString") - data class SimpleSealedString(val value: String?) : TestSealedStructure() + data class SimpleSealedString(val value: String?) : TestSealedStructure + + @Serializable + @SerialName("inlineString") + @JvmInline + value class InlineSealedString(val value: String) : TestSealedStructure } @Serializable