diff options
author | Leonid Startsev <sandwwraith@users.noreply.github.com> | 2023-09-14 19:53:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-14 19:53:40 +0200 |
commit | 7d287c84e83e6af4c5c0ecb27cd573db9d0cffd9 (patch) | |
tree | 85a1425752c4ee6d8d3f5cd3a34a9525ce4d5d5c | |
parent | 01fcfeefca0ccfa836dcddac8d6fc7a9a409e2cf (diff) | |
download | kotlinx.serialization-7d287c84e83e6af4c5c0ecb27cd573db9d0cffd9.tar.gz |
Support decoding maps with boolean keys (#2440)
We ignore quoted/unquoted state when decoding maps with number keys, so it is logical to do the same for boolean maps.
Fixes #2438
4 files changed, 42 insertions, 27 deletions
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt index 61f31de9..560e51fe 100644 --- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt @@ -5,6 +5,7 @@ package kotlinx.serialization.json import kotlinx.serialization.* +import kotlinx.serialization.builtins.* import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor @@ -42,6 +43,9 @@ class JsonMapKeysTest : JsonTestBase() { private data class WithMap(val map: Map<Long, Long>) @Serializable + private data class WithBooleanMap(val map: Map<Boolean, Boolean>) + + @Serializable private data class WithValueKeyMap(val map: Map<PrimitiveCarrier, Long>) @Serializable @@ -60,13 +64,42 @@ class JsonMapKeysTest : JsonTestBase() { private data class WithContextualKey(val map: Map<@Contextual ContextualValue, Long>) @Test - fun testMapKeysShouldBeStrings() = parametrizedTest(default) { + fun testMapKeysSupportNumbers() = parametrizedTest { assertStringFormAndRestored( """{"map":{"10":10,"20":20}}""", WithMap(mapOf(10L to 10L, 20L to 20L)), WithMap.serializer(), - this + default + ) + } + + @Test + fun testMapKeysSupportBooleans() = parametrizedTest { + assertStringFormAndRestored( + """{"map":{"true":false,"false":true}}""", + WithBooleanMap(mapOf(true to false, false to true)), + WithBooleanMap.serializer(), + default + ) + } + + // As a result of quoting ignorance when parsing primitives, it is possible to parse unquoted maps if Kotlin keys are non-string primitives. + // This is not spec-compliant, but I do not see any problems with it. + @Test + fun testMapDeserializesUnquotedKeys() = parametrizedTest { + assertEquals(WithMap(mapOf(10L to 10L, 20L to 20L)), default.decodeFromString("""{"map":{10:10,20:20}}""")) + assertEquals( + WithBooleanMap(mapOf(true to false, false to true)), + default.decodeFromString("""{"map":{true:false,false:true}}""") ) + assertFailsWithSerial("JsonDecodingException") { + default.decodeFromString( + MapSerializer( + String.serializer(), + Boolean.serializer() + ),"""{"map":{true:false,false:true}}""" + ) + } } @Test diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt index 41d3c2c2..d24c7d7c 100644 --- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt @@ -37,7 +37,7 @@ class LenientTest : JsonTestBase() { @Test fun testQuotedBoolean() = parametrizedTest { val json = """{"i":1, "l":2, "b":"true", "s":"string"}""" - assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), json, it) } + assertEquals(value, default.decodeFromString(Holder.serializer(), json, it)) assertEquals(value, lenient.decodeFromString(Holder.serializer(), json, it)) } diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt index 6cedabce..ce930090 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt @@ -268,23 +268,14 @@ internal open class StreamingJsonDecoder( } } - + /* + * The primitives are allowed to be quoted and unquoted + * to simplify map key parsing and integrations with third-party API. + */ override fun decodeBoolean(): Boolean { - /* - * We prohibit any boolean literal that is not strictly 'true' or 'false' as it is considered way too - * error-prone, but allow quoted literal in relaxed mode for booleans. - */ - return if (configuration.isLenient) { - lexer.consumeBooleanLenient() - } else { - lexer.consumeBoolean() - } + return lexer.consumeBooleanLenient() } - /* - * The rest of the primitives are allowed to be quoted and unquoted - * to simplify integrations with third-party API. - */ override fun decodeByte(): Byte { val value = lexer.consumeNumericLiteral() // Check for overflow diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt index 40a3e41f..4b355f65 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt @@ -91,16 +91,7 @@ private sealed class AbstractJsonTreeDecoder( override fun decodeTaggedNotNullMark(tag: String): Boolean = currentElement(tag) !== JsonNull override fun decodeTaggedBoolean(tag: String): Boolean { - val value = getPrimitiveValue(tag) - if (!json.configuration.isLenient) { - val literal = value.asLiteral("boolean") - if (literal.isString) throw JsonDecodingException( - -1, "Boolean literal for key '$tag' should be unquoted.\n$lenientHint", currentObject().toString() - ) - } - return value.primitive("boolean") { - booleanOrNull ?: throw IllegalArgumentException() /* Will be handled by 'primitive' */ - } + return getPrimitiveValue(tag).primitive("boolean", JsonPrimitive::booleanOrNull) } override fun decodeTaggedByte(tag: String) = getPrimitiveValue(tag).primitive("byte") { |