Skip to content

Commit

Permalink
Fix the issue where decoding a nullable object or list could fail.
Browse files Browse the repository at this point in the history
Fixes #6.
  • Loading branch information
charleskorn committed Aug 31, 2019
1 parent bb2b1cb commit 52fda15
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 10 deletions.
22 changes: 18 additions & 4 deletions src/main/kotlin/com/charleskorn/kaml/YamlInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ private class YamlListInput(val list: YamlList, context: SerialModule, configura
return nextElementIndex++
}

override fun decodeNotNullMark(): Boolean = checkTypeAndDecodeFromCurrentValue("a (possibly null) scalar value") { decodeNotNullMark() }
override fun decodeNotNullMark(): Boolean {
if (!haveStartedReadingElements) {
return true
}

return currentElementDecoder.decodeNotNullMark()
}

override fun decodeString(): String = checkTypeAndDecodeFromCurrentValue("a string") { decodeString() }
override fun decodeInt(): Int = checkTypeAndDecodeFromCurrentValue("an integer") { decodeInt() }
override fun decodeLong(): Long = checkTypeAndDecodeFromCurrentValue("a long") { decodeLong() }
Expand All @@ -122,7 +129,7 @@ private class YamlListInput(val list: YamlList, context: SerialModule, configura
override fun decodeEnum(enumDescription: EnumDescriptor): Int = checkTypeAndDecodeFromCurrentValue("an enumeration value") { decodeEnum(enumDescription) }

private fun <T> checkTypeAndDecodeFromCurrentValue(expectedTypeDescription: String, action: YamlInput.() -> T): T {
if (!::currentElementDecoder.isInitialized) {
if (!haveStartedReadingElements) {
throw IncorrectTypeException("Expected $expectedTypeDescription, but got a list", list.location)
}

Expand Down Expand Up @@ -218,7 +225,14 @@ private class YamlMapInput(val map: YamlMap, context: SerialModule, configuratio
throw UnknownPropertyException(name, knownPropertyNames, location)
}

override fun decodeNotNullMark(): Boolean = checkTypeAndDecodeFromCurrentValue("a (possibly null) scalar value") { decodeNotNullMark() }
override fun decodeNotNullMark(): Boolean {
if (!haveStartedReadingEntries) {
return true
}

return fromCurrentValue { decodeNotNullMark() }
}

override fun decodeString(): String = checkTypeAndDecodeFromCurrentValue("a string") { decodeString() }
override fun decodeInt(): Int = checkTypeAndDecodeFromCurrentValue("an integer") { decodeInt() }
override fun decodeLong(): Long = checkTypeAndDecodeFromCurrentValue("a long") { decodeLong() }
Expand All @@ -231,7 +245,7 @@ private class YamlMapInput(val map: YamlMap, context: SerialModule, configuratio
override fun decodeEnum(enumDescription: EnumDescriptor): Int = checkTypeAndDecodeFromCurrentValue("an enumeration value") { decodeEnum(enumDescription) }

private fun <T> checkTypeAndDecodeFromCurrentValue(expectedTypeDescription: String, action: YamlInput.() -> T): T {
if (!::currentValueDecoder.isInitialized) {
if (!haveStartedReadingEntries) {
throw IncorrectTypeException("Expected $expectedTypeDescription, but got a map", map.location)
}

Expand Down
39 changes: 33 additions & 6 deletions src/test/kotlin/com/charleskorn/kaml/YamlReadingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,14 @@ object YamlReadingTest : Spek({
}
}

context("parsing that input as a nullable list") {
val result = Yaml.default.parse(makeNullable(String.serializer().list), input)

it("deserializes it to the expected value") {
assert(result).toBe(listOf("thing1", "thing2", "thing3"))
}
}

context("parsing that input with a serializer that uses YAML location information when throwing exceptions") {
it("throws an exception with the correct location information") {
assert({ Yaml.default.parse(LocationThrowingSerializer.list, input) }).toThrow<LocationInformationException> {
Expand Down Expand Up @@ -1063,6 +1071,25 @@ object YamlReadingTest : Spek({
}
}
}

context("given an object where the first property is nullable") {
val input = """
mariaDb:
host: "db.test.com"
""".trimIndent()

@Serializable
data class MariaDb(val host: String)

@Serializable
data class Server(val mariaDb: MariaDb? = null)

val result = Yaml.default.parse(Server.serializer(), input)

it("deserializes it to the expected object") {
assert(result).toBe(Server(MariaDb("db.test.com")))
}
}
}

describe("parsing values with a dynamically installed serializer") {
Expand Down Expand Up @@ -1097,7 +1124,7 @@ object YamlReadingTest : Spek({

describe("parsing values with mismatched types") {
context("given a list") {
mapOf(
listOf(
"a string" to StringSerializer,
"an integer" to IntSerializer,
"a long" to LongSerializer,
Expand All @@ -1110,8 +1137,8 @@ object YamlReadingTest : Spek({
"an enumeration value" to EnumSerializer(TestEnum::class),
"a map" to (StringSerializer to StringSerializer).map,
"an object" to ComplexStructure.serializer(),
"a (possibly null) scalar value" to makeNullable(StringSerializer)
).forEach { description, serializer ->
"a string" to makeNullable(StringSerializer)
).forEach { (description, serializer) ->
val input = "- thing"

context("parsing that input as $description") {
Expand Down Expand Up @@ -1171,7 +1198,7 @@ object YamlReadingTest : Spek({
}

context("given a map") {
mapOf(
listOf(
"a string" to StringSerializer,
"an integer" to IntSerializer,
"a long" to LongSerializer,
Expand All @@ -1183,8 +1210,8 @@ object YamlReadingTest : Spek({
"a character" to CharSerializer,
"an enumeration value" to EnumSerializer(TestEnum::class),
"a list" to StringSerializer.list,
"a (possibly null) scalar value" to makeNullable(StringSerializer)
).forEach { description, serializer ->
"a string" to makeNullable(StringSerializer)
).forEach { (description, serializer) ->
val input = "key: value"

context("parsing that input as $description") {
Expand Down

0 comments on commit 52fda15

Please sign in to comment.