Skip to content

Commit 5bba108

Browse files
authored
Fix beginStructure in JsonTreeDecoder when inner structure descriptor is same as outer (#2346)
Instead of returning the same instance of decoder (with invalid position) a new instance is created, and polyDiscriminator and polyDescriptor are passed to the new instance. This way check for unknown keys in endStructure can properly filter out polymorphic discriminator (by default "type) from potential unknown keys. Fixes #2343
1 parent fd75d35 commit 5bba108

File tree

2 files changed

+52
-6
lines changed

2 files changed

+52
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.json.polymorphic
6+
7+
import kotlinx.serialization.Serializable
8+
import kotlinx.serialization.json.*
9+
import kotlin.test.*
10+
11+
class JsonTreeDecoderPolymorphicTest : JsonTestBase() {
12+
13+
@Serializable
14+
sealed class Sealed
15+
16+
@Serializable
17+
data class ClassContainingItself(
18+
val a: String,
19+
val b: String,
20+
val c: ClassContainingItself? = null,
21+
val d: String?
22+
) : Sealed()
23+
24+
val inner = ClassContainingItself(
25+
"InnerA",
26+
"InnerB",
27+
null,
28+
"InnerC"
29+
)
30+
val outer = ClassContainingItself(
31+
"OuterA",
32+
"OuterB",
33+
inner,
34+
"OuterC"
35+
)
36+
37+
@Test
38+
fun testDecodingWhenClassContainsItself() = parametrizedTest { jsonTestingMode ->
39+
val encoded = default.encodeToString(outer as Sealed, jsonTestingMode)
40+
val decoded: Sealed = Json.decodeFromString(encoded, jsonTestingMode)
41+
assertEquals(outer, decoded)
42+
}
43+
}

formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ private sealed class AbstractJsonTreeDecoder(
4444
@JvmField
4545
protected val configuration = json.configuration
4646

47-
private fun currentObject() = currentTagOrNull?.let { currentElement(it) } ?: value
47+
protected fun currentObject() = currentTagOrNull?.let { currentElement(it) } ?: value
4848

4949
override fun decodeJsonElement(): JsonElement = currentObject()
5050

@@ -256,11 +256,14 @@ private open class JsonTreeDecoder(
256256
override fun currentElement(tag: String): JsonElement = value.getValue(tag)
257257

258258
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
259-
/*
260-
* For polymorphic serialization we'd like to avoid excessive decoder creating in
261-
* beginStructure to properly preserve 'polyDiscriminator' field and filter it out.
262-
*/
263-
if (descriptor === polyDescriptor) return this
259+
// polyDiscriminator needs to be preserved so the check for unknown keys
260+
// in endStructure can filter polyDiscriminator out.
261+
if (descriptor === polyDescriptor) {
262+
return JsonTreeDecoder(
263+
json, cast(currentObject(), polyDescriptor), polyDiscriminator, polyDescriptor
264+
)
265+
}
266+
264267
return super.beginStructure(descriptor)
265268
}
266269

0 commit comments

Comments
 (0)