diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonLexer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonLexer.kt index 247e9c6f8..2a05bb0c9 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonLexer.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonLexer.kt @@ -368,13 +368,21 @@ internal class JsonLexer(private val source: String) { fun consumeStringLenientNotNull(): String { val result = consumeStringLenient() - if (result == NULL) { // Check if lenient value is 'null' and fail for non-nullable read if so + /* + * Check if lenient value is 'null' _without_ quotation marks and fail for non-nullable read if so. + */ + if (result == NULL && wasUnquotedString()) { fail("Unexpected 'null' value instead of string literal") } return result } - // Allows to consume unquoted string + private fun wasUnquotedString(): Boolean { + // Is invoked _only_ when the 'null' string was read, thus 'cP - 1' is always within bounds + return source[currentPosition - 1] != STRING + } + + // Allows consuming unquoted string fun consumeStringLenient(): String { if (peekedString != null) { return takePeeked() diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt index 74fed528c..b89e853f6 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt @@ -4,12 +4,10 @@ package kotlinx.serialization.json -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.internal.JsonDecodingException -import kotlinx.serialization.json.internal.JsonException -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.internal.* +import kotlin.test.* class LenientTest : JsonTestBase() { @@ -81,5 +79,17 @@ class LenientTest : JsonTestBase() { assertEquals(NullableString("nul"), lenient.decodeFromString("""{"s":nul}""", it)) assertEquals(NullableString("null1"), lenient.decodeFromString("""{"s":null1}""", it)) assertEquals(NullableString(null), lenient.decodeFromString("""{"s":null}""", it)) + assertEquals(NullableString("null"), lenient.decodeFromString("""{"s":"null"}""", it)) + assertEquals(NullableString("null"), lenient.decodeFromString("""{"s":"null" }""", it)) + assertEquals(NullableString("null "), lenient.decodeFromString("""{"s":"null " }""", it)) + } + + @Test + fun testTopLevelNulls() = parametrizedTest { + assertEquals("nul", lenient.decodeFromString("""nul""", it)) + assertEquals("null1", lenient.decodeFromString("""null1""", it)) + assertEquals(null, lenient.decodeFromString(String.serializer().nullable, """null""", it)) + assertEquals("null", lenient.decodeFromString(""""null"""", it)) + assertEquals("null ", lenient.decodeFromString(""""null """", it)) } }