Skip to content

Commit d4a2686

Browse files
Fixed type discriminator value for custom serializer that uses encodeJsonElement (#2628)
Fixes #2451 Co-authored-by: Leonid Startsev <[email protected]>
1 parent da020f9 commit d4a2686

File tree

5 files changed

+87
-9
lines changed

5 files changed

+87
-9
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization.features
6+
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.builtins.*
9+
import kotlinx.serialization.descriptors.*
10+
import kotlinx.serialization.encoding.*
11+
import kotlinx.serialization.json.*
12+
import kotlinx.serialization.modules.*
13+
import kotlin.test.*
14+
15+
class PolymorphismForCustomTest : JsonTestBase() {
16+
17+
private val customSerializer = object : KSerializer<VImpl> {
18+
override val descriptor: SerialDescriptor =
19+
buildClassSerialDescriptor("VImpl") {
20+
element("a", String.serializer().descriptor)
21+
}
22+
23+
override fun deserialize(decoder: Decoder): VImpl {
24+
decoder as JsonDecoder
25+
val jsonObject = decoder.decodeJsonElement() as JsonObject
26+
return VImpl(
27+
(jsonObject["a"] as JsonPrimitive).content
28+
)
29+
}
30+
31+
override fun serialize(encoder: Encoder, value: VImpl) {
32+
encoder as JsonEncoder
33+
encoder.encodeJsonElement(
34+
JsonObject(mapOf("a" to JsonPrimitive(value.a)))
35+
)
36+
}
37+
}
38+
39+
@Serializable
40+
data class ValueHolder<V : Any>(
41+
@Polymorphic val value: V,
42+
)
43+
44+
data class VImpl(val a: String)
45+
46+
val json = Json {
47+
serializersModule = SerializersModule {
48+
polymorphic(Any::class, VImpl::class, customSerializer)
49+
}
50+
}
51+
52+
@Test
53+
fun test() = parametrizedTest { mode ->
54+
val valueHolder = ValueHolder(VImpl("aaa"))
55+
val encoded = json.encodeToString(ValueHolder.serializer(customSerializer), valueHolder, mode)
56+
assertEquals("""{"value":{"type":"VImpl","a":"aaa"}}""", encoded)
57+
58+
val decoded = json.decodeFromString<ValueHolder<*>>(ValueHolder.serializer(customSerializer), encoded, mode)
59+
60+
assertEquals(valueHolder, decoded)
61+
}
62+
63+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import kotlin.jvm.*
1616
internal inline fun <T> JsonEncoder.encodePolymorphically(
1717
serializer: SerializationStrategy<T>,
1818
value: T,
19-
ifPolymorphic: (String) -> Unit
19+
ifPolymorphic: (discriminatorName: String, serialName: String) -> Unit
2020
) {
2121
if (json.configuration.useArrayPolymorphism) {
2222
serializer.serialize(this, value)
@@ -42,7 +42,7 @@ internal inline fun <T> JsonEncoder.encodePolymorphically(
4242
actual as SerializationStrategy<T>
4343
} else serializer
4444

45-
if (baseClassDiscriminator != null) ifPolymorphic(baseClassDiscriminator)
45+
if (baseClassDiscriminator != null) ifPolymorphic(baseClassDiscriminator, actualSerializer.descriptor.serialName)
4646
actualSerializer.serialize(this, value)
4747
}
4848

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ internal class StreamingJsonEncoder(
6262
}
6363

6464
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
65-
encodePolymorphically(serializer, value) {
66-
polymorphicDiscriminator = it
65+
encodePolymorphically(serializer, value) { discriminatorName, serialName ->
66+
polymorphicDiscriminator = discriminatorName
67+
polymorphicSerialName = serialName
6768
}
6869
}
6970

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ private sealed class AbstractJsonTreeEncoder(
7878
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
7979
// Writing non-structured data (i.e. primitives) on top-level (e.g. without any tag) requires special output
8080
if (currentTagOrNull != null || !serializer.descriptor.carrierDescriptor(serializersModule).requiresTopLevelTag) {
81-
encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
81+
encodePolymorphically(serializer, value) { discriminatorName, serialName ->
82+
polymorphicDiscriminator = discriminatorName
83+
polymorphicSerialName = serialName
84+
}
8285
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
8386
encodeSerializableValue(serializer, value)
8487
}
@@ -155,7 +158,14 @@ private sealed class AbstractJsonTreeEncoder(
155158

156159
val discriminator = polymorphicDiscriminator
157160
if (discriminator != null) {
158-
encoder.putElement(discriminator, JsonPrimitive(polymorphicSerialName ?: descriptor.serialName))
161+
if (encoder is JsonTreeMapEncoder) {
162+
// first parameter of `putElement` is ignored in JsonTreeMapEncoder
163+
encoder.putElement("key", JsonPrimitive(discriminator))
164+
encoder.putElement("value", JsonPrimitive(polymorphicSerialName ?: descriptor.serialName))
165+
166+
} else {
167+
encoder.putElement(discriminator, JsonPrimitive(polymorphicSerialName ?: descriptor.serialName))
168+
}
159169
polymorphicDiscriminator = null
160170
polymorphicSerialName = null
161171
}

formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ private class DynamicObjectEncoder(
6262
* Flag of usage polymorphism with discriminator attribute
6363
*/
6464
private var polymorphicDiscriminator: String? = null
65+
private var polymorphicSerialName: String? = null
66+
6567

6668
private object NoOutputMark
6769

@@ -183,8 +185,9 @@ private class DynamicObjectEncoder(
183185
private fun isNotStructured() = result === NoOutputMark
184186

185187
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
186-
encodePolymorphically(serializer, value) {
187-
polymorphicDiscriminator = it
188+
encodePolymorphically(serializer, value) { discriminatorName, serialName ->
189+
polymorphicDiscriminator = discriminatorName
190+
polymorphicSerialName = serialName
188191
}
189192
}
190193

@@ -209,8 +212,9 @@ private class DynamicObjectEncoder(
209212
}
210213

211214
if (polymorphicDiscriminator != null) {
212-
current.jsObject[polymorphicDiscriminator!!] = descriptor.serialName
215+
current.jsObject[polymorphicDiscriminator!!] = polymorphicSerialName ?: descriptor.serialName
213216
polymorphicDiscriminator = null
217+
polymorphicSerialName = null
214218
}
215219

216220
current.index = 0

0 commit comments

Comments
 (0)