diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/JsonElementPolymorphicErrorTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/JsonElementPolymorphicErrorTest.kt new file mode 100644 index 0000000000..ee290490cb --- /dev/null +++ b/formats/json-tests/commonTest/src/kotlinx/serialization/JsonElementPolymorphicErrorTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization + + +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.test.* +import kotlin.test.* + +class JsonElementPolymorphicErrorTest : JsonTestBase() { + + @Serializable + abstract class Abstract + + @Serializable + data class IntChild(val value: Int) : Abstract() + + @Serializable + data class CollectionChild(val value: Int) : Abstract() + + @Serializable + data class Holder(val value: Abstract) + + private val format = Json { + prettyPrint = false + serializersModule = SerializersModule { + polymorphic(Abstract::class) { + subclass(IntChild::class, IntChildSerializer) + subclass(CollectionChild::class, CollectionChildSerializer) + } + } + } + + object IntChildSerializer : JsonTransformingSerializer(serializer()) { + override fun transformSerialize(element: JsonElement): JsonElement { + return element.jsonObject.getValue("value") + } + } + + object CollectionChildSerializer : JsonTransformingSerializer(serializer()) { + override fun transformSerialize(element: JsonElement): JsonElement { + val value = element.jsonObject.getValue("value") + return JsonArray(listOf(value)) + } + } + + @Test + fun test() = parametrizedTest { mode -> + assertFailsWithMessage("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") { + format.encodeToString( + Holder.serializer(), + Holder(IntChild(42)), + mode + ) + } + + assertFailsWithMessage("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") { + format.encodeToString( + Holder.serializer(), + Holder(CollectionChild(42)), + mode + ) + } + + } + +} diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt index 26cdbbcdd0..acc0bf4737 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt @@ -100,3 +100,7 @@ internal fun SerialDescriptor.classDiscriminator(json: Json): String { return json.configuration.classDiscriminator } +internal fun throwJsonElementPolymorphicException(serialName: String?, element: JsonElement): Nothing { + 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.") +} + diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt index bf45382135..4eaf079d30 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt @@ -54,6 +54,9 @@ internal class StreamingJsonEncoder( } override fun encodeJsonElement(element: JsonElement) { + if (polymorphicDiscriminator != null && element !is JsonObject) { + throwJsonElementPolymorphicException(polymorphicSerialName, element) + } encodeSerializableValue(JsonElementSerializer, element) } diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt index 66b7f3c6e8..74c95b1e0a 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt @@ -41,6 +41,9 @@ private sealed class AbstractJsonTreeEncoder( descriptor.getJsonElementName(json, index) override fun encodeJsonElement(element: JsonElement) { + if (polymorphicDiscriminator != null && element !is JsonObject) { + throwJsonElementPolymorphicException(polymorphicSerialName, element) + } encodeSerializableValue(JsonElementSerializer, element) } diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt index cec6879764..16da5a5307 100644 --- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt +++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt @@ -165,6 +165,9 @@ private class DynamicObjectEncoder( } override fun encodeJsonElement(element: JsonElement) { + if (polymorphicDiscriminator != null && element !is JsonObject) { + throwJsonElementPolymorphicException(polymorphicSerialName, element) + } encodeSerializableValue(JsonElementSerializer, element) }