Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2017-2026 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.features.sealed

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonTestBase
import kotlin.jvm.JvmInline
import kotlin.test.Test

class SealedInterfacesInlineMapChildTest : JsonTestBase() {

@Serializable
sealed interface Parent {

@JvmInline
@Serializable
@SerialName("child")
value class Child(val value: Map<Int, String>) : Parent
}

@Test
fun encodesDecodesInlineMapChildWithClassDiscriminator() {
val value = Parent.Child(mapOf(1 to "one", 2 to "two"))
assertJsonFormAndRestored(
Parent.serializer(),
value,
"""{"type":"child","1":"one","2":"two"}"""
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ internal open class StreamingJsonDecoder(
lexer.path.pushDescriptor(descriptor)
lexer.consumeNextToken(newMode.begin)
checkLeadingComma()
if (newMode == WriteMode.MAP) {
skipMapDiscriminator()
}
return when (newMode) {
// In fact resets current index that these modes rely on
WriteMode.LIST, WriteMode.MAP, WriteMode.POLY_OBJ -> StreamingJsonDecoder(
Expand Down Expand Up @@ -153,6 +156,18 @@ internal open class StreamingJsonDecoder(
}
}

// Skip the discriminator when entering MAP mode. decodeMapIndex can't skip
// it like decodeObjectIndex because we delegate the actual key decoding to
// serializers, and decodeObjectIndex only needs to care about string keys.
private fun skipMapDiscriminator() {
if (discriminatorHolder?.discriminatorToSkip == null) return
if (discriminatorHolder.trySkip(decodeStringKey())) {
lexer.consumeNextToken(COLON)
lexer.skipElement(configuration.isLenient)
val _ = lexer.tryConsumeComma()
}
}

override fun <T> decodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private sealed class AbstractJsonTreeDecoder(
StructureKind.LIST, is PolymorphicKind -> JsonTreeListDecoder(json, cast(currentObject, descriptor))
StructureKind.MAP -> json.selectMapMode(
descriptor,
{ JsonTreeMapDecoder(json, cast(currentObject, descriptor)) },
{ JsonTreeMapDecoder(json, cast(currentObject, descriptor), polymorphicDiscriminator) },
{ JsonTreeListDecoder(json, cast(currentObject, descriptor)) }
)
else -> JsonTreeDecoder(json, cast(currentObject, descriptor), polymorphicDiscriminator)
Expand Down Expand Up @@ -303,8 +303,16 @@ private open class JsonTreeDecoder(
}
}

private class JsonTreeMapDecoder(json: Json, override val value: JsonObject) : JsonTreeDecoder(json, value) {
private val keys = value.keys.toList()
private class JsonTreeMapDecoder(
json: Json,
override val value: JsonObject,
polymorphicDiscriminator: String? = null
) : JsonTreeDecoder(json, value, polymorphicDiscriminator) {
private val keys = if (polymorphicDiscriminator != null) {
value.keys.filter { it != polymorphicDiscriminator }
} else {
value.keys.toList()
}
private val size: Int = keys.size * 2
private var position = -1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ private sealed class AbstractJsonTreeEncoder(
return if (currentTagOrNull != null) {
if (polymorphicDiscriminator != null) polymorphicSerialName = descriptor.serialName
super.encodeInline(descriptor)
} else if (polymorphicDiscriminator != null) {
polymorphicSerialName = descriptor.serialName
this
} else {
JsonPrimitiveEncoder(json, nodeConsumer).encodeInline(descriptor)
}
Expand Down