diff options
author | Vsevolod Tolstopyatov <qwwdfsad@gmail.com> | 2021-06-15 16:23:44 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-15 16:23:44 +0300 |
commit | b051cb3f846dbcfc8cdcdd863c5f4d5978fa7941 (patch) | |
tree | 2c03e18cfab0a96df9530dbdf21b6286e52e27f9 /formats/json | |
parent | 2ef890c8a2c7367c741efb4613aa3195eebe24e1 (diff) | |
download | kotlinx.serialization-b051cb3f846dbcfc8cdcdd863c5f4d5978fa7941.tar.gz |
Throw JsonDecodingException instead of ClassCastException during unexpected null in TreeJsonDecoder (#1550)
Diffstat (limited to 'formats/json')
-rw-r--r-- | formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt | 32 | ||||
-rw-r--r-- | formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt | 15 |
2 files changed, 33 insertions, 14 deletions
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 e14a04c4..78017bb5 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt @@ -72,7 +72,7 @@ private sealed class AbstractJsonTreeDecoder( override fun decodeNotNullMark(): Boolean = currentObject() !is JsonNull - protected open fun getValue(tag: String): JsonPrimitive { + protected fun getPrimitiveValue(tag: String): JsonPrimitive { val currentElement = currentElement(tag) return currentElement as? JsonPrimitive ?: throw JsonDecodingException( -1, @@ -83,16 +83,16 @@ private sealed class AbstractJsonTreeDecoder( protected abstract fun currentElement(tag: String): JsonElement override fun decodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor): Int = - enumDescriptor.getJsonNameIndexOrThrow(json, getValue(tag).content) + enumDescriptor.getJsonNameIndexOrThrow(json, getPrimitiveValue(tag).content) override fun decodeTaggedNull(tag: String): Nothing? = null override fun decodeTaggedNotNullMark(tag: String): Boolean = currentElement(tag) !== JsonNull override fun decodeTaggedBoolean(tag: String): Boolean { - val value = getValue(tag) + val value = getPrimitiveValue(tag) if (!json.configuration.isLenient) { - val literal = value as JsonLiteral + val literal = value.asLiteral("boolean") if (literal.isString) throw JsonDecodingException( -1, "Boolean literal for key '$tag' should be unquoted.\n$lenientHint", currentObject().toString() ) @@ -102,36 +102,36 @@ private sealed class AbstractJsonTreeDecoder( } } - override fun decodeTaggedByte(tag: String) = getValue(tag).primitive("byte") { + override fun decodeTaggedByte(tag: String) = getPrimitiveValue(tag).primitive("byte") { val result = int if (result in Byte.MIN_VALUE..Byte.MAX_VALUE) result.toByte() else null } - override fun decodeTaggedShort(tag: String) = getValue(tag).primitive("short") { + override fun decodeTaggedShort(tag: String) = getPrimitiveValue(tag).primitive("short") { val result = int if (result in Short.MIN_VALUE..Short.MAX_VALUE) result.toShort() else null } - override fun decodeTaggedInt(tag: String) = getValue(tag).primitive("int") { int } - override fun decodeTaggedLong(tag: String) = getValue(tag).primitive("long") { long } + override fun decodeTaggedInt(tag: String) = getPrimitiveValue(tag).primitive("int") { int } + override fun decodeTaggedLong(tag: String) = getPrimitiveValue(tag).primitive("long") { long } override fun decodeTaggedFloat(tag: String): Float { - val result = getValue(tag).primitive("float") { float } + val result = getPrimitiveValue(tag).primitive("float") { float } val specialFp = json.configuration.allowSpecialFloatingPointValues if (specialFp || result.isFinite()) return result throw InvalidFloatingPointDecoded(result, tag, currentObject().toString()) } override fun decodeTaggedDouble(tag: String): Double { - val result = getValue(tag).primitive("double") { double } + val result = getPrimitiveValue(tag).primitive("double") { double } val specialFp = json.configuration.allowSpecialFloatingPointValues if (specialFp || result.isFinite()) return result throw InvalidFloatingPointDecoded(result, tag, currentObject().toString()) } - override fun decodeTaggedChar(tag: String): Char = getValue(tag).primitive("char") { content.single() } + override fun decodeTaggedChar(tag: String): Char = getPrimitiveValue(tag).primitive("char") { content.single() } private inline fun <T: Any> JsonPrimitive.primitive(primitive: String, block: JsonPrimitive.() -> T?): T { try { @@ -146,9 +146,9 @@ private sealed class AbstractJsonTreeDecoder( } override fun decodeTaggedString(tag: String): String { - val value = getValue(tag) + val value = getPrimitiveValue(tag) if (!json.configuration.isLenient) { - val literal = value as JsonLiteral + val literal = value.asLiteral("string") if (!literal.isString) throw JsonDecodingException( -1, "String literal for key '$tag' should be quoted.\n$lenientHint", currentObject().toString() ) @@ -157,9 +157,13 @@ private sealed class AbstractJsonTreeDecoder( return value.content } + private fun JsonPrimitive.asLiteral(type: String): JsonLiteral { + return this as? JsonLiteral ?: throw JsonDecodingException(-1, "Unexpected 'null' when $type was expected") + } + @OptIn(ExperimentalUnsignedTypes::class) override fun decodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Decoder = - if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(JsonLexer(getValue(tag).content), json) + if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(JsonLexer(getPrimitiveValue(tag).content), json) else super.decodeTaggedInline(tag, inlineDescriptor) } diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt index e9ac4375..87b0f358 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt @@ -143,4 +143,19 @@ class JsonParserFailureModesTest : JsonTestBase() { assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":a}""", it) } assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":-a}""", it) } } + + + @Serializable + data class BooleanWrapper(val b: Boolean) + + @Serializable + data class StringWrapper(val s: String) + + @Test + fun testUnexpectedNull() = parametrizedTest { + assertFailsWith<JsonDecodingException> { default.decodeFromString<BooleanWrapper>("""{"b":{"b":"b"}}""", it) } + assertFailsWith<JsonDecodingException> { default.decodeFromString<BooleanWrapper>("""{"b":null}""", it) } + assertFailsWith<JsonDecodingException> { default.decodeFromString<StringWrapper>("""{"s":{"s":"s"}}""", it) } + assertFailsWith<JsonDecodingException> { default.decodeFromString<StringWrapper>("""{"s":null}""", it) } + } } |