From 4326f58cd2b9f28c9328230292739297dc8fc556 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 29 Nov 2021 16:57:22 +0300 Subject: [PATCH 1/2] Fix incorrect handling of object end when JsonTreeReader (JsonElement) is used with decodeToSequence Fixes #1779 --- .../json/internal/JsonTreeReader.kt | 8 +++--- .../features/JsonStreamFlowTest.kt | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt index b6d56b044..0d0fd87f0 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.json.internal @@ -34,8 +34,10 @@ internal class JsonTreeReader( result[key] = element // Verify the next token lastToken = lexer.consumeNextToken() - if (lastToken != TC_COMMA && lastToken != TC_END_OBJ) { - lexer.fail("Expected end of the object or comma") + when(lastToken) { + TC_COMMA -> Unit // no-op, can continue with `canConsumeValue` that verifies the token after comma + TC_END_OBJ -> break // `canConsumeValue` can return incorrect result, since it checks token _after_ TC_END_OBJ + else -> lexer.fail("Expected end of the object or comma") } } // Check for the correct ending diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt b/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt index 455a00381..b22bb72e4 100644 --- a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt +++ b/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt @@ -8,7 +8,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.* +import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.features.sealed.SealedChild +import kotlinx.serialization.features.sealed.SealedParent import kotlinx.serialization.json.* import kotlinx.serialization.json.internal.JsonDecodingException import kotlinx.serialization.test.assertFailsWithMessage @@ -108,6 +111,29 @@ class JsonStreamFlowTest { assertEquals(inputList, json.decodeToSequence(ins, StringData.serializer()).toList()) } + @Test + fun testJsonElement() { + val list = listOf( + buildJsonObject { put("foo", "bar") }, + buildJsonObject { put("foo", "baz") }, + JsonPrimitive(10), + JsonPrimitive("abacaba"), + buildJsonObject { put("foo", "qux") } + ) + val input = """${list[0]} ${list[1]} ${list[2]} ${list[3]} ${list[4]}""" + val decoded = json.decodeToSequence(input.asInputStream()).toList() + assertEquals(list, decoded) + } + + + @Test + fun testSealedClasses() { + val input = """{"type":"first child","i":1,"j":10} {"type":"first child","i":1,"j":11}""" + val iter = json.iterateOverStream(input.asInputStream(), SealedParent.serializer()) + iter.assertNext(SealedChild(10)) + iter.assertNext(SealedChild(11)) + } + @Test fun testMalformedArray() { val input1 = """[1, 2, 3""" From 3109bc6e482dcb969ed1d4869fe8e27e7d9ab5c9 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Tue, 30 Nov 2021 15:16:45 +0300 Subject: [PATCH 2/2] ~fix review comments --- .../serialization/json/internal/JsonTreeReader.kt | 4 ++-- .../serialization/features/JsonStreamFlowTest.kt | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt index 0d0fd87f0..7c01daa8f 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt @@ -4,7 +4,7 @@ package kotlinx.serialization.json.internal -import kotlinx.serialization.* +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.* @OptIn(ExperimentalSerializationApi::class) @@ -34,7 +34,7 @@ internal class JsonTreeReader( result[key] = element // Verify the next token lastToken = lexer.consumeNextToken() - when(lastToken) { + when (lastToken) { TC_COMMA -> Unit // no-op, can continue with `canConsumeValue` that verifies the token after comma TC_END_OBJ -> break // `canConsumeValue` can return incorrect result, since it checks token _after_ TC_END_OBJ else -> lexer.fail("Expected end of the object or comma") diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt b/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt index b22bb72e4..3de5a615f 100644 --- a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt +++ b/formats/json/jvmTest/src/kotlinx/serialization/features/JsonStreamFlowTest.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.* -import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.features.sealed.SealedChild import kotlinx.serialization.features.sealed.SealedParent @@ -120,9 +119,12 @@ class JsonStreamFlowTest { JsonPrimitive("abacaba"), buildJsonObject { put("foo", "qux") } ) - val input = """${list[0]} ${list[1]} ${list[2]} ${list[3]} ${list[4]}""" - val decoded = json.decodeToSequence(input.asInputStream()).toList() - assertEquals(list, decoded) + val inputWs = """${list[0]} ${list[1]} ${list[2]} ${list[3]} ${list[4]}""" + val decodedWs = json.decodeToSequence(inputWs.asInputStream()).toList() + assertEquals(list, decodedWs, "Failed whitespace-separated test") + val inputArray = """[${list[0]}, ${list[1]},${list[2]} , ${list[3]} ,${list[4]}]""" + val decodedArrayWrap = json.decodeToSequence(inputArray.asInputStream()).toList() + assertEquals(list, decodedArrayWrap, "Failed array-wrapped test") }