From 00b9e0fa4ae015fdb34339271d6723bbc5b16871 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Thu, 4 Jun 2020 18:13:01 +0300 Subject: [PATCH 1/4] Introduce treatNullAsMissing flag for JSON and implement it for streaming parser --- .../src/kotlinx/serialization/json/Json.kt | 2 + .../serialization/json/JsonConfiguration.kt | 1 + .../serialization/json/internal/JsonReader.kt | 12 ++- .../json/internal/StreamingJsonInput.kt | 26 ++++- .../json/JsonUseDefaultOnNullTest.kt | 98 +++++++++++++++++++ 5 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt diff --git a/runtime/commonMain/src/kotlinx/serialization/json/Json.kt b/runtime/commonMain/src/kotlinx/serialization/json/Json.kt index 915665c16..a7b7ce5d4 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/Json.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/Json.kt @@ -279,6 +279,7 @@ public class JsonBuilder { public var prettyPrint: Boolean = false public var unquotedPrint: Boolean = false public var indent: String = " " + public var treatNullAsMissing: Boolean = false public var useArrayPolymorphism: Boolean = false public var classDiscriminator: String = "type" public var serialModule: SerialModule = EmptyModule @@ -293,6 +294,7 @@ public class JsonBuilder { prettyPrint, unquotedPrint, indent, + treatNullAsMissing, useArrayPolymorphism, classDiscriminator ) diff --git a/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt b/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt index 2a8788085..8a5db8f6f 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt @@ -54,6 +54,7 @@ public data class JsonConfiguration @UnstableDefault constructor( internal val prettyPrint: Boolean = false, internal val unquotedPrint: Boolean = false, internal val indent: String = defaultIndent, + internal val treatNullAsMissing: Boolean = false, internal val useArrayPolymorphism: Boolean = false, internal val classDiscriminator: String = defaultDiscriminator ) { diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt index 8a13a8b7a..56eb6075e 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/JsonReader.kt @@ -140,10 +140,16 @@ internal class JsonReader(private val source: String) { fun takeString(): String { if (tokenClass != TC_OTHER && tokenClass != TC_STRING) fail( - "Expected string or non-null literal", tokenPosition) + "Expected string or non-null literal", tokenPosition + ) return takeStringInternal() } + fun peekString(isLenient: Boolean): String? { + return if (tokenClass != TC_STRING && (!isLenient || tokenClass != TC_OTHER)) null + else takeStringInternal(advance = false) + } + fun takeStringQuoted(): String { if (tokenClass != TC_STRING) fail( "Expected string literal with quotes. $lenientHint", @@ -157,11 +163,11 @@ internal class JsonReader(private val source: String) { return takeStringInternal() } - private fun takeStringInternal(): String { + private fun takeStringInternal(advance: Boolean = true): String { val prevStr = if (offset < 0) String(buf, 0, length) else source.substring(offset, offset + length) - nextToken() + if (advance) nextToken() return prevStr } diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt index e0cd1fc43..2f2d61322 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt @@ -5,6 +5,7 @@ package kotlinx.serialization.json.internal import kotlinx.serialization.* +import kotlinx.serialization.CompositeDecoder.Companion.UNKNOWN_NAME import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* import kotlinx.serialization.modules.* @@ -108,6 +109,19 @@ internal class StreamingJsonInput internal constructor( } } + private fun treatAsMissing(descriptor: SerialDescriptor, index: Int): Boolean { + if (!configuration.treatNullAsMissing) return false + val elementDescriptor = descriptor.getElementDescriptor(index) + if (reader.tokenClass == TC_NULL && !elementDescriptor.isNullable) return true // null for non-nullable + if (elementDescriptor.kind == UnionKind.ENUM_KIND) { + val enumValue = reader.peekString(configuration.isLenient) + ?: return false // if value is not a string, decodeEnum() will throw correct exception + val enumIndex = elementDescriptor.getElementIndex(enumValue) + if (enumIndex == UNKNOWN_NAME) return true + } + return false + } + private fun decodeObjectIndex(tokenClass: Byte, descriptor: SerialDescriptor): Int { if (tokenClass == TC_COMMA && !reader.canBeginValue) { reader.fail("Unexpected trailing comma") @@ -119,11 +133,17 @@ internal class StreamingJsonInput internal constructor( reader.requireTokenClass(TC_COLON) { "Expected ':'" } reader.nextToken() val index = descriptor.getElementIndex(key) - if (index != CompositeDecoder.UNKNOWN_NAME) { - return index + val isUnknown = if (index != UNKNOWN_NAME) { + if (treatAsMissing(descriptor, index)) { + false // skip known element + } else { + return index // read known element + } + } else { + true // unknown element } - if (!configuration.ignoreUnknownKeys) { + if (isUnknown && !configuration.ignoreUnknownKeys) { reader.fail( "Encountered an unknown key '$key'. You can enable 'JsonConfiguration.ignoreUnknownKeys' property" + " to ignore unknown keys" diff --git a/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt b/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt new file mode 100644 index 000000000..6cc52be5c --- /dev/null +++ b/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.json + +import kotlinx.serialization.* +import kotlin.test.* + +class JsonUseDefaultOnNullTest : JsonTestBase() { + @Serializable + data class WithBoolean(val b: Boolean = false) + + @Serializable + data class WithEnum(val e: SampleEnum = SampleEnum.OptionC) + + @Serializable + data class MultipleValues( + val data: StringData, + val data2: IntData = IntData(0), + val i: Int = 42, + val e: SampleEnum = SampleEnum.OptionA, + val foo: String + ) + + val json = Json(JsonConfiguration.Default.copy(treatNullAsMissing = true, isLenient = true)) + + private inline fun doTest(inputs: List, expected: T) { + for (input in inputs) { + // todo : this overload works incorrectly +// parametrizedTest(json) { j -> + val parsed = json.parse(serializer(), input) + assertEquals(expected, parsed, "Failed on input: $input") +// } + } + } + + @Test + fun testBoolean() = doTest( + listOf( + """{"b":false}""", + """{"b":null}""", + """{}""", + ), + WithBoolean() + ) + + @Test + fun testEnum() { + doTest( + listOf( + """{"e":unknown_value}""", + """{"e":"unknown_value"}""", + """{"e":null}""", + """{}""", + ), + WithEnum() + ) + assertFailsWith { + json.parse(WithEnum.serializer(), """{"e":{"x":"definitely not a valid enum value"}}""") + } + assertFailsWith { // test user still sees exception on missing quotes + Json(json.configuration.copy(isLenient = false)).parse(WithEnum.serializer(), """{"e":unknown_value}""") + } + } + + @Test + fun testAll() { + val testData = mapOf( + """{"data":{"data":"foo"},"data2":null,"i":null,"e":null,"foo":"bar"}""" to MultipleValues( + StringData("foo"), + foo = "bar" + ), + """{"data":{"data":"foo"},"data2":{"intV":42},"i":null,"e":null,"foo":"bar"}""" to MultipleValues( + StringData( + "foo" + ), IntData(42), foo = "bar" + ), + """{"data":{"data":"foo"},"data2":{"intV":42},"i":0,"e":"NoOption","foo":"bar"}""" to MultipleValues( + StringData("foo"), + IntData(42), + i = 0, + foo = "bar" + ), + """{"data":{"data":"foo"},"data2":{"intV":42},"i":0,"e":"OptionC","foo":"bar"}""" to MultipleValues( + StringData("foo"), + IntData(42), + i = 0, + e = SampleEnum.OptionC, + foo = "bar" + ), + ) + for ((input, expected) in testData) { + assertEquals(expected, json.parse(MultipleValues.serializer(), input), "Failed on input: $input") + } + } + +} From 5e0acfc1bddee4de27a11eb651eb6d53e395d088 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Thu, 4 Jun 2020 20:08:24 +0300 Subject: [PATCH 2/4] Support treatNullAsMissing for TreeJsonInput fix error-prone test helper method Fix legacyJsIr tests that can't use serializer() --- .../serialization/json/internal/TreeJsonInput.kt | 15 ++++++++++++++- .../serialization/json/JsonMapKeysTest.kt | 6 +++--- .../kotlinx/serialization/json/JsonTestBase.kt | 8 ++++---- .../json/JsonUseDefaultOnNullTest.kt | 16 ++++++++-------- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt index eef6cf709..a4615648a 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt @@ -156,10 +156,23 @@ private open class JsonTreeInput( ) : AbstractJsonTreeInput(json, value) { private var position = 0 + private fun treatAsMissing(descriptor: SerialDescriptor, index: Int, tag: String): Boolean { + if (!configuration.treatNullAsMissing) return false + val elementDescriptor = descriptor.getElementDescriptor(index) + if (currentElement(tag).isNull && !elementDescriptor.isNullable) return true // null for non-nullable + if (elementDescriptor.kind == UnionKind.ENUM_KIND) { + val enumValue = (currentElement(tag) as? JsonElement)?.contentOrNull + ?: return false // if value is not a string, decodeEnum() will throw correct exception + val enumIndex = elementDescriptor.getElementIndex(enumValue) + if (enumIndex == CompositeDecoder.UNKNOWN_NAME) return true + } + return false + } + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { while (position < descriptor.elementsCount) { val name = descriptor.getTag(position++) - if (name in value) { + if (name in value && !treatAsMissing(descriptor, position - 1, name)) { return position - 1 } } diff --git a/runtime/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt b/runtime/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt index 743c79f47..a59b72310 100644 --- a/runtime/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt +++ b/runtime/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt @@ -5,7 +5,7 @@ package kotlinx.serialization.json import kotlinx.serialization.* -import kotlinx.serialization.test.assertStringFormAndRestored +import kotlinx.serialization.test.* import kotlin.test.* class JsonMapKeysTest : JsonTestBase() { @@ -19,12 +19,12 @@ class JsonMapKeysTest : JsonTestBase() { private data class WithComplexKey(val map: Map) @Test - fun testMapKeysShouldBeStrings() = parametrizedTest(default) { fmt -> + fun testMapKeysShouldBeStrings() = parametrizedTest(default) { assertStringFormAndRestored( """{"map":{"10":10,"20":20}}""", WithMap(mapOf(10L to 10L, 20L to 20L)), WithMap.serializer(), - fmt + this ) } diff --git a/runtime/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt b/runtime/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt index 7c126acbe..4fece2fd2 100644 --- a/runtime/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt +++ b/runtime/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt @@ -100,7 +100,7 @@ abstract class JsonTestBase { processResults(streamingResult, treeResult) } - private inner class DualFormat( + private inner class SwitchableJson( val json: Json, val useStreaming: Boolean, override val context: SerialModule = EmptyModule @@ -114,9 +114,9 @@ abstract class JsonTestBase { } } - protected fun parametrizedTest(json: Json, test: StringFormat.(StringFormat) -> Unit) { - val streamingResult = runCatching { json.test(DualFormat(json, true)) } - val treeResult = runCatching { json.test(DualFormat(json, false)) } + protected fun parametrizedTest(json: Json, test: StringFormat.() -> Unit) { + val streamingResult = runCatching { SwitchableJson(json, true).test() } + val treeResult = runCatching { SwitchableJson(json, false).test() } processResults(streamingResult, treeResult) } diff --git a/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt b/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt index 6cc52be5c..b879b03d4 100644 --- a/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt +++ b/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt @@ -25,13 +25,11 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { val json = Json(JsonConfiguration.Default.copy(treatNullAsMissing = true, isLenient = true)) - private inline fun doTest(inputs: List, expected: T) { + private inline fun doTest(inputs: List, expected: T, serializer: KSerializer) { for (input in inputs) { - // todo : this overload works incorrectly -// parametrizedTest(json) { j -> - val parsed = json.parse(serializer(), input) - assertEquals(expected, parsed, "Failed on input: $input") -// } + parametrizedTest(json) { + assertEquals(expected, parse(serializer, input), "Failed on input: $input") + } } } @@ -42,7 +40,8 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { """{"b":null}""", """{}""", ), - WithBoolean() + WithBoolean(), + WithBoolean.serializer() ) @Test @@ -54,7 +53,8 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { """{"e":null}""", """{}""", ), - WithEnum() + WithEnum(), + WithEnum.serializer() ) assertFailsWith { json.parse(WithEnum.serializer(), """{"e":{"x":"definitely not a valid enum value"}}""") From 6f9b36d53809b6450bcb4943fb2c1dbf7d918e98 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Fri, 19 Jun 2020 17:11:57 +0300 Subject: [PATCH 3/4] Review feedback: change flag name and test names --- .../src/kotlinx/serialization/json/Json.kt | 4 ++-- .../kotlinx/serialization/json/JsonConfiguration.kt | 6 +++++- .../json/internal/StreamingJsonInput.kt | 3 +-- .../serialization/json/internal/TreeJsonInput.kt | 3 +-- ...Test.kt => JsonUseDefaultOnNullAndUnknownTest.kt} | 12 ++++++------ 5 files changed, 15 insertions(+), 13 deletions(-) rename runtime/commonTest/src/kotlinx/serialization/json/{JsonUseDefaultOnNullTest.kt => JsonUseDefaultOnNullAndUnknownTest.kt} (89%) diff --git a/runtime/commonMain/src/kotlinx/serialization/json/Json.kt b/runtime/commonMain/src/kotlinx/serialization/json/Json.kt index a7b7ce5d4..ffe9ca63b 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/Json.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/Json.kt @@ -279,7 +279,7 @@ public class JsonBuilder { public var prettyPrint: Boolean = false public var unquotedPrint: Boolean = false public var indent: String = " " - public var treatNullAsMissing: Boolean = false + public var coerceInputValues: Boolean = false public var useArrayPolymorphism: Boolean = false public var classDiscriminator: String = "type" public var serialModule: SerialModule = EmptyModule @@ -294,7 +294,7 @@ public class JsonBuilder { prettyPrint, unquotedPrint, indent, - treatNullAsMissing, + coerceInputValues, useArrayPolymorphism, classDiscriminator ) diff --git a/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt b/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt index 8a5db8f6f..12f66e696 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt @@ -37,6 +37,10 @@ import kotlin.jvm.* * * * [indent] specifies indent string to use with [prettyPrint] mode. * + * * [coerceInputValues] allows coercing incorrect json value to the default property value in the following cases: + * 1. Json value is `null` but property type is non-nullable. + * 2. Property type is an enum type, but json value contains unknown enum member. + * * * [useArrayPolymorphism] switches polymorphic serialization to the default array format. * This is an option for legacy JSON format and should not be generally used. * @@ -54,7 +58,7 @@ public data class JsonConfiguration @UnstableDefault constructor( internal val prettyPrint: Boolean = false, internal val unquotedPrint: Boolean = false, internal val indent: String = defaultIndent, - internal val treatNullAsMissing: Boolean = false, + internal val coerceInputValues: Boolean = false, internal val useArrayPolymorphism: Boolean = false, internal val classDiscriminator: String = defaultDiscriminator ) { diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt index 2f2d61322..2d8b2217b 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt @@ -110,7 +110,6 @@ internal class StreamingJsonInput internal constructor( } private fun treatAsMissing(descriptor: SerialDescriptor, index: Int): Boolean { - if (!configuration.treatNullAsMissing) return false val elementDescriptor = descriptor.getElementDescriptor(index) if (reader.tokenClass == TC_NULL && !elementDescriptor.isNullable) return true // null for non-nullable if (elementDescriptor.kind == UnionKind.ENUM_KIND) { @@ -134,7 +133,7 @@ internal class StreamingJsonInput internal constructor( reader.nextToken() val index = descriptor.getElementIndex(key) val isUnknown = if (index != UNKNOWN_NAME) { - if (treatAsMissing(descriptor, index)) { + if (configuration.coerceInputValues && treatAsMissing(descriptor, index)) { false // skip known element } else { return index // read known element diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt index a4615648a..9148156f9 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt @@ -157,7 +157,6 @@ private open class JsonTreeInput( private var position = 0 private fun treatAsMissing(descriptor: SerialDescriptor, index: Int, tag: String): Boolean { - if (!configuration.treatNullAsMissing) return false val elementDescriptor = descriptor.getElementDescriptor(index) if (currentElement(tag).isNull && !elementDescriptor.isNullable) return true // null for non-nullable if (elementDescriptor.kind == UnionKind.ENUM_KIND) { @@ -172,7 +171,7 @@ private open class JsonTreeInput( override fun decodeElementIndex(descriptor: SerialDescriptor): Int { while (position < descriptor.elementsCount) { val name = descriptor.getTag(position++) - if (name in value && !treatAsMissing(descriptor, position - 1, name)) { + if (name in value && (!configuration.coerceInputValues || !treatAsMissing(descriptor, position - 1, name))) { return position - 1 } } diff --git a/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt b/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullAndUnknownTest.kt similarity index 89% rename from runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt rename to runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullAndUnknownTest.kt index b879b03d4..76c91c0dd 100644 --- a/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullTest.kt +++ b/runtime/commonTest/src/kotlinx/serialization/json/JsonUseDefaultOnNullAndUnknownTest.kt @@ -7,7 +7,7 @@ package kotlinx.serialization.json import kotlinx.serialization.* import kotlin.test.* -class JsonUseDefaultOnNullTest : JsonTestBase() { +class JsonUseDefaultOnNullAndUnknownTest : JsonTestBase() { @Serializable data class WithBoolean(val b: Boolean = false) @@ -23,7 +23,7 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { val foo: String ) - val json = Json(JsonConfiguration.Default.copy(treatNullAsMissing = true, isLenient = true)) + val json = Json(JsonConfiguration.Default.copy(coerceInputValues = true, isLenient = true)) private inline fun doTest(inputs: List, expected: T, serializer: KSerializer) { for (input in inputs) { @@ -34,7 +34,7 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { } @Test - fun testBoolean() = doTest( + fun testUseDefaultOnNonNullableBoolean() = doTest( listOf( """{"b":false}""", """{"b":null}""", @@ -45,7 +45,7 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { ) @Test - fun testEnum() { + fun testUseDefaultOnUnknownEnum() { doTest( listOf( """{"e":unknown_value}""", @@ -65,8 +65,8 @@ class JsonUseDefaultOnNullTest : JsonTestBase() { } @Test - fun testAll() { - val testData = mapOf( + fun testUseDefaultInMultipleCases() { + val testData = mapOf( """{"data":{"data":"foo"},"data2":null,"i":null,"e":null,"foo":"bar"}""" to MultipleValues( StringData("foo"), foo = "bar" From c62509e8bffb5b2efa6cd30fd9f1b27fe6662bd8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 22 Jun 2020 12:02:42 +0300 Subject: [PATCH 4/4] rename --- .../serialization/json/internal/StreamingJsonInput.kt | 7 +++++-- .../kotlinx/serialization/json/internal/TreeJsonInput.kt | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt index 2d8b2217b..c62997eb8 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonInput.kt @@ -109,7 +109,10 @@ internal class StreamingJsonInput internal constructor( } } - private fun treatAsMissing(descriptor: SerialDescriptor, index: Int): Boolean { + /* + * Checks whether JSON has `null` value for non-null property or unknown enum value for enum property + */ + private fun coerceInputValue(descriptor: SerialDescriptor, index: Int): Boolean { val elementDescriptor = descriptor.getElementDescriptor(index) if (reader.tokenClass == TC_NULL && !elementDescriptor.isNullable) return true // null for non-nullable if (elementDescriptor.kind == UnionKind.ENUM_KIND) { @@ -133,7 +136,7 @@ internal class StreamingJsonInput internal constructor( reader.nextToken() val index = descriptor.getElementIndex(key) val isUnknown = if (index != UNKNOWN_NAME) { - if (configuration.coerceInputValues && treatAsMissing(descriptor, index)) { + if (configuration.coerceInputValues && coerceInputValue(descriptor, index)) { false // skip known element } else { return index // read known element diff --git a/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt b/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt index 9148156f9..3200934da 100644 --- a/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt +++ b/runtime/commonMain/src/kotlinx/serialization/json/internal/TreeJsonInput.kt @@ -156,7 +156,10 @@ private open class JsonTreeInput( ) : AbstractJsonTreeInput(json, value) { private var position = 0 - private fun treatAsMissing(descriptor: SerialDescriptor, index: Int, tag: String): Boolean { + /* + * Checks whether JSON has `null` value for non-null property or unknown enum value for enum property + */ + private fun coerceInputValue(descriptor: SerialDescriptor, index: Int, tag: String): Boolean { val elementDescriptor = descriptor.getElementDescriptor(index) if (currentElement(tag).isNull && !elementDescriptor.isNullable) return true // null for non-nullable if (elementDescriptor.kind == UnionKind.ENUM_KIND) { @@ -171,7 +174,7 @@ private open class JsonTreeInput( override fun decodeElementIndex(descriptor: SerialDescriptor): Int { while (position < descriptor.elementsCount) { val name = descriptor.getTag(position++) - if (name in value && (!configuration.coerceInputValues || !treatAsMissing(descriptor, position - 1, name))) { + if (name in value && (!configuration.coerceInputValues || !coerceInputValue(descriptor, position - 1, name))) { return position - 1 } }