Skip to content

Commit c628e29

Browse files
Prohibited use of elements other than JsonObject in JsonTransformingSerializer with polymorphic serialization (#2715)
If JsonTransformingSerializer is used as a serializer for the descendant of a polymorphic class, then we do not know how to add a type discriminator to the returned result of a primitive or array type. Since there is no general solution to this problem on the library side, the user must take care of the correct processing of such types and, possibly, manually implement polymorphism. Resolves #2164 Co-authored-by: Leonid Startsev <[email protected]>
1 parent 3de98ff commit c628e29

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization
6+
7+
8+
import kotlinx.serialization.json.*
9+
import kotlinx.serialization.modules.SerializersModule
10+
import kotlinx.serialization.modules.polymorphic
11+
import kotlinx.serialization.test.*
12+
import kotlin.test.*
13+
14+
class JsonElementPolymorphicErrorTest : JsonTestBase() {
15+
16+
@Serializable
17+
abstract class Abstract
18+
19+
@Serializable
20+
data class IntChild(val value: Int) : Abstract()
21+
22+
@Serializable
23+
data class CollectionChild(val value: Int) : Abstract()
24+
25+
@Serializable
26+
data class Holder(val value: Abstract)
27+
28+
private val format = Json {
29+
prettyPrint = false
30+
serializersModule = SerializersModule {
31+
polymorphic(Abstract::class) {
32+
subclass(IntChild::class, IntChildSerializer)
33+
subclass(CollectionChild::class, CollectionChildSerializer)
34+
}
35+
}
36+
}
37+
38+
object IntChildSerializer : JsonTransformingSerializer<IntChild>(serializer()) {
39+
override fun transformSerialize(element: JsonElement): JsonElement {
40+
return element.jsonObject.getValue("value")
41+
}
42+
}
43+
44+
object CollectionChildSerializer : JsonTransformingSerializer<CollectionChild>(serializer()) {
45+
override fun transformSerialize(element: JsonElement): JsonElement {
46+
val value = element.jsonObject.getValue("value")
47+
return JsonArray(listOf(value))
48+
}
49+
}
50+
51+
@Test
52+
fun test() = parametrizedTest { mode ->
53+
assertFailsWithMessage<SerializationException>("Class with serial name kotlinx.serialization.JsonElementPolymorphicErrorTest.IntChild cannot be serialized polymorphically because it is represented as JsonLiteral. Make sure that its JsonTransformingSerializer returns JsonObject, so class discriminator can be added to it") {
54+
format.encodeToString(
55+
Holder.serializer(),
56+
Holder(IntChild(42)),
57+
mode
58+
)
59+
}
60+
61+
assertFailsWithMessage<SerializationException>("Class with serial name kotlinx.serialization.JsonElementPolymorphicErrorTest.CollectionChild cannot be serialized polymorphically because it is represented as JsonArray. Make sure that its JsonTransformingSerializer returns JsonObject, so class discriminator can be added to it") {
62+
format.encodeToString(
63+
Holder.serializer(),
64+
Holder(CollectionChild(42)),
65+
mode
66+
)
67+
}
68+
69+
}
70+
71+
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,7 @@ internal fun SerialDescriptor.classDiscriminator(json: Json): String {
100100
return json.configuration.classDiscriminator
101101
}
102102

103+
internal fun throwJsonElementPolymorphicException(serialName: String?, element: JsonElement): Nothing {
104+
throw JsonEncodingException("Class with serial name $serialName cannot be serialized polymorphically because it is represented as ${element::class.simpleName}. Make sure that its JsonTransformingSerializer returns JsonObject, so class discriminator can be added to it.")
105+
}
106+

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

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ internal class StreamingJsonEncoder(
5454
}
5555

5656
override fun encodeJsonElement(element: JsonElement) {
57+
if (polymorphicDiscriminator != null && element !is JsonObject) {
58+
throwJsonElementPolymorphicException(polymorphicSerialName, element)
59+
}
5760
encodeSerializableValue(JsonElementSerializer, element)
5861
}
5962

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

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ private sealed class AbstractJsonTreeEncoder(
4141
descriptor.getJsonElementName(json, index)
4242

4343
override fun encodeJsonElement(element: JsonElement) {
44+
if (polymorphicDiscriminator != null && element !is JsonObject) {
45+
throwJsonElementPolymorphicException(polymorphicSerialName, element)
46+
}
4447
encodeSerializableValue(JsonElementSerializer, element)
4548
}
4649

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

+3
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ private class DynamicObjectEncoder(
165165
}
166166

167167
override fun encodeJsonElement(element: JsonElement) {
168+
if (polymorphicDiscriminator != null && element !is JsonObject) {
169+
throwJsonElementPolymorphicException(polymorphicSerialName, element)
170+
}
168171
encodeSerializableValue(JsonElementSerializer, element)
169172
}
170173

0 commit comments

Comments
 (0)