From dbb29e76417fefbab830d64ea133183709a6dbf6 Mon Sep 17 00:00:00 2001 From: Jonas Broeckmann <36777568+Jojo4GH@users.noreply.github.com> Date: Fri, 1 Nov 2024 03:56:02 +0100 Subject: [PATCH] fix: `Decoder` does not represent the current node correctly (#617) --- .../com/charleskorn/kaml/YamlListInput.kt | 8 +++ .../charleskorn/kaml/YamlMapLikeInputBase.kt | 10 ++++ .../com/charleskorn/kaml/YamlReadingTest.kt | 53 +++++++++++++++++-- 3 files changed, 67 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 1c190803..432dceed 100644 --- a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt +++ b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt @@ -2100,6 +2100,23 @@ class YamlReadingTest : FlatFunSpec({ result shouldBe ServerConfig(DatabaseListing(listOf(Database("A"), Database("B")))) } } + + context("decoding with a custom serializer for a non-root node") { + val theInput = """ + objectWithCustomSerializer: + - cats + - dogs + - birds + """.trimIndent() + + val result = Yaml.default.decodeFromString(ObjectContainingObjectWithCustomSerializer.serializer(), theInput) + + test("decodes the Yaml as an ObjectContainingObjectWithCustomSerializer") { + result shouldBe ObjectContainingObjectWithCustomSerializer( + ObjectWithCustomSerializer("cats;dogs;birds"), + ) + } + } } } }) @@ -2204,10 +2221,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) } @@ -2216,3 +2230,34 @@ private object DecodingFromYamlNodeSerializer : KSerializer { override fun serialize(encoder: Encoder, value: DatabaseListing) = throw UnsupportedOperationException() } + +@Serializable +private data class ObjectContainingObjectWithCustomSerializer( + val objectWithCustomSerializer: ObjectWithCustomSerializer, +) + +@Serializable(with = SerializerForObjectWithCustomSerializer::class) +private data class ObjectWithCustomSerializer( + val combinedValues: String, +) + +private object SerializerForObjectWithCustomSerializer : KSerializer { + private const val ValuesSeparator = ";" + + private val listSerializer = ListSerializer(String.serializer()) + override val descriptor = listSerializer.descriptor + + override fun serialize(encoder: Encoder, value: ObjectWithCustomSerializer) { + encoder.encodeSerializableValue(listSerializer, value.combinedValues.split(ValuesSeparator)) + } + + override fun deserialize(decoder: Decoder): ObjectWithCustomSerializer { + check(decoder is YamlInput) + + // Intentionally parse the values from the current yaml node + val values = decoder.node.yamlList.items + .map { it.yamlScalar.content } + + return ObjectWithCustomSerializer(values.joinToString(ValuesSeparator)) + } +}