From c10d0af06dcaad61b5c1765e62c84946ed71b18f Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 12 Jun 2024 17:58:48 +0200 Subject: [PATCH] Prohibited use of elements other than JsonObject is When polymorphic serialization for JsonTransformingSerializer 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 --- .../JsonElementPolymorphicErrorTest.kt | 71 +++++++++++++++++++ .../json/internal/Polymorphic.kt | 4 ++ .../json/internal/StreamingJsonEncoder.kt | 3 + .../json/internal/TreeJsonEncoder.kt | 3 + .../json/internal/DynamicEncoders.kt | 3 + 5 files changed, 84 insertions(+) create mode 100644 formats/json-tests/commonTest/src/kotlinx/serialization/JsonElementPolymorphicErrorTest.kt 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 000000000..5e077d056 --- /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("Json element JsonLiteral cannot be serialized polymorphous, for serial name 'kotlinx.serialization.JsonElementPolymorphicErrorTest.IntChild'") { + format.encodeToString( + Holder.serializer(), + Holder(IntChild(42)), + mode + ) + } + + assertFailsWithMessage("Json element JsonArray cannot be serialized polymorphous, for serial name 'kotlinx.serialization.JsonElementPolymorphicErrorTest.CollectionChild'") { + 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 26cdbbcdd..a3130935b 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("Json element ${element::class.simpleName} cannot be serialized polymorphous, for serial name '$serialName'. Make sure that all JsonTransformingSerializer serializers return JsonObject") +} + 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 bf4538213..4eaf079d3 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 66b7f3c6e..74c95b1e0 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 cec687976..16da5a530 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) }