diff --git a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt index 4cda7b1a61..53fd4c30be 100644 --- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt @@ -55,7 +55,9 @@ public class ContextualSerializer( public constructor(serializableClass: KClass) : this(serializableClass, null, EMPTY_SERIALIZER_ARRAY) public override val descriptor: SerialDescriptor = - buildSerialDescriptor("kotlinx.serialization.ContextualSerializer", SerialKind.CONTEXTUAL).withContext(serializableClass) + buildSerialDescriptor("kotlinx.serialization.ContextualSerializer", SerialKind.CONTEXTUAL) { + annotations = fallbackSerializer?.descriptor?.annotations.orEmpty() + }.withContext(serializableClass) public override fun serialize(encoder: Encoder, value: T) { encoder.encodeSerializableValue(serializer(encoder.serializersModule), value) diff --git a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt index 3304faa4ca..6c1dbad4ad 100644 --- a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt @@ -67,14 +67,28 @@ import kotlin.reflect.* */ @OptIn(ExperimentalSerializationApi::class) public class PolymorphicSerializer(override val baseClass: KClass) : AbstractPolymorphicSerializer() { - public override val descriptor: SerialDescriptor = + + @PublishedApi // should we allow user access to this constructor? + internal constructor( + baseClass: KClass, + classAnnotations: Array + ) : this(baseClass) { + _descriptor.annotations = classAnnotations.asList() + } + + private val _descriptor: SerialDescriptorImpl = buildSerialDescriptor("kotlinx.serialization.Polymorphic", PolymorphicKind.OPEN) { element("type", String.serializer().descriptor) element( "value", - buildSerialDescriptor("kotlinx.serialization.Polymorphic<${baseClass.simpleName}>", SerialKind.CONTEXTUAL) + buildSerialDescriptor( + "kotlinx.serialization.Polymorphic<${baseClass.simpleName}>", + SerialKind.CONTEXTUAL + ) ) - }.withContext(baseClass) + } as SerialDescriptorImpl + + public override val descriptor: SerialDescriptor = _descriptor.withContext(baseClass) override fun toString(): String { return "kotlinx.serialization.PolymorphicSerializer(baseClass: $baseClass)" diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt index 334c0af43e..1322729347 100644 --- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt @@ -77,6 +77,17 @@ public class SealedClassSerializer( subclassSerializers: Array> ) : AbstractPolymorphicSerializer() { + @PublishedApi + internal constructor( + serialName: String, + baseClass: KClass, + subclasses: Array>, + subclassSerializers: Array>, + classAnnotations: Array + ) : this(serialName, baseClass, subclasses, subclassSerializers) { + (this.descriptor as SerialDescriptorImpl).annotations = classAnnotations.asList() + } + override val descriptor: SerialDescriptor = buildSerialDescriptor(serialName, PolymorphicKind.SEALED) { element("type", String.serializer().descriptor) val elementDescriptor = @@ -87,7 +98,6 @@ public class SealedClassSerializer( } } element("value", elementDescriptor) - } private val class2Serializer: Map, KSerializer> diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt index f5f9d3a833..08d6625ac5 100644 --- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt +++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt @@ -272,7 +272,8 @@ internal class SerialDescriptorImpl( builder: ClassSerialDescriptorBuilder ) : SerialDescriptor, CachedNames { - override val annotations: List = builder.annotations + override var annotations: List = builder.annotations + internal set override val serialNames: Set = builder.elementNames.toHashSet() private val elementNames: Array = builder.elementNames.toTypedArray() diff --git a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt index c68a841229..9542fd81bf 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt @@ -17,6 +17,15 @@ import kotlinx.serialization.encoding.* @PublishedApi @OptIn(ExperimentalSerializationApi::class) internal class ObjectSerializer(serialName: String, private val objectInstance: T) : KSerializer { + @PublishedApi + internal constructor( + serialName: String, + objectInstance: T, + classAnnotations: Array + ) : this(serialName, objectInstance) { + (this.descriptor as SerialDescriptorImpl).annotations = classAnnotations.asList() + } + override val descriptor: SerialDescriptor = buildSerialDescriptor(serialName, StructureKind.OBJECT) override fun serialize(encoder: Encoder, value: T) { diff --git a/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt b/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt index 232f59aa3c..770ac50b0a 100644 --- a/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt +++ b/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt @@ -5,8 +5,7 @@ package kotlinx.serialization import kotlinx.serialization.descriptors.* -import kotlin.test.Test -import kotlin.test.assertEquals +import kotlin.test.* class SerialDescriptorAnnotationsTest { @@ -17,7 +16,33 @@ class SerialDescriptorAnnotationsTest { @Serializable @SerialName("MyClass") @CustomAnnotation("onClass") - data class WithNames(val a: Int, @CustomAnnotation("onProperty") val veryLongName: String) + data class WithNames( + val a: Int, + @CustomAnnotationWithDefault @CustomAnnotation("onProperty") val veryLongName: String + ) + + @SerialInfo + @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) + annotation class CustomAnnotationWithDefault(val value: String = "default_annotation_value") + + @SerialInfo + @Target(AnnotationTarget.PROPERTY) + public annotation class JShort(val order: SByteOrder = SByteOrder.BE, val mod: SByteMod = SByteMod.Add) + + public enum class SByteOrder { + BE, LE + } + + public enum class SByteMod { + None, Add + } + + @Serializable + public class Foo( + @JShort(SByteOrder.LE, SByteMod.None) public val bar: Short, + @JShort public val baz: Short + ) + @Test fun testSerialNameOnClass() { @@ -40,5 +65,56 @@ class SerialDescriptorAnnotationsTest { assertEquals("onClass", name) } + @Test + fun testCustomAnnotationWithDefaultValue() { + val value = + WithNames.serializer().descriptor + .getElementAnnotations(1).filterIsInstance().single() + assertEquals("default_annotation_value", value.value) + } + + @Test + fun testAnnotationWithMultipleArgs() { + fun SerialDescriptor.getValues(i: Int) = getElementAnnotations(i).filterIsInstance().single().run { order to mod } + assertEquals(SByteOrder.LE to SByteMod.None, Foo.serializer().descriptor.getValues(0)) + assertEquals(SByteOrder.BE to SByteMod.Add, Foo.serializer().descriptor.getValues(1)) + } + private fun List.getCustom() = filterIsInstance().single().value + + @Serializable + @CustomAnnotation("sealed") + sealed class Result { + @Serializable class OK(val s: String): Result() + } + + @Serializable + @CustomAnnotation("abstract") + abstract class AbstractResult { + var result: String = "" + } + + @Serializable + @CustomAnnotation("object") + object ObjectResult {} + + @Serializable + class Holder(val r: Result, val a: AbstractResult, val o: ObjectResult, @Contextual val names: WithNames) + + private fun doTest(position: Int, expected: String) { + val desc = Holder.serializer().descriptor.getElementDescriptor(position) + assertEquals(expected, desc.annotations.getCustom()) + } + + @Test + fun testCustomAnnotationOnSealedClass() = doTest(0, "sealed") + + @Test + fun testCustomAnnotationOnPolymorphicClass() = doTest(1, "abstract") + + @Test + fun testCustomAnnotationOnObject() = doTest(2, "object") + + @Test + fun testCustomAnnotationTransparentForContextual() = doTest(3, "onClass") } diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt new file mode 100644 index 0000000000..0173bda658 --- /dev/null +++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.json + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.internal.* +import kotlin.native.concurrent.* + +/** + * Indicates that the field can be represented in JSON + * with multiple possible alternative names. + * [Json] format recognizes this annotation and is able to decode + * the data using any of the alternative names. + * + * Unlike [SerialName] annotation, does not affect JSON encoding in any way. + * + * Example of usage: + * ``` + * @Serializable + * data class Project(@JsonNames("title") val name: String) + * + * val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") + * println(project) + * val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") + * println(oldProject) + * ``` + * + * This annotation has lesser priority than [SerialName]. + * + * @see JsonBuilder.useAlternativeNames + */ +@SerialInfo +@Target(AnnotationTarget.PROPERTY) +@ExperimentalSerializationApi +public annotation class JsonNames(vararg val names: String) + +/** + * Specifies key for class discriminator value used during polymorphic serialization in [Json]. + * Provided key is used only for an annotated class, to configure global class discriminator, use [JsonBuilder.classDiscriminator] + * property. + * + * It is possible to define different class discriminators for different parts of class hierarchy. + * Pay attention to the fact that class discriminator, same as polymorphic serializer's base class, is + * determined statically. + * + * Example: + * ``` + * @Serializable + * @JsonClassDiscriminator("class") + * abstract class Base + * + * @Serializable + * @JsonClassDiscriminator("error_class") + * abstract class ErrorClass: Base() + * + * @Serializable + * class Message(val message: Base, val error: ErrorClass?) + * + * val message = Json.decodeFromString("""{"message": {"class":"my.app.BaseMessage", "message": "not found"}, "error": {"error_class":"my.app.GenericError", "error_code": 404}}""") + * ``` + * + * @see JsonBuilder.classDiscriminator + */ +@SerialInfo +@Target(AnnotationTarget.CLASS) +@ExperimentalSerializationApi +public annotation class JsonClassDiscriminator(val discriminator: String) diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNames.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNames.kt deleted file mode 100644 index fcb1227806..0000000000 --- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNames.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.serialization.json - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.internal.* -import kotlin.native.concurrent.* - -/** - * An annotation that indicates the field can be represented in JSON - * with multiple possible alternative names. - * [Json] format recognizes this annotation and is able to decode - * the data using any of the alternative names. - * - * Unlike [SerialName] annotation, does not affect JSON encoding in any way. - * - * Example of usage: - * ``` - * @Serializable - * data class Project(@JsonNames("title") val name: String) - * - * val project = Json.decodeFromString("""{"name":"kotlinx.serialization"}""") - * println(project) - * val oldProject = Json.decodeFromString("""{"title":"kotlinx.coroutines"}""") - * println(oldProject) - * ``` - * - * This annotation has lesser priority than [SerialName]. - * - * @see JsonBuilder.useAlternativeNames - */ -@SerialInfo -@Target(AnnotationTarget.PROPERTY) -@ExperimentalSerializationApi -public annotation class JsonNames(vararg val names: String) 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 73f00c4764..459f4af475 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt @@ -11,26 +11,22 @@ import kotlinx.serialization.internal.* import kotlinx.serialization.json.* @Suppress("UNCHECKED_CAST") -internal inline fun JsonEncoder.encodePolymorphically(serializer: SerializationStrategy, value: T, ifPolymorphic: () -> Unit) { +internal inline fun JsonEncoder.encodePolymorphically( + serializer: SerializationStrategy, + value: T, + ifPolymorphic: (String) -> Unit +) { if (serializer !is AbstractPolymorphicSerializer<*> || json.configuration.useArrayPolymorphism) { serializer.serialize(this, value) return } - val actualSerializer = findActualSerializer(serializer as SerializationStrategy, value as Any) - ifPolymorphic() - actualSerializer.serialize(this, value) -} - -private fun JsonEncoder.findActualSerializer( - serializer: SerializationStrategy, - value: Any -): SerializationStrategy { val casted = serializer as AbstractPolymorphicSerializer - val actualSerializer = casted.findPolymorphicSerializer(this, value) - validateIfSealed(casted, actualSerializer, json.configuration.classDiscriminator) - val kind = actualSerializer.descriptor.kind - checkKind(kind) - return actualSerializer + val baseClassDiscriminator = serializer.descriptor.classDiscriminator(json) + val actualSerializer = casted.findPolymorphicSerializer(this, value as Any) + validateIfSealed(casted, actualSerializer, baseClassDiscriminator) + checkKind(actualSerializer.descriptor.kind) + ifPolymorphic(baseClassDiscriminator) + actualSerializer.serialize(this, value) } private fun validateIfSealed( @@ -64,7 +60,7 @@ internal fun JsonDecoder.decodeSerializableValuePolymorphic(deserializer: De } val jsonTree = cast(decodeJsonElement(), deserializer.descriptor) - val discriminator = json.configuration.classDiscriminator + val discriminator = deserializer.descriptor.classDiscriminator(json) val type = jsonTree[discriminator]?.jsonPrimitive?.content val actualSerializer = deserializer.findPolymorphicSerializerOrNull(this, type) ?: throwSerializerNotFound(type, jsonTree) @@ -79,3 +75,8 @@ private fun throwSerializerNotFound(type: String?, jsonTree: JsonObject): Nothin else "class discriminator '$type'" throw JsonDecodingException(-1, "Polymorphic serializer was not found for $suffix", jsonTree.toString()) } + +internal fun SerialDescriptor.classDiscriminator(json: Json): String = + annotations.filterIsInstance().singleOrNull()?.discriminator + ?: json.configuration.classDiscriminator + 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 5b68278c20..0a66e2a276 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt @@ -44,7 +44,7 @@ internal class StreamingJsonEncoder( // Forces serializer to wrap all values into quotes private var forceQuoting: Boolean = false - private var writePolymorphic = false + private var polymorphicDiscriminator: String? = null init { val i = mode.ordinal @@ -64,13 +64,13 @@ internal class StreamingJsonEncoder( override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { encodePolymorphically(serializer, value) { - writePolymorphic = true + polymorphicDiscriminator = it } } private fun encodeTypeInfo(descriptor: SerialDescriptor) { composer.nextItem() - encodeString(configuration.classDiscriminator) + encodeString(polymorphicDiscriminator!!) composer.print(COLON) composer.space() encodeString(descriptor.serialName) @@ -83,9 +83,9 @@ internal class StreamingJsonEncoder( composer.indent() } - if (writePolymorphic) { - writePolymorphic = false + if (polymorphicDiscriminator != null) { encodeTypeInfo(descriptor) + polymorphicDiscriminator = null } if (mode == newMode) { 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 53d7e0174c..362538377d 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt @@ -33,7 +33,7 @@ private sealed class AbstractJsonTreeEncoder( @JvmField protected val configuration = json.configuration - private var writePolymorphic = false + private var polymorphicDiscriminator: String? = null override fun encodeJsonElement(element: JsonElement) { encodeSerializableValue(JsonElementSerializer, element) @@ -70,7 +70,7 @@ private sealed class AbstractJsonTreeEncoder( override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { // Writing non-structured data (i.e. primitives) on top-level (e.g. without any tag) requires special output if (currentTagOrNull != null || serializer.descriptor.kind !is PrimitiveKind && serializer.descriptor.kind !== SerialKind.ENUM) { - encodePolymorphically(serializer, value) { writePolymorphic = true } + encodePolymorphically(serializer, value) { polymorphicDiscriminator = it } } else JsonPrimitiveEncoder(json, nodeConsumer).apply { encodeSerializableValue(serializer, value) endEncode(serializer.descriptor) @@ -126,9 +126,9 @@ private sealed class AbstractJsonTreeEncoder( else -> JsonTreeEncoder(json, consumer) } - if (writePolymorphic) { - writePolymorphic = false - encoder.putElement(configuration.classDiscriminator, JsonPrimitive(descriptor.serialName)) + if (polymorphicDiscriminator != null) { + encoder.putElement(polymorphicDiscriminator!!, JsonPrimitive(descriptor.serialName)) + polymorphicDiscriminator = null } return encoder diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt b/formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt new file mode 100644 index 0000000000..99cba9fed3 --- /dev/null +++ b/formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.serialization.features + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import kotlin.test.* + +class JsonClassDiscriminatorTest : JsonTestBase() { + @Serializable + @JsonClassDiscriminator("sealedType") + sealed class SealedMessage { + @Serializable + @SerialName("SealedMessage.StringMessage") + data class StringMessage(val description: String, val message: String) : SealedMessage() + + @SerialName("EOF") + @Serializable + object EOF : SealedMessage() + } + + @Serializable + @JsonClassDiscriminator("abstractType") + abstract class AbstractMessage { + @Serializable + @SerialName("Message.StringMessage") + data class StringMessage(val description: String, val message: String) : AbstractMessage() + + @Serializable + @SerialName("Message.IntMessage") + data class IntMessage(val description: String, val message: Int) : AbstractMessage() + } + + + @Test + fun testSealedClassesHaveCustomDiscriminator() { + val messages = listOf( + SealedMessage.StringMessage("string message", "foo"), + SealedMessage.EOF + ) + val expected = + """[{"sealedType":"SealedMessage.StringMessage","description":"string message","message":"foo"},{"sealedType":"EOF"}]""" + assertJsonFormAndRestored( + ListSerializer(SealedMessage.serializer()), + messages, + expected, + ) + } + + @Test + fun testAbstractClassesHaveCustomDiscriminator() { + val messages = listOf( + AbstractMessage.StringMessage("string message", "foo"), + AbstractMessage.IntMessage("int message", 42), + ) + val module = SerializersModule { + polymorphic(AbstractMessage::class) { + subclass(AbstractMessage.StringMessage.serializer()) + subclass(AbstractMessage.IntMessage.serializer()) + } + } + val json = Json { serializersModule = module } + val expected = + """[{"abstractType":"Message.StringMessage","description":"string message","message":"foo"},{"abstractType":"Message.IntMessage","description":"int message","message":42}]""" + assertJsonFormAndRestored(ListSerializer(AbstractMessage.serializer()), messages, expected, json) + } + + @Serializable + @JsonClassDiscriminator("class") + abstract class Base + + @Serializable + @JsonClassDiscriminator("error_class") + abstract class ErrorClass : Base() + + @Serializable + data class Message(val message: Base, val error: ErrorClass?) + + @Serializable + @SerialName("my.app.BaseMessage") + data class BaseMessage(val message: String) : Base() + + @Serializable + @SerialName("my.app.GenericError") + data class GenericError(@SerialName("error_code") val errorCode: Int) : ErrorClass() + + + @Test + fun testDocumentationSample() { + val module = SerializersModule { + polymorphic(Base::class) { + subclass(BaseMessage.serializer()) + } + polymorphic(ErrorClass::class) { + subclass(GenericError.serializer()) + } + } + val json = Json { serializersModule = module } + assertJsonFormAndRestored( + Message.serializer(), + Message(BaseMessage("not found"), GenericError(404)), + """{"message":{"class":"my.app.BaseMessage","message":"not found"},"error":{"error_class":"my.app.GenericError","error_code":404}}""", + json + ) + } +} 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 633ab35fcb..d32cca0cd5 100644 --- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt +++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt @@ -61,7 +61,7 @@ private class DynamicObjectEncoder( /** * Flag of usage polymorphism with discriminator attribute */ - private var writePolymorphic = false + private var polymorphicDiscriminator: String? = null private object NoOutputMark @@ -173,7 +173,7 @@ private class DynamicObjectEncoder( override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { encodePolymorphically(serializer, value) { - writePolymorphic = true + polymorphicDiscriminator = it } } @@ -197,9 +197,9 @@ private class DynamicObjectEncoder( enterNode(child, newMode) } - if (writePolymorphic) { - writePolymorphic = false - current.jsObject[json.configuration.classDiscriminator] = descriptor.serialName + if (polymorphicDiscriminator != null) { + current.jsObject[polymorphicDiscriminator!!] = descriptor.serialName + polymorphicDiscriminator = null } current.index = 0 diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt b/formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt index c95799a9b7..3ff05ba05b 100644 --- a/formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt +++ b/formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt @@ -39,6 +39,14 @@ class DynamicPolymorphismTest { data class DefaultChild(val default: String? = "default"): Sealed(5) } + @Serializable + @JsonClassDiscriminator("sealed_custom") + sealed class SealedCustom { + @Serializable + @SerialName("data_class") + data class DataClassChild(val name: String) : SealedCustom() + } + @Serializable data class CompositeClass(val mark: String, val nested: Sealed) @@ -75,6 +83,16 @@ class DynamicPolymorphismTest { } } + @Test + fun testCustomClassDiscriminator() { + val value = SealedCustom.DataClassChild("custom-discriminator-test") + encodeAndDecode(SealedCustom.serializer(), value, objectJson) { + assertEquals("data_class", this["sealed_custom"]) + assertEquals(undefined, this.type) + assertEquals(2, fieldsCount(this)) + } + } + @Test fun testComposite() { val nestedValue = Sealed.DataClassChild("child")