From 4ed38d7d1b5845bdbc7a6114ca5e31e9172c01b6 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sat, 17 Feb 2024 20:26:46 +0800
Subject: [PATCH 01/12] Initially add the annotations and refactor the
serializers, descriptors, and serializers modules for serializing type
numbers in polymorphic serialization without any support from the
serialization plugin yet
---
.../src/kotlinx/serialization/Annotations.kt | 21 ++++++
.../serialization/PolymorphicSerializer.kt | 7 ++
.../kotlinx/serialization/SealedSerializer.kt | 26 +++++++
.../descriptors/SerialDescriptor.kt | 13 ++++
.../descriptors/SerialDescriptors.kt | 18 ++++-
.../internal/AbstractPolymorphicSerializer.kt | 55 +++++++++++++--
.../modules/PolymorphicModuleBuilder.kt | 34 +++++++++-
.../modules/SerializersModule.kt | 63 ++++++++++++++++-
.../modules/SerializersModuleBuilders.kt | 67 ++++++++++++++++++-
.../modules/SerializersModuleCollector.kt | 21 +++++-
.../json/internal/PolymorphismValidator.kt | 7 ++
11 files changed, 322 insertions(+), 10 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index 67104dc3c6..20f7b962e3 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -152,6 +152,27 @@ public annotation class Serializer(
// @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082
public annotation class SerialName(val value: String)
+/**
+ * Requires all subclasses to use [SerialPolymorphicNumber].
+ */
+@MustBeDocumented
+@Target(AnnotationTarget.CLASS)
+@Repeatable
+public annotation class UseSerialPolymorphicNumbers
+
+/**
+ * When its parent class is annotated with [UseSerialPolymorphicNumbers],
+ * overrides its [String]-typed serial name when serialized as a subclass of the parent class in [baseClass]
+ * (including the value overridden by [SerialName] if set)
+ * with a [Int]-typed number in [value].
+ *
+ * Using a number instead of a string shortens the size of the serialized message, especially in a binary format.
+ */
+@MustBeDocumented
+@Target(AnnotationTarget.CLASS)
+@Repeatable
+public annotation class SerialPolymorphicNumber(val baseClass: KClass<*>, val value: Int)
+
/**
* Indicates that property must be present during deserialization process, despite having a default value.
*/
diff --git a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
index 6ee7071735..95fd2c98f9 100644
--- a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
@@ -101,6 +101,13 @@ public fun AbstractPolymorphicSerializer.findPolymorphicSerializer(
): DeserializationStrategy =
findPolymorphicSerializerOrNull(decoder, klassName) ?: throwSubtypeNotRegistered(klassName, baseClass)
+@InternalSerializationApi
+public fun AbstractPolymorphicSerializer.findPolymorphicSerializerWithNumber(
+ decoder: CompositeDecoder,
+ serialPolymorphicNumber: Int?
+): DeserializationStrategy =
+ findPolymorphicSerializerWithNumberOrNull(decoder, serialPolymorphicNumber) ?: throwSubtypeNotRegistered(serialPolymorphicNumber, baseClass)
+
@InternalSerializationApi
public fun AbstractPolymorphicSerializer.findPolymorphicSerializer(
encoder: Encoder,
diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
index 52b7c0544d..eb6f29cf54 100644
--- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
@@ -117,6 +117,7 @@ public class SealedClassSerializer(
private val class2Serializer: Map, KSerializer>
private val serialName2Serializer: Map>
+ private val serialPolymorphicNumber2Serializer : Map>?
init {
if (subclasses.size != subclassSerializers.size) {
@@ -127,6 +128,7 @@ public class SealedClassSerializer(
// Plugin should produce identical serializers, although they are not always strictly equal (e.g. new ObjectSerializer
// may be created every time)
class2Serializer = subclasses.zip(subclassSerializers).toMap()
+
serialName2Serializer = class2Serializer.entries.groupingBy { it.value.descriptor.serialName }
.aggregate, KSerializer>, String, Map.Entry, KSerializer>>
{ key, accumulator, element, _ ->
@@ -138,6 +140,23 @@ public class SealedClassSerializer(
}
element
}.mapValues { it.value.value }
+
+ serialPolymorphicNumber2Serializer = if (descriptor.useSerialPolymorphicNumbers)
+ class2Serializer.entries.groupingBy {
+ it.value.descriptor.serialPolymorphicNumberByBaseClass.getValue(baseClass)
+ }
+ .aggregate, KSerializer>, Int, Map.Entry, KSerializer>>
+ { key, accumulator, element, _ ->
+ if (accumulator != null) {
+ error(
+ "Multiple sealed subclasses of '$baseClass' have the same serial polymorphic number '$key':" +
+ " '${accumulator.key}', '${element.key}'"
+ )
+ }
+ element
+ }.mapValues { it.value.value }
+ else
+ null
}
override fun findPolymorphicSerializerOrNull(
@@ -147,6 +166,13 @@ public class SealedClassSerializer(
return serialName2Serializer[klassName] ?: super.findPolymorphicSerializerOrNull(decoder, klassName)
}
+ @InternalSerializationApi
+ override fun findPolymorphicSerializerWithNumberOrNull(
+ decoder: CompositeDecoder, serialPolymorphicNumber: Int?
+ ): DeserializationStrategy? =
+ serialPolymorphicNumber2Serializer!![serialPolymorphicNumber]
+ ?: super.findPolymorphicSerializerWithNumberOrNull(decoder, serialPolymorphicNumber)
+
override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: T): SerializationStrategy? {
return (class2Serializer[value::class] ?: super.findPolymorphicSerializerOrNull(encoder, value))?.cast()
}
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
index 17fdbfe0f7..94edbce928 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
@@ -7,6 +7,7 @@ package kotlinx.serialization.descriptors
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.encoding.*
+import kotlin.reflect.*
/**
* Serial descriptor is an inherent property of [KSerializer] that describes the structure of the serializable type.
@@ -195,6 +196,18 @@ public interface SerialDescriptor {
@ExperimentalSerializationApi
public val elementsCount: Int
+ /**
+ * TODO
+ */
+ @ExperimentalSerializationApi
+ public val useSerialPolymorphicNumbers: Boolean get() = false
+
+ /**
+ * TODO
+ */
+ @ExperimentalSerializationApi
+ public val serialPolymorphicNumberByBaseClass: Map, Int> get() = emptyMap()
+
/**
* Returns serial annotations of the associated class.
* Serial annotations can be used to specify an additional metadata that may be used during serialization.
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
index cb380aafc0..df5eb3c6c8 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
@@ -54,6 +54,8 @@ import kotlin.reflect.*
public fun buildClassSerialDescriptor(
serialName: String,
vararg typeParameters: SerialDescriptor,
+ useSerialPolymorphicNumbers: Boolean = false,
+ serialPolymorphicNumbers: Map, Int> = emptyMap(),
builderAction: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
@@ -64,6 +66,8 @@ public fun buildClassSerialDescriptor(
StructureKind.CLASS,
sdBuilder.elementNames.size,
typeParameters.toList(),
+ useSerialPolymorphicNumbers,
+ serialPolymorphicNumbers,
sdBuilder
)
}
@@ -140,13 +144,23 @@ public fun buildSerialDescriptor(
serialName: String,
kind: SerialKind,
vararg typeParameters: SerialDescriptor,
+ useSerialPolymorphicNumbers: Boolean = false,
+ serialPolymorphicNumbers: Map, Int> = emptyMap(),
builder: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
require(kind != StructureKind.CLASS) { "For StructureKind.CLASS please use 'buildClassSerialDescriptor' instead" }
val sdBuilder = ClassSerialDescriptorBuilder(serialName)
sdBuilder.builder()
- return SerialDescriptorImpl(serialName, kind, sdBuilder.elementNames.size, typeParameters.toList(), sdBuilder)
+ return SerialDescriptorImpl(
+ serialName,
+ kind,
+ sdBuilder.elementNames.size,
+ typeParameters.toList(),
+ useSerialPolymorphicNumbers,
+ serialPolymorphicNumbers,
+ sdBuilder
+ )
}
@@ -309,6 +323,8 @@ internal class SerialDescriptorImpl(
override val kind: SerialKind,
override val elementsCount: Int,
typeParameters: List,
+ override val useSerialPolymorphicNumbers : Boolean,
+ override val serialPolymorphicNumberByBaseClass : Map, Int>,
builder: ClassSerialDescriptorBuilder
) : SerialDescriptor, CachedNames {
diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
index 26d3b5e27f..7b0c3bfe1a 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -7,6 +7,7 @@ package kotlinx.serialization.internal
import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlin.jvm.*
+import kotlin.properties.*
import kotlin.reflect.*
/**
@@ -31,13 +32,22 @@ public abstract class AbstractPolymorphicSerializer internal constructo
public final override fun serialize(encoder: Encoder, value: T) {
val actualSerializer = findPolymorphicSerializer(encoder, value)
encoder.encodeStructure(descriptor) {
- encodeStringElement(descriptor, 0, actualSerializer.descriptor.serialName)
+ if (descriptor.useSerialPolymorphicNumbers)
+ encodeIntElement(
+ descriptor,
+ 0,
+ // it seems not possible to cache this with the current implementation that serializers are completely separated from serializers modules
+ actualSerializer.descriptor.serialPolymorphicNumberByBaseClass.getValue(baseClass)
+ )
+ else
+ encodeStringElement(descriptor, 0, actualSerializer.descriptor.serialName)
encodeSerializableElement(descriptor, 1, actualSerializer.cast(), value)
}
}
public final override fun deserialize(decoder: Decoder): T = decoder.decodeStructure(descriptor) {
var klassName: String? = null
+ var serialPolymorphicNumber: Int? = null
var value: Any? = null
if (decodeSequentially()) {
return@decodeStructure decodeSequentially(this)
@@ -48,14 +58,25 @@ public abstract class AbstractPolymorphicSerializer internal constructo
CompositeDecoder.DECODE_DONE -> {
break@mainLoop
}
+
0 -> {
- klassName = decodeStringElement(descriptor, index)
+ if (descriptor.useSerialPolymorphicNumbers)
+ serialPolymorphicNumber = decodeIntElement(descriptor, index)
+ else
+ klassName = decodeStringElement(descriptor, index)
}
+
1 -> {
- klassName = requireNotNull(klassName) { "Cannot read polymorphic value before its type token" }
- val serializer = findPolymorphicSerializer(this, klassName)
+ val serializer = if (descriptor.useSerialPolymorphicNumbers) {
+ requireNotNull(serialPolymorphicNumber) { "Cannot read polymorphic value before its type token" }
+ findPolymorphicSerializerWithNumber(this, serialPolymorphicNumber)
+ } else {
+ requireNotNull(klassName) { "Cannot read polymorphic value before its type token" }
+ findPolymorphicSerializer(this, klassName)
+ }
value = decodeSerializableElement(descriptor, index, serializer)
}
+
else -> throw SerializationException(
"Invalid index in polymorphic deserialization of " +
(klassName ?: "unknown class") +
@@ -83,6 +104,16 @@ public abstract class AbstractPolymorphicSerializer internal constructo
klassName: String?
): DeserializationStrategy? = decoder.serializersModule.getPolymorphic(baseClass, klassName)
+ /**
+ * TODO
+ */
+ @InternalSerializationApi
+ public open fun findPolymorphicSerializerWithNumberOrNull(
+ decoder: CompositeDecoder,
+ serialPolymorphicNumber: Int?
+ ): DeserializationStrategy? =
+ decoder.serializersModule.getPolymorphicWithNumber(baseClass, serialPolymorphicNumber)
+
/**
* Lookups an actual serializer for given [value] within the current [base class][baseClass].
@@ -109,6 +140,22 @@ internal fun throwSubtypeNotRegistered(subClassName: String?, baseClass: KClass<
)
}
+@JvmName("throwSubtypeNotRegistered")
+internal fun throwSubtypeNotRegistered(serialPolymorphicNumber: Int?, baseClass: KClass<*>): Nothing {
+ val scope = "in the polymorphic scope of '${baseClass.simpleName}'"
+ throw SerializationException(
+ (
+ if (serialPolymorphicNumber == null)
+ "Class discriminator serial polymorphic number was missing and no default serializers were registered $scope."
+ else
+ "Serializer for subclass serial polymorphic number '$serialPolymorphicNumber' is not found $scope.\n" +
+ "Check if class with serial polymorphic number '$serialPolymorphicNumber' exists and serializer is registered in a corresponding SerializersModule.\n" +
+ "To be registered automatically, class annotated with '@SerialPolymorphicNumber($serialPolymorphicNumber)' has to be '@Serializable', and the base class '${baseClass.simpleName}' has to be sealed and '@Serializable'.\n"
+ ) +
+ "\nRemove the `@UseSerialPolymorphicNumbers` annotation from the base class `${baseClass.simpleName}` if you want to switch back to polymorphic serialization using the serial name strings."
+ )
+}
+
@JvmName("throwSubtypeNotRegistered")
internal fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing =
throwSubtypeNotRegistered(subClass.simpleName ?: "$subClass", baseClass)
diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
index 1b8d431e1a..69552b4d00 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
@@ -21,7 +21,15 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
) {
private val subclasses: MutableList, KSerializer>> = mutableListOf()
private var defaultSerializerProvider: ((Base) -> SerializationStrategy?)? = null
- private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy?)? = null
+ private var defaultDeserializerProvider: PolymorphicDeserializerProvider? = null
+
+ /*
+ // TODO implement this or remove?
+ /**
+ * If specified, overrides [SerializersModuleBuilder.allUseSerialPolymorphicNumbers] and the [UseSerialPolymorphicNumbers] annotation.
+ */
+ public var useSerialPolymorphicNumbers: Boolean? = null
+ */
/**
* Registers a [subclass] [serializer] in the resulting module under the [base class][Base].
@@ -30,6 +38,19 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
subclasses.add(subclass to serializer)
}
+ /*
+ // TODO implement this or remove
+ /**
+ * Registers a [subclass] [serializer] in the resulting module under the [base class][Base] with the serial polymorphic number.
+ * If the class already has a [SerialPolymorphicNumber] annotation it's overridden by [serialPolymorphicNumber] here.
+ */
+ public fun subclassWithSerialPolymorphicNumber(
+ subclass: KClass, serialPolymorphicNumber: Int, serializer: KSerializer
+ ) {
+ // TODO
+ }
+ */
+
/**
* Adds a default serializers provider associated with the given [baseClass] to the resulting module.
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
@@ -54,6 +75,17 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
this.defaultDeserializerProvider = defaultDeserializerProvider
}
+ /*
+ // TODO remove
+ @Deprecated(
+ "Deprecated in favor of function with new `PolymorphicDeserializerProvider` API",
+ level = DeprecationLevel.WARNING // Since TODO. Raise to ERROR in TODO, hide in TODO
+ )
+ public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) {
+ defaultDeserializer(defaultDeserializerProvider.toNewApi())
+ }
+ */
+
/**
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
* This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [defaultDeserializer].
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
index 8a9126d747..efcc391baf 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
@@ -62,6 +62,18 @@ public sealed class SerializersModule {
@ExperimentalSerializationApi
public abstract fun getPolymorphic(baseClass: KClass, serializedClassName: String?): DeserializationStrategy?
+ /**
+ * TODO
+ */
+ public abstract fun getPolymorphicWithNumber(
+ baseClass: KClass, serializedNumber: Int?
+ ): DeserializationStrategy?
+
+ // TODO remove
+ // TODO old design for cashing serializers by number in `AbstractPolymorphicSerializer` which probably doesn't work and is not needed
+ @ExperimentalSerializationApi
+ public abstract fun getPolymorphicForAllSubclasses(baseClass: KClass): Map, KSerializer>
+
/**
* Copies contents of this module to the given [collector].
*/
@@ -76,7 +88,8 @@ public sealed class SerializersModule {
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("EmptySerializersModule()"))
@JsName("EmptySerializersModuleLegacyJs") // Compatibility with JS
-public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap())
+public val EmptySerializersModule: SerializersModule =
+ SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap())
/**
* Returns a combination of two serial modules
@@ -131,6 +144,15 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true)
}
+
+ override fun polymorphicDefaultDeserializerForNumber(
+ baseClass: KClass,
+ defaultDeserializerProvider: PolymorphicDeserializerProviderForNumber
+ ) {
+ registerDefaultPolymorphicDeserializerForNumber(
+ baseClass, defaultDeserializerProvider, allowOverwrite = true
+ )
+ }
})
}
@@ -147,7 +169,9 @@ internal class SerialModuleImpl(
@JvmField val polyBase2Serializers: Map, Map, KSerializer<*>>>,
private val polyBase2DefaultSerializerProvider: Map, PolymorphicSerializerProvider<*>>,
private val polyBase2NamedSerializers: Map, Map>>,
- private val polyBase2DefaultDeserializerProvider: Map, PolymorphicDeserializerProvider<*>>
+ private val polyBase2NumberedSerializers: Map, Map>>, // TODO remove
+ private val polyBase2DefaultDeserializerProvider: Map, PolymorphicDeserializerProvider<*>>,
+ private val polyBase2DefaultDeserializerProviderForNumber: Map, PolymorphicDeserializerProviderForNumber<*>>
) : SerializersModule() {
override fun getPolymorphic(baseClass: KClass, value: T): SerializationStrategy? {
@@ -167,6 +191,23 @@ internal class SerialModuleImpl(
return (polyBase2DefaultDeserializerProvider[baseClass] as? PolymorphicDeserializerProvider)?.invoke(serializedClassName)
}
+ override fun getPolymorphicWithNumber(
+ baseClass: KClass, serializedNumber: Int?
+ ): DeserializationStrategy? {
+ // Registered
+ val registered = polyBase2NumberedSerializers[baseClass]?.get(serializedNumber) as? KSerializer
+ if (registered != null) return registered
+ // Default
+ return (polyBase2DefaultDeserializerProviderForNumber[baseClass] as? PolymorphicDeserializerProviderForNumber)?.invoke(
+ serializedNumber
+ )
+ }
+
+ // TODO remove
+ @ExperimentalSerializationApi
+ override fun getPolymorphicForAllSubclasses(baseClass: KClass): Map, KSerializer> =
+ polyBase2Serializers.getValue(baseClass) as Map, KSerializer>
+
override fun getContextual(kClass: KClass, typeArgumentsSerializers: List>): KSerializer? {
return (class2ContextualFactory[kClass]?.invoke(typeArgumentsSerializers)) as? KSerializer?
}
@@ -202,7 +243,25 @@ internal class SerialModuleImpl(
}
}
+/*
+// TODO remove old suboptimal design
+
+public interface PolymorphicDeserializerProvider {
+ public fun fromClassName(className: String?): DeserializationStrategy?
+ public fun fromSerialPolymorphicNumber(serialPolymorphicNumber: Int?): DeserializationStrategy?
+}
+
+internal fun ((className: String?) -> DeserializationStrategy?).toNewApi() =
+ object : PolymorphicDeserializerProvider {
+ override fun fromClassName(className: String?): DeserializationStrategy? =
+ this@toNewApi(className)
+
+ override fun fromSerialPolymorphicNumber(serialPolymorphicNumber: Int?): DeserializationStrategy? =
+ throw AssertionError("This instance should only be created by legacy code therefore this function shouldn't be invoked here.")
+ }
+*/
internal typealias PolymorphicDeserializerProvider = (className: String?) -> DeserializationStrategy?
+internal typealias PolymorphicDeserializerProviderForNumber = (serialPolymorphicNumber: Int?) -> DeserializationStrategy?
internal typealias PolymorphicSerializerProvider = (value: Base) -> SerializationStrategy?
/** This class is needed to support re-registering the same static (argless) serializers:
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
index dfb9d819e3..2503b5c24b 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
@@ -45,10 +45,14 @@ public fun EmptySerializersModule(): SerializersModule = @Suppress("DEPRECATION"
@OptIn(ExperimentalSerializationApi::class)
public class SerializersModuleBuilder @PublishedApi internal constructor() : SerializersModuleCollector {
private val class2ContextualProvider: MutableMap, ContextualProvider> = hashMapOf()
+ //public var allUseSerialPolymorphicNumbers : Boolean? = null // TODO implement this or remove
+ //private val class2UseSerialPolymorphicNumbers : MutableMap, Boolean> = hashMapOf() // TODO implement this or remove
private val polyBase2Serializers: MutableMap, MutableMap, KSerializer<*>>> = hashMapOf()
private val polyBase2DefaultSerializerProvider: MutableMap, PolymorphicSerializerProvider<*>> = hashMapOf()
private val polyBase2NamedSerializers: MutableMap, MutableMap>> = hashMapOf()
+ private val polyBase2NumberedSerializers: MutableMap, MutableMap>> = hashMapOf()
private val polyBase2DefaultDeserializerProvider: MutableMap, PolymorphicDeserializerProvider<*>> = hashMapOf()
+ private val polyBase2DefaultDeserializerProviderForNumber: MutableMap, PolymorphicDeserializerProviderForNumber<*>> = hashMapOf()
/**
* Adds [serializer] associated with given [kClass] for contextual serialization.
@@ -132,6 +136,16 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false)
}
+ /**
+ * TODO
+ */
+ public override fun polymorphicDefaultDeserializerForNumber(
+ baseClass: KClass,
+ defaultDeserializerProvider: (serialPolymorphicNumber: Int?) -> DeserializationStrategy?
+ ) {
+ registerDefaultPolymorphicDeserializerForNumber(baseClass, defaultDeserializerProvider, false)
+ }
+
/**
* Copies the content of [module] module into the current builder.
*/
@@ -174,6 +188,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
internal fun registerDefaultPolymorphicDeserializer(
baseClass: KClass,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?,
+ //defaultDeserializerProvider: PolymorphicDeserializerProvider,
allowOverwrite: Boolean
) {
val previous = polyBase2DefaultDeserializerProvider[baseClass]
@@ -183,6 +198,20 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
polyBase2DefaultDeserializerProvider[baseClass] = defaultDeserializerProvider
}
+ @JvmName("registerDefaultPolymorphicDeserializerForNumber") // Don't mangle method name for prettier stack traces
+ internal fun registerDefaultPolymorphicDeserializerForNumber(
+ baseClass: KClass,
+ defaultDeserializerProvider: (polymorphicSerialNumber: Int?) -> DeserializationStrategy?,
+ //defaultDeserializerProvider: PolymorphicDeserializerProviderForNumber,
+ allowOverwrite: Boolean
+ ) {
+ val previous = polyBase2DefaultDeserializerProviderForNumber[baseClass]
+ if (previous != null && previous != defaultDeserializerProvider && !allowOverwrite) {
+ throw IllegalArgumentException("Default deserializers provider for $baseClass is already registered: $previous")
+ }
+ polyBase2DefaultDeserializerProviderForNumber[baseClass] = defaultDeserializerProvider
+ }
+
@JvmName("registerPolymorphicSerializer") // Don't mangle method name for prettier stack traces
internal fun registerPolymorphicSerializer(
baseClass: KClass,
@@ -192,17 +221,21 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
) {
// Check for overwrite
val name = concreteSerializer.descriptor.serialName
+ val number = concreteSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass]
val baseClassSerializers = polyBase2Serializers.getOrPut(baseClass, ::hashMapOf)
val previousSerializer = baseClassSerializers[concreteClass]
val names = polyBase2NamedSerializers.getOrPut(baseClass, ::hashMapOf)
+ val numbers = polyBase2NumberedSerializers.getOrPut(baseClass, ::hashMapOf)
if (allowOverwrite) {
// Remove previous serializers from name mapping
if (previousSerializer != null) {
names.remove(previousSerializer.descriptor.serialName)
+ numbers.remove(previousSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass])
}
// Update mappings
baseClassSerializers[concreteClass] = concreteSerializer
names[name] = concreteSerializer
+ number?.let { numbers[it] = concreteSerializer }
return
}
// Overwrite prohibited
@@ -212,6 +245,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
} else {
// Cleanup name mapping
names.remove(previousSerializer.descriptor.serialName)
+ numbers.remove(previousSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass])
}
}
val previousByName = names[name]
@@ -222,14 +256,45 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
"have the same serial name '$name': '$concreteClass' and '$conflictingClass'"
)
}
+ number?.let {
+ val previousByNumber = numbers[it]
+ if (previousByNumber != null) {
+ val conflictingClass =
+ polyBase2Serializers[baseClass]!!.asSequence().find { it.value === previousByNumber }
+ throw IllegalArgumentException(
+ "Multiple polymorphic serializers for base class '$baseClass' " +
+ "have the same polymorphic serial number '$number': '$concreteClass' and '$conflictingClass'"
+ )
+ }
+ }
// Overwrite if no conflicts
baseClassSerializers[concreteClass] = concreteSerializer
names[name] = concreteSerializer
+ number?.let { numbers[it] = concreteSerializer }
+ }
+
+ /*
+ // TODO implement this or remove?
+ internal fun registerUseSerialPolymorphicNumbers(baseClass: KClass<*>, useSerialPolymorphicNumbers: Boolean?) {
+ // TODO what about the annotation? baseDescriptor?
+ if (useSerialPolymorphicNumbers !== null)
+ class2UseSerialPolymorphicNumbers.put(baseClass, useSerialPolymorphicNumbers)
+ else
+ class2UseSerialPolymorphicNumbers.remove(baseClass)
}
+ */
@PublishedApi
internal fun build(): SerializersModule =
- SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2DefaultSerializerProvider, polyBase2NamedSerializers, polyBase2DefaultDeserializerProvider)
+ SerialModuleImpl(
+ class2ContextualProvider,
+ polyBase2Serializers,
+ polyBase2DefaultSerializerProvider,
+ polyBase2NamedSerializers,
+ polyBase2NumberedSerializers,
+ polyBase2DefaultDeserializerProvider,
+ polyBase2DefaultDeserializerProviderForNumber
+ )
}
/**
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
index c33d45a4c2..8421cab2bb 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
@@ -59,6 +59,20 @@ public interface SerializersModuleCollector {
defaultSerializerProvider: (value: Base) -> SerializationStrategy?
)
+ /*
+ // TODO remove
+ @Deprecated(
+ "Deprecated in favor of function with new `PolymorphicDeserializerProvider` API",
+ level = DeprecationLevel.WARNING // Since TODO. Raise to ERROR in TODO, hide in TODO
+ )
+ public fun polymorphicDefaultDeserializer(
+ baseClass: KClass,
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?
+ ) {
+ polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider.toNewApi())
+ }
+ */
+
/**
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
@@ -72,7 +86,7 @@ public interface SerializersModuleCollector {
*/
public fun polymorphicDefaultDeserializer(
baseClass: KClass,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?
+ defaultDeserializerProvider: PolymorphicDeserializerProvider
)
/**
@@ -101,4 +115,9 @@ public interface SerializersModuleCollector {
) {
polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)
}
+
+ public fun polymorphicDefaultDeserializerForNumber(
+ baseClass: KClass,
+ defaultDeserializerProvider: PolymorphicDeserializerProviderForNumber
+ )
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt
index e4606fae05..2b8126c366 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt
@@ -87,4 +87,11 @@ internal class PolymorphismValidator(
) {
// Nothing here
}
+
+ override fun polymorphicDefaultDeserializerForNumber(
+ baseClass: KClass,
+ defaultDeserializerProvider: (serialPolymorphicNumber: Int?) -> DeserializationStrategy?
+ ) {
+ // Nothing here
+ }
}
From d8e06b266dd9d75293857cf603171037e18d5d2a Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sun, 18 Feb 2024 11:16:22 +0800
Subject: [PATCH 02/12] Remove unused code introduced in the previous commit
---
.../modules/PolymorphicModuleBuilder.kt | 11 ---------
.../modules/SerializersModule.kt | 23 +++----------------
.../modules/SerializersModuleCollector.kt | 14 -----------
3 files changed, 3 insertions(+), 45 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
index 69552b4d00..1b1c4e01c5 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
@@ -75,17 +75,6 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
this.defaultDeserializerProvider = defaultDeserializerProvider
}
- /*
- // TODO remove
- @Deprecated(
- "Deprecated in favor of function with new `PolymorphicDeserializerProvider` API",
- level = DeprecationLevel.WARNING // Since TODO. Raise to ERROR in TODO, hide in TODO
- )
- public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) {
- defaultDeserializer(defaultDeserializerProvider.toNewApi())
- }
- */
-
/**
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
* This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [defaultDeserializer].
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
index efcc391baf..956dadf8a3 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
@@ -69,7 +69,7 @@ public sealed class SerializersModule {
baseClass: KClass, serializedNumber: Int?
): DeserializationStrategy?
- // TODO remove
+ // TODO remove or use
// TODO old design for cashing serializers by number in `AbstractPolymorphicSerializer` which probably doesn't work and is not needed
@ExperimentalSerializationApi
public abstract fun getPolymorphicForAllSubclasses(baseClass: KClass): Map, KSerializer>
@@ -169,7 +169,7 @@ internal class SerialModuleImpl(
@JvmField val polyBase2Serializers: Map, Map, KSerializer<*>>>,
private val polyBase2DefaultSerializerProvider: Map, PolymorphicSerializerProvider<*>>,
private val polyBase2NamedSerializers: Map, Map>>,
- private val polyBase2NumberedSerializers: Map, Map>>, // TODO remove
+ private val polyBase2NumberedSerializers: Map, Map>>,
private val polyBase2DefaultDeserializerProvider: Map, PolymorphicDeserializerProvider<*>>,
private val polyBase2DefaultDeserializerProviderForNumber: Map, PolymorphicDeserializerProviderForNumber<*>>
) : SerializersModule() {
@@ -203,7 +203,7 @@ internal class SerialModuleImpl(
)
}
- // TODO remove
+ // TODO remove or use
@ExperimentalSerializationApi
override fun getPolymorphicForAllSubclasses(baseClass: KClass): Map, KSerializer> =
polyBase2Serializers.getValue(baseClass) as Map, KSerializer>
@@ -243,23 +243,6 @@ internal class SerialModuleImpl(
}
}
-/*
-// TODO remove old suboptimal design
-
-public interface PolymorphicDeserializerProvider {
- public fun fromClassName(className: String?): DeserializationStrategy?
- public fun fromSerialPolymorphicNumber(serialPolymorphicNumber: Int?): DeserializationStrategy?
-}
-
-internal fun ((className: String?) -> DeserializationStrategy?).toNewApi() =
- object : PolymorphicDeserializerProvider {
- override fun fromClassName(className: String?): DeserializationStrategy? =
- this@toNewApi(className)
-
- override fun fromSerialPolymorphicNumber(serialPolymorphicNumber: Int?): DeserializationStrategy? =
- throw AssertionError("This instance should only be created by legacy code therefore this function shouldn't be invoked here.")
- }
-*/
internal typealias PolymorphicDeserializerProvider = (className: String?) -> DeserializationStrategy?
internal typealias PolymorphicDeserializerProviderForNumber = (serialPolymorphicNumber: Int?) -> DeserializationStrategy?
internal typealias PolymorphicSerializerProvider = (value: Base) -> SerializationStrategy?
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
index 8421cab2bb..fc0c7d12ae 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
@@ -59,20 +59,6 @@ public interface SerializersModuleCollector {
defaultSerializerProvider: (value: Base) -> SerializationStrategy?
)
- /*
- // TODO remove
- @Deprecated(
- "Deprecated in favor of function with new `PolymorphicDeserializerProvider` API",
- level = DeprecationLevel.WARNING // Since TODO. Raise to ERROR in TODO, hide in TODO
- )
- public fun polymorphicDefaultDeserializer(
- baseClass: KClass,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?
- ) {
- polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider.toNewApi())
- }
- */
-
/**
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
From f80d7a8471ae422f83dba956d0fdca614867d316 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sun, 18 Feb 2024 12:37:37 +0800
Subject: [PATCH 03/12] Remove unused code related to storing the map from
polymorphic subclasses to polymorphic numbers
See commit 3289fb22b30c7fe5ec7d577e3ffed3b0bc8b0a71.
---
.../internal/AbstractPolymorphicSerializer.kt | 1 -
.../kotlinx/serialization/modules/SerializersModule.kt | 10 ----------
2 files changed, 11 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
index 7b0c3bfe1a..209fc970df 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -36,7 +36,6 @@ public abstract class AbstractPolymorphicSerializer internal constructo
encodeIntElement(
descriptor,
0,
- // it seems not possible to cache this with the current implementation that serializers are completely separated from serializers modules
actualSerializer.descriptor.serialPolymorphicNumberByBaseClass.getValue(baseClass)
)
else
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
index 956dadf8a3..1aee35c1a2 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
@@ -69,11 +69,6 @@ public sealed class SerializersModule {
baseClass: KClass, serializedNumber: Int?
): DeserializationStrategy?
- // TODO remove or use
- // TODO old design for cashing serializers by number in `AbstractPolymorphicSerializer` which probably doesn't work and is not needed
- @ExperimentalSerializationApi
- public abstract fun getPolymorphicForAllSubclasses(baseClass: KClass): Map, KSerializer>
-
/**
* Copies contents of this module to the given [collector].
*/
@@ -203,11 +198,6 @@ internal class SerialModuleImpl(
)
}
- // TODO remove or use
- @ExperimentalSerializationApi
- override fun getPolymorphicForAllSubclasses(baseClass: KClass): Map, KSerializer> =
- polyBase2Serializers.getValue(baseClass) as Map, KSerializer>
-
override fun getContextual(kClass: KClass, typeArgumentsSerializers: List>): KSerializer? {
return (class2ContextualFactory[kClass]?.invoke(typeArgumentsSerializers)) as? KSerializer?
}
From 5834dcd0d696b4f1f0efcf5045e8857c7378d609 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Tue, 20 Feb 2024 21:52:12 +0800
Subject: [PATCH 04/12] Locate and fix a bug caused by lazy evaluation brought
early in `SealedSerializer` that causes the tests to break
---
.../commonMain/src/kotlinx/serialization/SealedSerializer.kt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
index eb6f29cf54..286338a189 100644
--- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
@@ -117,7 +117,6 @@ public class SealedClassSerializer(
private val class2Serializer: Map, KSerializer>
private val serialName2Serializer: Map>
- private val serialPolymorphicNumber2Serializer : Map>?
init {
if (subclasses.size != subclassSerializers.size) {
@@ -140,8 +139,10 @@ public class SealedClassSerializer(
}
element
}.mapValues { it.value.value }
+ }
- serialPolymorphicNumber2Serializer = if (descriptor.useSerialPolymorphicNumbers)
+ private val serialPolymorphicNumber2Serializer: Map>? by lazy(LazyThreadSafetyMode.PUBLICATION) {
+ if (descriptor.useSerialPolymorphicNumbers)
class2Serializer.entries.groupingBy {
it.value.descriptor.serialPolymorphicNumberByBaseClass.getValue(baseClass)
}
From 5c24ef01ae8dff9a08502149301e4489d6c41c59 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Tue, 20 Feb 2024 21:58:42 +0800
Subject: [PATCH 05/12] Remove a redundant suppression annotation
---
.../src/kotlinx/serialization/descriptors/SerialDescriptors.kt | 1 -
1 file changed, 1 deletion(-)
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
index df5eb3c6c8..88200ca2c5 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
@@ -49,7 +49,6 @@ import kotlin.reflect.*
* }
* ```
*/
-@Suppress("FunctionName")
@OptIn(ExperimentalSerializationApi::class)
public fun buildClassSerialDescriptor(
serialName: String,
From 51d9a75f55424324fe38f7a3441aab2907e85e8f Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sat, 24 Feb 2024 18:45:49 +0800
Subject: [PATCH 06/12] Add some tests for serial polymorphic numbers which
fail for now
Some miscellaneous changes:
1. Add a `getSerialPolymorphicNumberByBaseClass` function in `SerialDescriptor` to throw the appropriate exception.
1. Add `defaultDeserializerForNumber` which was missing in `PolymorphicModuleBuilder`.
---
.../kotlinx/serialization/SealedSerializer.kt | 2 +-
.../descriptors/SerialDescriptor.kt | 7 ++
.../internal/AbstractPolymorphicSerializer.kt | 4 +-
.../modules/PolymorphicModuleBuilder.kt | 15 ++++
.../modules/SerializersModuleBuilders.kt | 4 +-
.../SerialPolymorphicNumberTest.kt | 74 +++++++++++++++++++
.../serialization/test/JsonTestHelpers.kt | 18 +++++
7 files changed, 118 insertions(+), 6 deletions(-)
create mode 100644 formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
create mode 100644 formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt
diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
index 286338a189..82452d866e 100644
--- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
@@ -144,7 +144,7 @@ public class SealedClassSerializer(
private val serialPolymorphicNumber2Serializer: Map>? by lazy(LazyThreadSafetyMode.PUBLICATION) {
if (descriptor.useSerialPolymorphicNumbers)
class2Serializer.entries.groupingBy {
- it.value.descriptor.serialPolymorphicNumberByBaseClass.getValue(baseClass)
+ it.value.descriptor.getSerialPolymorphicNumberByBaseClass(baseClass)
}
.aggregate, KSerializer>, Int, Map.Entry, KSerializer>>
{ key, accumulator, element, _ ->
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
index 94edbce928..28f11f1012 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
@@ -208,6 +208,13 @@ public interface SerialDescriptor {
@ExperimentalSerializationApi
public val serialPolymorphicNumberByBaseClass: Map, Int> get() = emptyMap()
+ @ExperimentalSerializationApi
+ public fun getSerialPolymorphicNumberByBaseClass(baseClass: KClass<*>): Int =
+ serialPolymorphicNumberByBaseClass.getOrElse(baseClass) {
+ throw SerializationException("The serial polymorphic number for $serialName in the scope of ${baseClass.simpleName} is not found. " +
+ "Please annotate the class with `@SerialPolymorphicNumber` with the first argument ${baseClass.simpleName}.")
+ }
+
/**
* Returns serial annotations of the associated class.
* Serial annotations can be used to specify an additional metadata that may be used during serialization.
diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
index 209fc970df..8940b03e20 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -34,9 +34,7 @@ public abstract class AbstractPolymorphicSerializer internal constructo
encoder.encodeStructure(descriptor) {
if (descriptor.useSerialPolymorphicNumbers)
encodeIntElement(
- descriptor,
- 0,
- actualSerializer.descriptor.serialPolymorphicNumberByBaseClass.getValue(baseClass)
+ descriptor, 0, actualSerializer.descriptor.getSerialPolymorphicNumberByBaseClass(baseClass)
)
else
encodeStringElement(descriptor, 0, actualSerializer.descriptor.serialName)
diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
index 1b1c4e01c5..798842f152 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
@@ -22,6 +22,7 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
private val subclasses: MutableList, KSerializer>> = mutableListOf()
private var defaultSerializerProvider: ((Base) -> SerializationStrategy?)? = null
private var defaultDeserializerProvider: PolymorphicDeserializerProvider? = null
+ private var defaultDeserializerProviderForNumber: PolymorphicDeserializerProviderForNumber? = null
/*
// TODO implement this or remove?
@@ -75,6 +76,16 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
this.defaultDeserializerProvider = defaultDeserializerProvider
}
+ /**
+ * TODO
+ */
+ public fun defaultDeserializerForNumber(defaultDeserializerProviderForNumber: (serialPolymorphicNumber: Int?) -> DeserializationStrategy?) {
+ require(this.defaultDeserializerProviderForNumber == null) {
+ "Default deserializer provider for number is already registered for class $baseClass: ${this.defaultDeserializerProvider}"
+ }
+ this.defaultDeserializerProviderForNumber = defaultDeserializerProviderForNumber
+ }
+
/**
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
* This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [defaultDeserializer].
@@ -123,6 +134,10 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons
if (defaultDeserializer != null) {
builder.registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializer, false)
}
+
+ defaultDeserializerProviderForNumber?.let {
+ builder.registerDefaultPolymorphicDeserializerForNumber(baseClass, it, false)
+ }
}
}
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
index 2503b5c24b..e66f11d2e6 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
@@ -230,7 +230,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
// Remove previous serializers from name mapping
if (previousSerializer != null) {
names.remove(previousSerializer.descriptor.serialName)
- numbers.remove(previousSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass])
+ previousSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass]?.let { numbers.remove(it) }
}
// Update mappings
baseClassSerializers[concreteClass] = concreteSerializer
@@ -245,7 +245,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
} else {
// Cleanup name mapping
names.remove(previousSerializer.descriptor.serialName)
- numbers.remove(previousSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass])
+ previousSerializer.descriptor.serialPolymorphicNumberByBaseClass[baseClass]?.let { numbers.remove(it) }
}
}
val previousByName = names[name]
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
new file mode 100644
index 0000000000..1c52aca4c8
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class SerialPolymorphicNumberTest {
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed1 {
+ @Serializable
+ @SerialPolymorphicNumber(Sealed1::class, 1)
+ class Case : Sealed1()
+ }
+
+ @Serializable
+ sealed class Sealed2 {
+ @Serializable
+ @SerialPolymorphicNumber(Sealed2::class, 1)
+ class Case : Sealed2()
+ }
+
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed3 {
+ @Serializable
+ class Case : Sealed3()
+ }
+
+ @Test
+ fun testSealed() {
+ testConversion(Sealed1.Case(), """{"type":1}""")
+ testConversion(Sealed2.Case(), """{"type":"kotlinx.serialization.SerialPolymorphicNumberTest.Case"}""")
+ assertFailsWith(SerializationException::class) {
+ Json.encodeToString(Sealed3.Case())
+ }
+ assertFailsWith(SerializationException::class) {
+ Json.decodeFromString("{}")
+ }
+ }
+
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Abstract {
+ @Serializable
+ @SerialPolymorphicNumber(Abstract::class, 1)
+ class Case : Abstract()
+
+ @Serializable
+ class Default(val type: Int?):Abstract()
+ }
+
+ val json = Json {
+ serializersModule = SerializersModule {
+ polymorphic(Abstract::class) {
+ subclass(Abstract.Case::class)
+ defaultDeserializerForNumber {
+ Abstract.Default.serializer()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testPolymorphicModule() {
+ testConversion(json, Abstract.Case(), """{"type":1}""")
+ testConversion(json, Abstract.Default(0), """{"type":0}""")
+ }
+}
\ No newline at end of file
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt
new file mode 100644
index 0000000000..0866e60d06
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+inline fun testConversion(json: Json, data: T, expectedHexString: String) {
+ val string = json.encodeToString(data)
+ assertEquals(expectedHexString, string)
+ assertEquals(data, json.decodeFromString(string))
+}
+
+inline fun testConversion(data: T, expectedHexString: String) =
+ testConversion(Json, data, expectedHexString)
From e101ae8e7805bfb6659a32394b2924da80afe243 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Tue, 27 Feb 2024 16:28:52 +0800
Subject: [PATCH 07/12] Enable Kotlin compiler bootstrap, use the corresponding
SNAPSHOT version, and update an annotation property name
The corresponding commit: https://github.com/huanshankeji/kotlin/commit/9860724c856e31b044321b21b59a5f8e87a3de70
---
core/commonMain/src/kotlinx/serialization/Annotations.kt | 2 +-
gradle.properties | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index 20f7b962e3..f8abe7959e 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -171,7 +171,7 @@ public annotation class UseSerialPolymorphicNumbers
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Repeatable
-public annotation class SerialPolymorphicNumber(val baseClass: KClass<*>, val value: Int)
+public annotation class SerialPolymorphicNumber(val baseClass: KClass<*>, val number: Int)
/**
* Indicates that property must be present during deserialization process, despite having a default value.
diff --git a/gradle.properties b/gradle.properties
index ff245336b3..94756b50eb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,8 +7,9 @@ version=1.6.3-SNAPSHOT
kotlin.version=1.9.21
+bootstrap=true
# This version takes precedence if 'bootstrap' property passed to project
-kotlin.version.snapshot=1.9.255-SNAPSHOT
+kotlin.version.snapshot=1.9.255-serialization-plugin-polymorphic-number-SNAPSHOT
# Also set KONAN_LOCAL_DIST environment variable in bootstrap mode to auto-assign konan.home
junit_version=4.12
From 1aa1354eb469992221868849385b6dee047da544 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Wed, 28 Feb 2024 18:05:29 +0800
Subject: [PATCH 08/12] Move `useSerialPolymorphicNumbers` and
`serialPolymorphicNumberByBaseClass` into `PluginGeneratedSerialDescriptor`
where it belongs and add some more tests in `SerialPolymorphicNumberTest`
(which fail for now)
---
.../descriptors/SerialDescriptors.kt | 18 +-----------------
.../PluginGeneratedSerialDescriptor.kt | 5 ++++-
.../SerialPolymorphicNumberTest.kt | 15 +++++++++++++++
3 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
index 88200ca2c5..de3cc17375 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
@@ -53,8 +53,6 @@ import kotlin.reflect.*
public fun buildClassSerialDescriptor(
serialName: String,
vararg typeParameters: SerialDescriptor,
- useSerialPolymorphicNumbers: Boolean = false,
- serialPolymorphicNumbers: Map, Int> = emptyMap(),
builderAction: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
@@ -65,8 +63,6 @@ public fun buildClassSerialDescriptor(
StructureKind.CLASS,
sdBuilder.elementNames.size,
typeParameters.toList(),
- useSerialPolymorphicNumbers,
- serialPolymorphicNumbers,
sdBuilder
)
}
@@ -143,23 +139,13 @@ public fun buildSerialDescriptor(
serialName: String,
kind: SerialKind,
vararg typeParameters: SerialDescriptor,
- useSerialPolymorphicNumbers: Boolean = false,
- serialPolymorphicNumbers: Map, Int> = emptyMap(),
builder: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
require(kind != StructureKind.CLASS) { "For StructureKind.CLASS please use 'buildClassSerialDescriptor' instead" }
val sdBuilder = ClassSerialDescriptorBuilder(serialName)
sdBuilder.builder()
- return SerialDescriptorImpl(
- serialName,
- kind,
- sdBuilder.elementNames.size,
- typeParameters.toList(),
- useSerialPolymorphicNumbers,
- serialPolymorphicNumbers,
- sdBuilder
- )
+ return SerialDescriptorImpl(serialName, kind, sdBuilder.elementNames.size, typeParameters.toList(), sdBuilder)
}
@@ -322,8 +308,6 @@ internal class SerialDescriptorImpl(
override val kind: SerialKind,
override val elementsCount: Int,
typeParameters: List,
- override val useSerialPolymorphicNumbers : Boolean,
- override val serialPolymorphicNumberByBaseClass : Map, Int>,
builder: ClassSerialDescriptorBuilder
) : SerialDescriptor, CachedNames {
diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
index a954bdab00..266cd3217d 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
@@ -8,6 +8,7 @@ package kotlinx.serialization.internal
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
+import kotlin.reflect.*
/**
* Implementation that plugin uses to implement descriptors for auto-generated serializers.
@@ -17,7 +18,9 @@ import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
internal open class PluginGeneratedSerialDescriptor(
override val serialName: String,
private val generatedSerializer: GeneratedSerializer<*>? = null,
- final override val elementsCount: Int
+ final override val elementsCount: Int,
+ override val useSerialPolymorphicNumbers: Boolean = false,
+ override val serialPolymorphicNumberByBaseClass: Map, Int> = emptyMap()
) : SerialDescriptor, CachedNames {
override val kind: SerialKind get() = StructureKind.CLASS
override val annotations: List get() = classAnnotations ?: emptyList()
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
index 1c52aca4c8..f60b2810ee 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
@@ -32,6 +32,19 @@ class SerialPolymorphicNumberTest {
class Case : Sealed3()
}
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed4 {
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed41 : Sealed4(){
+ @Serializable
+ @SerialPolymorphicNumber(Sealed4::class, 1)
+ @SerialPolymorphicNumber(Sealed41::class, 2)
+ class Case : Sealed41()
+ }
+ }
+
@Test
fun testSealed() {
testConversion(Sealed1.Case(), """{"type":1}""")
@@ -42,6 +55,8 @@ class SerialPolymorphicNumberTest {
assertFailsWith(SerializationException::class) {
Json.decodeFromString("{}")
}
+ testConversion(Sealed4.Sealed41.Case(), """{"type":1}""")
+ testConversion(Sealed4.Sealed41.Case(), """{"type":2}""")
}
@Serializable
From bcae134ff9b5d5e2778f05c57d64f11853a68cb9 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sat, 2 Mar 2024 19:04:03 +0800
Subject: [PATCH 09/12] Mark the annotations `UseSerialPolymorphicNumbers` and
`SerialPolymorphicNumber` with `@SerialInfo` to be stored in
`SerialDescriptor.annotations` and refactor related code
Main changes:
1. Extract common lazy properties in `CommonSerialDescriptor` and make both `PluginGeneratedSerialDescriptor` and `SerialDescriptorImpl` inherit it.
1. Support serial polymorphic numbers with JSON's custom implementations in `AbstractJsonTreeEncoder`, `StreamingJsonEncoder`, and `DynamicObjectEncoder` while adapting the `encodePolymorphically` and `decodeSerializableValuePolymorphic` functions.
1. Revert gradle.properties since there is no need to update the compiler plugin anymore.
1. Make all tests pass in `SerialPolymorphicNumberTest` and copy it into the Protobuf module and adapt.
---
.../src/kotlinx/serialization/Annotations.kt | 9 +-
.../descriptors/SerialDescriptor.kt | 35 ++++--
.../descriptors/SerialDescriptors.kt | 2 +-
.../internal/CommonSerialDescriptor.kt | 17 +++
.../PluginGeneratedSerialDescriptor.kt | 7 +-
.../SerialPolymorphicNumberTest.kt | 35 +++---
.../serialization/test/JsonTestHelpers.kt | 20 +++-
.../json/internal/Polymorphic.kt | 22 +++-
.../json/internal/StreamingJsonEncoder.kt | 11 +-
.../json/internal/TreeJsonEncoder.kt | 13 +-
.../json/internal/DynamicEncoders.kt | 10 +-
.../protobuf/SerialPolymorphicNumberTest.kt | 112 ++++++++++++++++++
gradle.properties | 3 +-
13 files changed, 234 insertions(+), 62 deletions(-)
create mode 100644 core/commonMain/src/kotlinx/serialization/internal/CommonSerialDescriptor.kt
create mode 100644 formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index f8abe7959e..65fabaadc4 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -155,22 +155,23 @@ public annotation class SerialName(val value: String)
/**
* Requires all subclasses to use [SerialPolymorphicNumber].
*/
-@MustBeDocumented
+@SerialInfo
@Target(AnnotationTarget.CLASS)
-@Repeatable
+@ExperimentalSerializationApi
public annotation class UseSerialPolymorphicNumbers
/**
* When its parent class is annotated with [UseSerialPolymorphicNumbers],
* overrides its [String]-typed serial name when serialized as a subclass of the parent class in [baseClass]
* (including the value overridden by [SerialName] if set)
- * with a [Int]-typed number in [value].
+ * with a [Int]-typed number in [number].
*
* Using a number instead of a string shortens the size of the serialized message, especially in a binary format.
*/
-@MustBeDocumented
+@SerialInfo
@Target(AnnotationTarget.CLASS)
@Repeatable
+@ExperimentalSerializationApi
public annotation class SerialPolymorphicNumber(val baseClass: KClass<*>, val number: Int)
/**
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
index 28f11f1012..b735a6219a 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
@@ -196,33 +196,44 @@ public interface SerialDescriptor {
@ExperimentalSerializationApi
public val elementsCount: Int
+ /**
+ * Returns serial annotations of the associated class.
+ * Serial annotations can be used to specify an additional metadata that may be used during serialization.
+ * Only annotations marked with [SerialInfo] are added to the resulting list.
+ */
+ @ExperimentalSerializationApi
+ public val annotations: List get() = emptyList()
+
/**
* TODO
*/
@ExperimentalSerializationApi
- public val useSerialPolymorphicNumbers: Boolean get() = false
+ public val useSerialPolymorphicNumbers: Boolean
+ get() =
+ annotations.any { it is UseSerialPolymorphicNumbers }
/**
* TODO
*/
@ExperimentalSerializationApi
- public val serialPolymorphicNumberByBaseClass: Map, Int> get() = emptyMap()
+ public val serialPolymorphicNumberByBaseClass: Map, Int>
+ get() =
+ annotations.asSequence().mapNotNull { it as? SerialPolymorphicNumber }
+ .groupBy { it.baseClass }
+ .mapValues {
+ it.value.singleOrNull()?.number
+ ?: throw SerializationException("duplicate base classes in `@SerialPolymorphicNumber` annotations registered for $serialName")
+ }
@ExperimentalSerializationApi
public fun getSerialPolymorphicNumberByBaseClass(baseClass: KClass<*>): Int =
serialPolymorphicNumberByBaseClass.getOrElse(baseClass) {
- throw SerializationException("The serial polymorphic number for $serialName in the scope of ${baseClass.simpleName} is not found. " +
- "Please annotate the class with `@SerialPolymorphicNumber` with the first argument ${baseClass.simpleName}.")
+ throw SerializationException(
+ "The serial polymorphic number for `$serialName` in the scope of `${baseClass.simpleName}` is not found. " +
+ "Please annotate the class with `@SerialPolymorphicNumber` with the first argument being `${baseClass.simpleName}`."
+ )
}
- /**
- * Returns serial annotations of the associated class.
- * Serial annotations can be used to specify an additional metadata that may be used during serialization.
- * Only annotations marked with [SerialInfo] are added to the resulting list.
- */
- @ExperimentalSerializationApi
- public val annotations: List get() = emptyList()
-
/**
* Returns a positional name of the child at the given [index].
* Positional name represents a corresponding property name in the class, associated with
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
index de3cc17375..6fe2e39a8e 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
@@ -309,7 +309,7 @@ internal class SerialDescriptorImpl(
override val elementsCount: Int,
typeParameters: List,
builder: ClassSerialDescriptorBuilder
-) : SerialDescriptor, CachedNames {
+) : CommonSerialDescriptor(), CachedNames {
override val annotations: List = builder.annotations
override val serialNames: Set = builder.elementNames.toHashSet()
diff --git a/core/commonMain/src/kotlinx/serialization/internal/CommonSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/CommonSerialDescriptor.kt
new file mode 100644
index 0000000000..3834d585b1
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/internal/CommonSerialDescriptor.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlin.reflect.*
+
+internal abstract class CommonSerialDescriptor : SerialDescriptor {
+ @ExperimentalSerializationApi
+ override val useSerialPolymorphicNumbers: Boolean by lazy { super.useSerialPolymorphicNumbers }
+
+ @ExperimentalSerializationApi
+ override val serialPolymorphicNumberByBaseClass: Map, Int> by lazy { super.serialPolymorphicNumberByBaseClass }
+}
\ No newline at end of file
diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
index 266cd3217d..0053097907 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
@@ -8,7 +8,6 @@ package kotlinx.serialization.internal
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
-import kotlin.reflect.*
/**
* Implementation that plugin uses to implement descriptors for auto-generated serializers.
@@ -18,10 +17,8 @@ import kotlin.reflect.*
internal open class PluginGeneratedSerialDescriptor(
override val serialName: String,
private val generatedSerializer: GeneratedSerializer<*>? = null,
- final override val elementsCount: Int,
- override val useSerialPolymorphicNumbers: Boolean = false,
- override val serialPolymorphicNumberByBaseClass: Map, Int> = emptyMap()
-) : SerialDescriptor, CachedNames {
+ final override val elementsCount: Int
+) : CommonSerialDescriptor(), CachedNames {
override val kind: SerialKind get() = StructureKind.CLASS
override val annotations: List get() = classAnnotations ?: emptyList()
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
index f60b2810ee..5595a6cf65 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerialPolymorphicNumberTest.kt
@@ -15,21 +15,25 @@ class SerialPolymorphicNumberTest {
sealed class Sealed1 {
@Serializable
@SerialPolymorphicNumber(Sealed1::class, 1)
- class Case : Sealed1()
+ data class Case1(val property: Int) : Sealed1()
+
+ @Serializable
+ @SerialPolymorphicNumber(Sealed1::class, 2)
+ object Case2 : Sealed1()
}
@Serializable
sealed class Sealed2 {
@Serializable
@SerialPolymorphicNumber(Sealed2::class, 1)
- class Case : Sealed2()
+ object Case : Sealed2()
}
@Serializable
@UseSerialPolymorphicNumbers
sealed class Sealed3 {
@Serializable
- class Case : Sealed3()
+ object Case : Sealed3()
}
@Serializable
@@ -37,37 +41,38 @@ class SerialPolymorphicNumberTest {
sealed class Sealed4 {
@Serializable
@UseSerialPolymorphicNumbers
- sealed class Sealed41 : Sealed4(){
+ sealed class Sealed41 : Sealed4() {
@Serializable
@SerialPolymorphicNumber(Sealed4::class, 1)
@SerialPolymorphicNumber(Sealed41::class, 2)
- class Case : Sealed41()
+ object Case : Sealed41()
}
}
@Test
fun testSealed() {
- testConversion(Sealed1.Case(), """{"type":1}""")
- testConversion(Sealed2.Case(), """{"type":"kotlinx.serialization.SerialPolymorphicNumberTest.Case"}""")
+ testConversion(Sealed1.Case1(1), """{"type":1,"property":1}""")
+ testConversion(Sealed1.Case2, """{"type":2}""")
+ testConversion(Sealed2.Case, """{"type":"${Sealed2.Case.serializer().descriptor.serialName}"}""")
assertFailsWith(SerializationException::class) {
- Json.encodeToString(Sealed3.Case())
+ Json.encodeToString(Sealed3.Case)
}
assertFailsWith(SerializationException::class) {
Json.decodeFromString("{}")
}
- testConversion(Sealed4.Sealed41.Case(), """{"type":1}""")
- testConversion(Sealed4.Sealed41.Case(), """{"type":2}""")
+ testConversion(Sealed4.Sealed41.Case, """{"type":1}""")
+ testConversion(Sealed4.Sealed41.Case, """{"type":2}""")
}
@Serializable
@UseSerialPolymorphicNumbers
- sealed class Abstract {
+ abstract class Abstract {
@Serializable
@SerialPolymorphicNumber(Abstract::class, 1)
- class Case : Abstract()
+ object Case : Abstract()
@Serializable
- class Default(val type: Int?):Abstract()
+ data class Default(val type: Int?) : Abstract()
}
val json = Json {
@@ -83,7 +88,7 @@ class SerialPolymorphicNumberTest {
@Test
fun testPolymorphicModule() {
- testConversion(json, Abstract.Case(), """{"type":1}""")
- testConversion(json, Abstract.Default(0), """{"type":0}""")
+ testConversion(json, Abstract.Case, """{"type":1}""")
+ assertEquals(Abstract.Default(0), json.decodeFromString("""{"type":0}"""))
}
}
\ No newline at end of file
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt
index 0866e60d06..c6274036b7 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonTestHelpers.kt
@@ -8,11 +8,19 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlin.test.*
-inline fun testConversion(json: Json, data: T, expectedHexString: String) {
- val string = json.encodeToString(data)
- assertEquals(expectedHexString, string)
- assertEquals(data, json.decodeFromString(string))
+inline fun testConversion(json: Json, data: T, expectedString: String) {
+ assertEquals(expectedString, json.encodeToString(data))
+ assertEquals(data, json.decodeFromString(expectedString))
+
+ jvmOnly {
+ assertEquals(expectedString, json.encodeViaStream(serializer(), data))
+ assertEquals(data, json.decodeViaStream(serializer(), expectedString))
+ }
+
+ val jsonElement = json.encodeToJsonElement(data)
+ assertEquals(expectedString, jsonElement.toString())
+ assertEquals(data, json.decodeFromJsonElement(jsonElement))
}
-inline fun testConversion(data: T, expectedHexString: String) =
- testConversion(Json, data, expectedHexString)
+inline fun testConversion(data: T, expectedString: String) =
+ testConversion(Json, data, expectedString)
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 636f340ddb..8522f5ed27 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
@@ -9,14 +9,13 @@ import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.modules.*
-import kotlin.jvm.*
@Suppress("UNCHECKED_CAST")
internal inline fun JsonEncoder.encodePolymorphically(
serializer: SerializationStrategy,
value: T,
- ifPolymorphic: (String) -> Unit
+ ifHasBaseClassDiscriminator: (String) -> Unit,
+ ifUseSerialPolymorphicNumber : (Int) -> Unit
) {
if (json.configuration.useArrayPolymorphism) {
serializer.serialize(this, value)
@@ -42,7 +41,11 @@ internal inline fun JsonEncoder.encodePolymorphically(
actual as SerializationStrategy
} else serializer
- if (baseClassDiscriminator != null) ifPolymorphic(baseClassDiscriminator)
+ if (baseClassDiscriminator != null) ifHasBaseClassDiscriminator(baseClassDiscriminator)
+
+ if (isPolymorphicSerializer && serializer.descriptor.useSerialPolymorphicNumbers)
+ ifUseSerialPolymorphicNumber(actualSerializer.descriptor.getSerialPolymorphicNumberByBaseClass((serializer as AbstractPolymorphicSerializer).baseClass))
+
actualSerializer.serialize(this, value)
}
@@ -79,11 +82,18 @@ internal fun JsonDecoder.decodeSerializableValuePolymorphic(deserializer: De
val discriminator = deserializer.descriptor.classDiscriminator(json)
val jsonTree = cast(decodeJsonElement(), deserializer.descriptor)
- val type = jsonTree[discriminator]?.jsonPrimitive?.contentOrNull // differentiate between `"type":"null"` and `"type":null`.
+ val useSerialPolymorphicNumbers = deserializer.descriptor.useSerialPolymorphicNumbers
+ val type = if (useSerialPolymorphicNumbers)
+ jsonTree[discriminator]?.jsonPrimitive?.intOrNull
+ else
+ jsonTree[discriminator]?.jsonPrimitive?.contentOrNull // differentiate between `"type":"null"` and `"type":null`.
@Suppress("UNCHECKED_CAST")
val actualSerializer =
try {
- deserializer.findPolymorphicSerializer(this, type)
+ if (useSerialPolymorphicNumbers)
+ deserializer.findPolymorphicSerializerWithNumber(this, type as Int?)
+ else
+ deserializer.findPolymorphicSerializer(this, type as String?)
} catch (it: SerializationException) { // Wrap SerializationException into JsonDecodingException to preserve input
throw JsonDecodingException(-1, it.message!!, jsonTree.toString())
} as DeserializationStrategy
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 cf562de5c8..f45c804c47 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
@@ -43,6 +43,7 @@ internal class StreamingJsonEncoder(
// Forces serializer to wrap all values into quotes
private var forceQuoting: Boolean = false
private var polymorphicDiscriminator: String? = null
+ private var serialPolymorphicNumber : Int? = null
init {
val i = mode.ordinal
@@ -61,9 +62,7 @@ internal class StreamingJsonEncoder(
}
override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) {
- encodePolymorphically(serializer, value) {
- polymorphicDiscriminator = it
- }
+ encodePolymorphically(serializer, value, { polymorphicDiscriminator = it }, { serialPolymorphicNumber = it })
}
private fun encodeTypeInfo(descriptor: SerialDescriptor) {
@@ -71,7 +70,11 @@ internal class StreamingJsonEncoder(
encodeString(polymorphicDiscriminator!!)
composer.print(COLON)
composer.space()
- encodeString(descriptor.serialName)
+ serialPolymorphicNumber?.let {
+ encodeInt(it)
+ serialPolymorphicNumber = null
+ }
+ ?: encodeString(descriptor.serialName)
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
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 5e3c808689..ac532f5f16 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
@@ -35,6 +35,7 @@ private sealed class AbstractJsonTreeEncoder(
protected val configuration = json.configuration
private var polymorphicDiscriminator: String? = null
+ private var serialPolymorphicNumber: Int? = null
override fun elementName(descriptor: SerialDescriptor, index: Int): String =
descriptor.getJsonElementName(json, index)
@@ -77,7 +78,8 @@ 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.carrierDescriptor(serializersModule).requiresTopLevelTag) {
- encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
+ encodePolymorphically(
+ serializer, value, { polymorphicDiscriminator = it }, { serialPolymorphicNumber = it })
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
encodeSerializableValue(serializer, value)
}
@@ -149,7 +151,14 @@ private sealed class AbstractJsonTreeEncoder(
}
if (polymorphicDiscriminator != null) {
- encoder.putElement(polymorphicDiscriminator!!, JsonPrimitive(descriptor.serialName))
+ encoder.putElement(
+ polymorphicDiscriminator!!,
+ serialPolymorphicNumber?.let {
+ serialPolymorphicNumber = null
+ JsonPrimitive(it)
+ }
+ ?: JsonPrimitive(descriptor.serialName)
+ )
polymorphicDiscriminator = null
}
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 4c4841d47d..32b0baa094 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
+++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
@@ -62,6 +62,7 @@ private class DynamicObjectEncoder(
* Flag of usage polymorphism with discriminator attribute
*/
private var polymorphicDiscriminator: String? = null
+ private var serialPolymorphicNumber: Int? = null
private object NoOutputMark
@@ -183,9 +184,7 @@ private class DynamicObjectEncoder(
private fun isNotStructured() = result === NoOutputMark
override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) {
- encodePolymorphically(serializer, value) {
- polymorphicDiscriminator = it
- }
+ encodePolymorphically(serializer, value, { polymorphicDiscriminator = it }, { serialPolymorphicNumber = it })
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
@@ -208,8 +207,9 @@ private class DynamicObjectEncoder(
enterNode(child, newMode)
}
- if (polymorphicDiscriminator != null) {
- current.jsObject[polymorphicDiscriminator!!] = descriptor.serialName
+ polymorphicDiscriminator?.let {
+ current.jsObject[it] =
+ serialPolymorphicNumber?.also { serialPolymorphicNumber = null } ?: descriptor.serialName
polymorphicDiscriminator = null
}
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt
new file mode 100644
index 0000000000..ba4e9822f5
--- /dev/null
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf
+
+import kotlinx.serialization.*
+import kotlinx.serialization.modules.*
+import kotlin.test.*
+
+/**
+ * Copied and adapted from [kotlinx.serialization.SerialPolymorphicNumberTest].
+ */
+class SerialPolymorphicNumberTest {
+ inline fun testConversion(protoBuf: ProtoBuf, data: T, expectedHexString: String) {
+ val string = protoBuf.encodeToHexString(data)
+ assertEquals(expectedHexString, string)
+ assertEquals(data, protoBuf.decodeFromHexString(string))
+ }
+
+ inline fun testConversion(data: T, expectedHexString: String) =
+ testConversion(ProtoBuf, data, expectedHexString)
+
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed1 {
+ @Serializable
+ @SerialPolymorphicNumber(Sealed1::class, 1)
+ data class Case1(val property: Int) : Sealed1()
+
+ @Serializable
+ @SerialPolymorphicNumber(Sealed1::class, 2)
+ object Case2 : Sealed1()
+ }
+
+ @Serializable
+ sealed class Sealed2 {
+ @Serializable
+ @SerialPolymorphicNumber(Sealed2::class, 1)
+ object Case : Sealed2()
+ }
+
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed3 {
+ @Serializable
+ object Case : Sealed3()
+ }
+
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed4 {
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ sealed class Sealed41 : Sealed4() {
+ @Serializable
+ @SerialPolymorphicNumber(Sealed4::class, 1)
+ @SerialPolymorphicNumber(Sealed41::class, 2)
+ object Case : Sealed41()
+ }
+ }
+
+ @Test
+ fun testSealed() {
+ testConversion(Sealed1.Case1(1), "080112020801")
+ testConversion(Sealed1.Case2, "08021200")
+ run {
+ val serialName = Sealed2.Case.serializer().descriptor.serialName
+ testConversion(
+ Sealed2.Case,
+ "0a" + serialName.length.toByte().toHexString() + serialName.encodeToByteArray().toHexString() + "1200"
+ )
+ }
+ assertFailsWith(SerializationException::class) {
+ ProtoBuf.encodeToHexString(Sealed3.Case)
+ }
+ assertFailsWith(SerializationException::class) {
+ ProtoBuf.decodeFromHexString("08011200")
+ }
+ testConversion(Sealed4.Sealed41.Case, "08011200")
+ testConversion(Sealed4.Sealed41.Case, "08021200")
+ }
+
+ @Serializable
+ @UseSerialPolymorphicNumbers
+ abstract class Abstract {
+ @Serializable
+ @SerialPolymorphicNumber(Abstract::class, 1)
+ object Case : Abstract()
+
+ @Serializable
+ data class Default(val type: Int?) : Abstract()
+ }
+
+ val protoBuf = ProtoBuf {
+ serializersModule = SerializersModule {
+ polymorphic(Abstract::class) {
+ subclass(Abstract.Case::class)
+ defaultDeserializerForNumber {
+ Abstract.Default.serializer()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testPolymorphicModule() {
+ testConversion(protoBuf, Abstract.Case, "08011200")
+ // TODO Not working. However, even the original default serializer for serial names is not working for Protobuf as tested.
+ // assertEquals(Abstract.Default(0), protoBuf.decodeFromHexString("08001200"))
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 94756b50eb..ff245336b3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,9 +7,8 @@ version=1.6.3-SNAPSHOT
kotlin.version=1.9.21
-bootstrap=true
# This version takes precedence if 'bootstrap' property passed to project
-kotlin.version.snapshot=1.9.255-serialization-plugin-polymorphic-number-SNAPSHOT
+kotlin.version.snapshot=1.9.255-SNAPSHOT
# Also set KONAN_LOCAL_DIST environment variable in bootstrap mode to auto-assign konan.home
junit_version=4.12
From c53d324c1871c2f69a786ddb3cbd4a5edcfd0782 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sat, 2 Mar 2024 19:14:03 +0800
Subject: [PATCH 10/12] Run `:apiDump` and review
---
core/api/kotlinx-serialization-core.api | 39 +++++++++++++++++++++++--
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api
index 4b46dcca17..8d22cb4fe3 100644
--- a/core/api/kotlinx-serialization-core.api
+++ b/core/api/kotlinx-serialization-core.api
@@ -69,6 +69,7 @@ public final class kotlinx/serialization/PolymorphicSerializer : kotlinx/seriali
public final class kotlinx/serialization/PolymorphicSerializerKt {
public static final fun findPolymorphicSerializer (Lkotlinx/serialization/internal/AbstractPolymorphicSerializer;Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/String;)Lkotlinx/serialization/DeserializationStrategy;
public static final fun findPolymorphicSerializer (Lkotlinx/serialization/internal/AbstractPolymorphicSerializer;Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy;
+ public static final fun findPolymorphicSerializerWithNumber (Lkotlinx/serialization/internal/AbstractPolymorphicSerializer;Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Integer;)Lkotlinx/serialization/DeserializationStrategy;
}
public abstract interface annotation class kotlinx/serialization/Required : java/lang/annotation/Annotation {
@@ -79,6 +80,7 @@ public final class kotlinx/serialization/SealedClassSerializer : kotlinx/seriali
public fun (Ljava/lang/String;Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;[Ljava/lang/annotation/Annotation;)V
public fun findPolymorphicSerializerOrNull (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/String;)Lkotlinx/serialization/DeserializationStrategy;
public fun findPolymorphicSerializerOrNull (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy;
+ public fun findPolymorphicSerializerWithNumberOrNull (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Integer;)Lkotlinx/serialization/DeserializationStrategy;
public fun getBaseClass ()Lkotlin/reflect/KClass;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
}
@@ -99,6 +101,21 @@ public abstract interface annotation class kotlinx/serialization/SerialName : ja
public abstract fun value ()Ljava/lang/String;
}
+public abstract interface annotation class kotlinx/serialization/SerialPolymorphicNumber : java/lang/annotation/Annotation {
+ public abstract fun baseClass ()Ljava/lang/Class;
+ public abstract fun number ()I
+}
+
+public abstract interface annotation class kotlinx/serialization/SerialPolymorphicNumber$Container : java/lang/annotation/Annotation {
+ public abstract fun value ()[Lkotlinx/serialization/SerialPolymorphicNumber;
+}
+
+public synthetic class kotlinx/serialization/SerialPolymorphicNumber$Impl : kotlinx/serialization/SerialPolymorphicNumber {
+ public fun (Lkotlin/reflect/KClass;I)V
+ public final synthetic fun baseClass ()Ljava/lang/Class;
+ public final synthetic fun number ()I
+}
+
public abstract interface annotation class kotlinx/serialization/Serializable : java/lang/annotation/Annotation {
public abstract fun with ()Ljava/lang/Class;
}
@@ -153,6 +170,13 @@ public abstract interface annotation class kotlinx/serialization/UseContextualSe
public abstract fun forClasses ()[Ljava/lang/Class;
}
+public abstract interface annotation class kotlinx/serialization/UseSerialPolymorphicNumbers : java/lang/annotation/Annotation {
+}
+
+public synthetic class kotlinx/serialization/UseSerialPolymorphicNumbers$Impl : kotlinx/serialization/UseSerialPolymorphicNumbers {
+ public fun ()V
+}
+
public abstract interface annotation class kotlinx/serialization/UseSerializers : java/lang/annotation/Annotation {
public abstract fun serializerClasses ()[Ljava/lang/Class;
}
@@ -280,6 +304,9 @@ public abstract interface class kotlinx/serialization/descriptors/SerialDescript
public abstract fun getElementsCount ()I
public abstract fun getKind ()Lkotlinx/serialization/descriptors/SerialKind;
public abstract fun getSerialName ()Ljava/lang/String;
+ public abstract fun getSerialPolymorphicNumberByBaseClass ()Ljava/util/Map;
+ public abstract fun getSerialPolymorphicNumberByBaseClass (Lkotlin/reflect/KClass;)I
+ public abstract fun getUseSerialPolymorphicNumbers ()Z
public abstract fun isElementOptional (I)Z
public abstract fun isInline ()Z
public abstract fun isNullable ()Z
@@ -287,6 +314,9 @@ public abstract interface class kotlinx/serialization/descriptors/SerialDescript
public final class kotlinx/serialization/descriptors/SerialDescriptor$DefaultImpls {
public static fun getAnnotations (Lkotlinx/serialization/descriptors/SerialDescriptor;)Ljava/util/List;
+ public static fun getSerialPolymorphicNumberByBaseClass (Lkotlinx/serialization/descriptors/SerialDescriptor;)Ljava/util/Map;
+ public static fun getSerialPolymorphicNumberByBaseClass (Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlin/reflect/KClass;)I
+ public static fun getUseSerialPolymorphicNumbers (Lkotlinx/serialization/descriptors/SerialDescriptor;)Z
public static fun isInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Z
public static fun isNullable (Lkotlinx/serialization/descriptors/SerialDescriptor;)Z
}
@@ -561,6 +591,7 @@ public abstract class kotlinx/serialization/internal/AbstractPolymorphicSerializ
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun findPolymorphicSerializerOrNull (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/String;)Lkotlinx/serialization/DeserializationStrategy;
public fun findPolymorphicSerializerOrNull (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy;
+ public fun findPolymorphicSerializerWithNumberOrNull (Lkotlinx/serialization/encoding/CompositeDecoder;Ljava/lang/Integer;)Lkotlinx/serialization/DeserializationStrategy;
public abstract fun getBaseClass ()Lkotlin/reflect/KClass;
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}
@@ -958,7 +989,7 @@ public final class kotlinx/serialization/internal/PluginExceptionsKt {
public static final fun throwMissingFieldException (IILkotlinx/serialization/descriptors/SerialDescriptor;)V
}
-public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : kotlinx/serialization/descriptors/SerialDescriptor, kotlinx/serialization/internal/CachedNames {
+public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : kotlinx/serialization/internal/CachedNames {
public fun (Ljava/lang/String;Lkotlinx/serialization/internal/GeneratedSerializer;I)V
public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/internal/GeneratedSerializer;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addElement (Ljava/lang/String;Z)V
@@ -975,8 +1006,6 @@ public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : ko
public fun getSerialNames ()Ljava/util/Set;
public fun hashCode ()I
public fun isElementOptional (I)Z
- public fun isInline ()Z
- public fun isNullable ()Z
public final fun pushAnnotation (Ljava/lang/annotation/Annotation;)V
public final fun pushClassAnnotation (Ljava/lang/annotation/Annotation;)V
public fun toString ()Ljava/lang/String;
@@ -1286,6 +1315,7 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
public final fun buildTo (Lkotlinx/serialization/modules/SerializersModuleBuilder;)V
public final fun default (Lkotlin/jvm/functions/Function1;)V
public final fun defaultDeserializer (Lkotlin/jvm/functions/Function1;)V
+ public final fun defaultDeserializerForNumber (Lkotlin/jvm/functions/Function1;)V
public final fun subclass (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
}
@@ -1296,6 +1326,7 @@ public abstract class kotlinx/serialization/modules/SerializersModule {
public static synthetic fun getContextual$default (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;Ljava/util/List;ILjava/lang/Object;)Lkotlinx/serialization/KSerializer;
public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lkotlinx/serialization/SerializationStrategy;
public abstract fun getPolymorphic (Lkotlin/reflect/KClass;Ljava/lang/String;)Lkotlinx/serialization/DeserializationStrategy;
+ public abstract fun getPolymorphicWithNumber (Lkotlin/reflect/KClass;Ljava/lang/Integer;)Lkotlinx/serialization/DeserializationStrategy;
}
public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotlinx/serialization/modules/SerializersModuleCollector {
@@ -1307,6 +1338,7 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl
public fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
public fun polymorphicDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
+ public fun polymorphicDefaultDeserializerForNumber (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
}
@@ -1324,6 +1356,7 @@ public abstract interface class kotlinx/serialization/modules/SerializersModuleC
public abstract fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
public abstract fun polymorphicDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public abstract fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
+ public abstract fun polymorphicDefaultDeserializerForNumber (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
public abstract fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
}
From 4973f071d84d8119008fc8facf3db43858305d0b Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Sat, 2 Mar 2024 22:52:55 +0800
Subject: [PATCH 11/12] Review all changes since commit
41c0bb10bcb04bb8c72828b0b5fe63ab85a348f5, improving some code and comments
and reverting some unnecessary changes
---
core/commonMain/src/kotlinx/serialization/Annotations.kt | 2 +-
.../src/kotlinx/serialization/SealedSerializer.kt | 1 -
.../serialization/internal/AbstractPolymorphicSerializer.kt | 6 +++---
.../serialization/modules/SerializersModuleBuilders.kt | 4 ++--
4 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index 65fabaadc4..cf99594f11 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -153,7 +153,7 @@ public annotation class Serializer(
public annotation class SerialName(val value: String)
/**
- * Requires all subclasses to use [SerialPolymorphicNumber].
+ * Requires all subclasses marked with this annotation to use [SerialPolymorphicNumber].
*/
@SerialInfo
@Target(AnnotationTarget.CLASS)
diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
index 82452d866e..ccb14c1dba 100644
--- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
@@ -127,7 +127,6 @@ public class SealedClassSerializer(
// Plugin should produce identical serializers, although they are not always strictly equal (e.g. new ObjectSerializer
// may be created every time)
class2Serializer = subclasses.zip(subclassSerializers).toMap()
-
serialName2Serializer = class2Serializer.entries.groupingBy { it.value.descriptor.serialName }
.aggregate, KSerializer>, String, Map.Entry, KSerializer>>
{ key, accumulator, element, _ ->
diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
index 8940b03e20..bb7f0b2f1f 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -143,11 +143,11 @@ internal fun throwSubtypeNotRegistered(serialPolymorphicNumber: Int?, baseClass:
throw SerializationException(
(
if (serialPolymorphicNumber == null)
- "Class discriminator serial polymorphic number was missing and no default serializers were registered $scope."
+ "Class discriminator (serial polymorphic number) was missing and no default serializers were registered $scope."
else
- "Serializer for subclass serial polymorphic number '$serialPolymorphicNumber' is not found $scope.\n" +
+ "Serializer for subclass serial polymorphic number '$serialPolymorphicNumber' is not found in $scope.\n" +
"Check if class with serial polymorphic number '$serialPolymorphicNumber' exists and serializer is registered in a corresponding SerializersModule.\n" +
- "To be registered automatically, class annotated with '@SerialPolymorphicNumber($serialPolymorphicNumber)' has to be '@Serializable', and the base class '${baseClass.simpleName}' has to be sealed and '@Serializable'.\n"
+ "To be registered automatically, class annotated with '@SerialPolymorphicNumber($serialPolymorphicNumber)' has to be '@Serializable', and the base class '${baseClass.simpleName}' marked with `@UseSerialPolymorphicNumbers` has to be sealed and '@Serializable'.\n"
) +
"\nRemove the `@UseSerialPolymorphicNumbers` annotation from the base class `${baseClass.simpleName}` if you want to switch back to polymorphic serialization using the serial name strings."
)
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
index e66f11d2e6..f7026ee447 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
@@ -188,7 +188,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
internal fun registerDefaultPolymorphicDeserializer(
baseClass: KClass,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?,
- //defaultDeserializerProvider: PolymorphicDeserializerProvider,
+ //defaultDeserializerProvider: PolymorphicDeserializerProvider, // this causes the build to fail on JS, but only when there is no trailing comment such as this one, which is strange
allowOverwrite: Boolean
) {
val previous = polyBase2DefaultDeserializerProvider[baseClass]
@@ -202,7 +202,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
internal fun registerDefaultPolymorphicDeserializerForNumber(
baseClass: KClass,
defaultDeserializerProvider: (polymorphicSerialNumber: Int?) -> DeserializationStrategy?,
- //defaultDeserializerProvider: PolymorphicDeserializerProviderForNumber,
+ //defaultDeserializerProvider: PolymorphicDeserializerProviderForNumber, // this causes the build to fail on JS, but only when there is no trailing comment such as this one, which is strange
allowOverwrite: Boolean
) {
val previous = polyBase2DefaultDeserializerProviderForNumber[baseClass]
From 23c385cc334f0224666d6d2783b2b938f8991d07 Mon Sep 17 00:00:00 2001
From: Shreck Ye
Date: Wed, 13 Mar 2024 00:22:28 +0800
Subject: [PATCH 12/12] Clarify the misconception in the test for
`defaultDeserializerForNumber` and make it work
See #2598.
---
.../serialization/protobuf/SerialPolymorphicNumberTest.kt | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt
index ba4e9822f5..a014dd7f73 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/SerialPolymorphicNumberTest.kt
@@ -89,7 +89,7 @@ class SerialPolymorphicNumberTest {
object Case : Abstract()
@Serializable
- data class Default(val type: Int?) : Abstract()
+ object Default : Abstract()
}
val protoBuf = ProtoBuf {
@@ -106,7 +106,6 @@ class SerialPolymorphicNumberTest {
@Test
fun testPolymorphicModule() {
testConversion(protoBuf, Abstract.Case, "08011200")
- // TODO Not working. However, even the original default serializer for serial names is not working for Protobuf as tested.
- // assertEquals(Abstract.Default(0), protoBuf.decodeFromHexString