Skip to content

Commit

Permalink
fix: Decoder does not represent the current node correctly (#617)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jojo4GH authored Nov 1, 2024
1 parent 0fa2c17 commit dbb29e7
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 4 deletions.
8 changes: 8 additions & 0 deletions src/commonMain/kotlin/com/charleskorn/kaml/YamlListInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
if (!haveStartedReadingElements) {
return super.decodeSerializableValue(deserializer)
}
return currentElementDecoder.decodeSerializableValue(deserializer)
}

private val haveStartedReadingElements: Boolean
get() = nextElementIndex > 0

Expand Down
10 changes: 10 additions & 0 deletions src/commonMain/kotlin/com/charleskorn/kaml/YamlMapLikeInputBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.charleskorn.kaml

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.modules.SerializersModule

Expand Down Expand Up @@ -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 <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
if (!haveStartedReadingEntries) {
return super.decodeSerializableValue(deserializer)
}
return fromCurrentValue {
decodeSerializableValue(deserializer)
}
}

protected fun <T> fromCurrentValue(action: YamlInput.() -> T): T {
try {
return action(currentValueDecoder)
Expand Down
53 changes: 49 additions & 4 deletions src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)
}
}
}
}
})
Expand Down Expand Up @@ -2204,10 +2221,7 @@ private object DecodingFromYamlNodeSerializer : KSerializer<DatabaseListing> {
override fun deserialize(decoder: Decoder): DatabaseListing {
check(decoder is YamlInput)

val currentMap = decoder.node.yamlMap.get<YamlMap>("databaseListing")
checkNotNull(currentMap)

val list = currentMap.entries.map { (_, value) ->
val list = decoder.node.yamlMap.entries.map { (_, value) ->
decoder.yaml.decodeFromYamlNode(Database.serializer(), value)
}

Expand All @@ -2216,3 +2230,34 @@ private object DecodingFromYamlNodeSerializer : KSerializer<DatabaseListing> {

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<ObjectWithCustomSerializer> {
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))
}
}

0 comments on commit dbb29e7

Please sign in to comment.