From 53859525c53c1bee06f5526548465beb3a2fb5c7 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 18 Sep 2021 14:52:20 +0100 Subject: [PATCH 1/4] Add polymorphic serializers --- core/api/kotlinx-serialization-core.api | 7 ++ .../serialization/descriptors/ContextAware.kt | 3 +- .../modules/PolymorphicModuleBuilder.kt | 68 +++++++++-- .../modules/SerializersModule.kt | 35 ++++-- .../modules/SerializersModuleBuilders.kt | 47 ++++++-- .../modules/SerializersModuleCollector.kt | 29 ++++- .../modules/ModuleBuildersTest.kt | 6 +- docs/polymorphism.md | 111 +++++++++++++++++- docs/serialization-guide.md | 1 + .../json/internal/PolymorphismValidator.kt | 11 +- .../serialization/PolymorphismTestData.kt | 2 + .../features/PolymorphismTest.kt | 61 ++++++++-- guide/example/example-poly-19.kt | 2 +- guide/example/example-poly-20.kt | 76 ++++++++++++ guide/test/PolymorphismTest.kt | 7 ++ 15 files changed, 414 insertions(+), 52 deletions(-) create mode 100644 guide/example/example-poly-20.kt diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 4d07ae1221..2505d9d1c2 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -1175,6 +1175,8 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder { public fun (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V 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 defaultSerializer (Lkotlin/jvm/functions/Function1;)V public final fun subclass (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V } @@ -1195,6 +1197,8 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl public final fun include (Lkotlinx/serialization/modules/SerializersModule;)V 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 polymorphicDeserializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V + public fun polymorphicSerializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } public final class kotlinx/serialization/modules/SerializersModuleBuildersKt { @@ -1209,10 +1213,13 @@ public abstract interface class kotlinx/serialization/modules/SerializersModuleC public abstract fun contextual (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V 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 polymorphicDeserializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V + public abstract fun polymorphicSerializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } public final class kotlinx/serialization/modules/SerializersModuleCollector$DefaultImpls { public static fun contextual (Lkotlinx/serialization/modules/SerializersModuleCollector;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V + public static fun polymorphicDefault (Lkotlinx/serialization/modules/SerializersModuleCollector;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } public final class kotlinx/serialization/modules/SerializersModuleKt { diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/ContextAware.kt b/core/commonMain/src/kotlinx/serialization/descriptors/ContextAware.kt index f05a5c558b..4a6367f48f 100644 --- a/core/commonMain/src/kotlinx/serialization/descriptors/ContextAware.kt +++ b/core/commonMain/src/kotlinx/serialization/descriptors/ContextAware.kt @@ -63,7 +63,8 @@ public fun SerializersModule.getContextualDescriptor(descriptor: SerialDescripto /** * Retrieves a collection of descriptors which serializers are registered for polymorphic serialization in [this] * with base class equal to [descriptor]'s [SerialDescriptor.capturedKClass]. - * This method does not retrieve serializers registered with [PolymorphicModuleBuilder.default]. + * This method does not retrieve serializers registered with [PolymorphicModuleBuilder.defaultDeserializer] + * or [PolymorphicModuleBuilder.defaultSerializer]. * * @see SerializersModule.getPolymorphic * @see SerializersModuleBuilder.polymorphic diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt index d19dcd6c2a..79685cc87d 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt @@ -20,7 +20,8 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons private val baseSerializer: KSerializer? = null ) { private val subclasses: MutableList, KSerializer>> = mutableListOf() - private var defaultSerializerProvider: ((String?) -> DeserializationStrategy?)? = null + private var defaultSerializerProvider: ((Base) -> SerializationStrategy?)? = null + private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy?)? = null /** * Registers a [subclass] [serializer] in the resulting module under the [base class][Base]. @@ -29,26 +30,66 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons subclasses.add(subclass to serializer) } + /** + * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. + * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found. + * + * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically. + * + * Default serializers provider affects only serialization process. + */ + @Suppress("UNCHECKED_CAST") + public fun defaultSerializer(defaultSerializerProvider: (value: T) -> SerializationStrategy?) { + require(this.defaultSerializerProvider == null) { + "Default serializer provider is already registered for class $baseClass: ${this.defaultSerializerProvider}" + } + this.defaultSerializerProvider = defaultSerializerProvider as ((Base) -> SerializationStrategy?) + } + /** * Adds a default serializers provider associated with the given [baseClass] to the resulting module. - * [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className` + * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` * were found. `className` could be `null` for formats that support nullable class discriminators * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`) * - * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically. + * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. * * Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown * type and have a precise serializer, so the default serializer has limited capabilities. * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer] * or [JsonContentPolymorphicSerializer] classes. * - * Default serializers provider affects only deserialization process. + * Default deserializers provider affects only deserialization process. */ - public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy?) { - require(this.defaultSerializerProvider == null) { - "Default serializer provider is already registered for class $baseClass: ${this.defaultSerializerProvider}" + public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) { + require(this.defaultDeserializerProvider == null) { + "Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}" } - this.defaultSerializerProvider = defaultSerializerProvider + this.defaultDeserializerProvider = defaultDeserializerProvider + } + + /** + * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. + * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` + * were found. `className` could be `null` for formats that support nullable class discriminators + * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`) + * + * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. + * + * Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown + * type and have a precise serializer, so the default serializer has limited capabilities. + * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer] + * or [JsonContentPolymorphicSerializer] classes. + * + * Default deserializers provider affects only deserialization process. + * + * @see defaultDeserializer + */ + @Deprecated("Specify whether it's a default serializer/deserializer", + ReplaceWith("defaultDeserializer(defaultSerializerProvider)") + ) + public fun default(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) { + defaultDeserializer(defaultDeserializerProvider) } @Suppress("UNCHECKED_CAST") @@ -63,9 +104,14 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons ) } - val default = defaultSerializerProvider - if (default != null) { - builder.registerDefaultPolymorphicSerializer(baseClass, default, false) + val defaultSerializer = defaultSerializerProvider + if (defaultSerializer != null) { + builder.registerDefaultPolymorphicSerializer(baseClass, defaultSerializer, false) + } + + val defaultDeserializer = defaultDeserializerProvider + if (defaultDeserializer != null) { + builder.registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializer, false) } } } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index 5d92117317..eb6327e03b 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -71,7 +71,7 @@ public sealed class SerializersModule { */ @SharedImmutable @ExperimentalSerializationApi -public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap()) +public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap()) /** * Returns a combination of two serial modules @@ -113,12 +113,19 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri registerPolymorphicSerializer(baseClass, actualClass, actualSerializer, allowOverwrite = true) } - override fun polymorphicDefault( + override fun polymorphicSerializerDefault( baseClass: KClass, - defaultSerializerProvider: (className: String?) -> DeserializationStrategy? + defaultSerializerProvider: (value: Base) -> SerializationStrategy? ) { registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, allowOverwrite = true) } + + override fun polymorphicDeserializerDefault( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? + ) { + registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true) + } }) } @@ -133,13 +140,18 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri internal class SerialModuleImpl( private val class2ContextualFactory: Map, ContextualProvider>, @JvmField val polyBase2Serializers: Map, Map, KSerializer<*>>>, + private val polyBase2DefaultSerializerProvider: Map, PolymorphicSerializerProvider<*>>, private val polyBase2NamedSerializers: Map, Map>>, - private val polyBase2DefaultProvider: Map, PolymorphicProvider<*>> + private val polyBase2DefaultDeserializerProvider: Map, PolymorphicDeserializerProvider<*>> ) : SerializersModule() { override fun getPolymorphic(baseClass: KClass, value: T): SerializationStrategy? { if (!value.isInstanceOf(baseClass)) return null - return polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy + // Registered + val registered = polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy + if (registered != null) return registered + // Default + return (polyBase2DefaultSerializerProvider[baseClass] as? PolymorphicSerializerProvider)?.invoke(value) } override fun getPolymorphic(baseClass: KClass, serializedClassName: String?): DeserializationStrategy? { @@ -147,7 +159,7 @@ internal class SerialModuleImpl( val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer if (registered != null) return registered // Default - return (polyBase2DefaultProvider[baseClass] as? PolymorphicProvider)?.invoke(serializedClassName) + return (polyBase2DefaultDeserializerProvider[baseClass] as? PolymorphicDeserializerProvider)?.invoke(serializedClassName) } override fun getContextual(kClass: KClass, typeArgumentsSerializers: List>): KSerializer? { @@ -175,13 +187,18 @@ internal class SerialModuleImpl( } } - polyBase2DefaultProvider.forEach { (baseClass, provider) -> - collector.polymorphicDefault(baseClass as KClass, provider as (PolymorphicProvider)) + polyBase2DefaultSerializerProvider.forEach { (baseClass, provider) -> + collector.polymorphicSerializerDefault(baseClass as KClass, provider as (PolymorphicSerializerProvider)) + } + + polyBase2DefaultDeserializerProvider.forEach { (baseClass, provider) -> + collector.polymorphicDeserializerDefault(baseClass as KClass, provider as (PolymorphicDeserializerProvider)) } } } -internal typealias PolymorphicProvider = (className: String?) -> DeserializationStrategy? +internal typealias PolymorphicDeserializerProvider = (className: String?) -> 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 f81f27fc1b..13f4370ff8 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -40,8 +40,9 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() - public class SerializersModuleBuilder @PublishedApi internal constructor() : SerializersModuleCollector { private val class2ContextualProvider: MutableMap, ContextualProvider> = hashMapOf() private val polyBase2Serializers: MutableMap, MutableMap, KSerializer<*>>> = hashMapOf() + private val polyBase2DefaultSerializerProvider: MutableMap, PolymorphicSerializerProvider<*>> = hashMapOf() private val polyBase2NamedSerializers: MutableMap, MutableMap>> = hashMapOf() - private val polyBase2DefaultProvider: MutableMap, PolymorphicProvider<*>> = hashMapOf() + private val polyBase2DefaultDeserializerProvider: MutableMap, PolymorphicDeserializerProvider<*>> = hashMapOf() /** * Adds [serializer] associated with given [kClass] for contextual serialization. @@ -91,17 +92,30 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser /** * Adds a default serializers provider associated with the given [baseClass] to the resulting module. - * [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className` + * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found. + * + * @see PolymorphicModuleBuilder.defaultSerializer + */ + public override fun polymorphicSerializerDefault( + baseClass: KClass, + defaultSerializerProvider: (value: Base) -> SerializationStrategy? + ) { + registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, false) + } + + /** + * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. + * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` * were found. `className` could be `null` for formats that support nullable class discriminators * (currently only `Json` with `useArrayPolymorphism` set to `false`) * - * @see PolymorphicModuleBuilder.default + * @see PolymorphicModuleBuilder.defaultDeserializer */ - public override fun polymorphicDefault( + public override fun polymorphicDeserializerDefault( baseClass: KClass, - defaultSerializerProvider: (className: String?) -> DeserializationStrategy? + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? ) { - registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, false) + registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false) } /** @@ -132,14 +146,27 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser @JvmName("registerDefaultPolymorphicSerializer") // Don't mangle method name for prettier stack traces internal fun registerDefaultPolymorphicSerializer( baseClass: KClass, - defaultSerializerProvider: (className: String?) -> DeserializationStrategy?, + defaultSerializerProvider: (value: Base) -> SerializationStrategy?, allowOverwrite: Boolean ) { - val previous = polyBase2DefaultProvider[baseClass] + val previous = polyBase2DefaultDeserializerProvider[baseClass] if (previous != null && previous != defaultSerializerProvider && !allowOverwrite) { throw IllegalArgumentException("Default serializers provider for class $baseClass is already registered: $previous") } - polyBase2DefaultProvider[baseClass] = defaultSerializerProvider + polyBase2DefaultSerializerProvider[baseClass] = defaultSerializerProvider + } + + @JvmName("registerDefaultPolymorphicDeserializer") // Don't mangle method name for prettier stack traces + internal fun registerDefaultPolymorphicDeserializer( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?, + allowOverwrite: Boolean + ) { + val previous = polyBase2DefaultDeserializerProvider[baseClass] + if (previous != null && previous != defaultDeserializerProvider && !allowOverwrite) { + throw IllegalArgumentException("Default deserializers provider for class $baseClass is already registered: $previous") + } + polyBase2DefaultDeserializerProvider[baseClass] = defaultDeserializerProvider } @JvmName("registerPolymorphicSerializer") // Don't mangle method name for prettier stack traces @@ -188,7 +215,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser @PublishedApi internal fun build(): SerializersModule = - SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2NamedSerializers, polyBase2DefaultProvider) + SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2DefaultSerializerProvider, polyBase2NamedSerializers, polyBase2DefaultDeserializerProvider) } /** diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt index 10ebc3b1f8..6097b7ffd0 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt @@ -48,11 +48,32 @@ public interface SerializersModuleCollector { /** * Accept a default serializer provider, associated with the [baseClass] for polymorphic serialization. * - * @see SerializersModuleBuilder.polymorphicDefault - * @see PolymorphicModuleBuilder.default + * @see SerializersModuleBuilder.polymorphicSerializerDefault + * @see PolymorphicModuleBuilder.defaultSerializer */ - public fun polymorphicDefault( + public fun polymorphicSerializerDefault( baseClass: KClass, - defaultSerializerProvider: (className: String?) -> DeserializationStrategy? + defaultSerializerProvider: (value: Base) -> SerializationStrategy? ) + + /** + * Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization. + * + * @see SerializersModuleBuilder.polymorphicDeserializerDefault + * @see PolymorphicModuleBuilder.defaultDeserializer + */ + public fun polymorphicDeserializerDefault( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? + ) + + @Deprecated("Specify whether using deserializer or serializer", + ReplaceWith("polymorphicDeserializerDefault(baseClass, defaultSerializerProvider)") + ) + public fun polymorphicDefault( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? + ) { + polymorphicDeserializerDefault(baseClass, defaultDeserializerProvider) + } } diff --git a/core/commonTest/src/kotlinx/serialization/modules/ModuleBuildersTest.kt b/core/commonTest/src/kotlinx/serialization/modules/ModuleBuildersTest.kt index 708cc25e46..edbd9d6f4e 100644 --- a/core/commonTest/src/kotlinx/serialization/modules/ModuleBuildersTest.kt +++ b/core/commonTest/src/kotlinx/serialization/modules/ModuleBuildersTest.kt @@ -279,13 +279,13 @@ class ModuleBuildersTest { fun testPolymorphicCollision() { val m1 = SerializersModule { polymorphic(Any::class) { - default { _ -> Unit.serializer() } + defaultDeserializer { _ -> Unit.serializer() } } } val m2 = SerializersModule { polymorphic(Any::class) { - default { _ -> Unit.serializer() } + defaultDeserializer { _ -> Unit.serializer() } } } @@ -297,7 +297,7 @@ class ModuleBuildersTest { val defaultSerializerProvider = { _: String? -> Unit.serializer() } val m1 = SerializersModule { polymorphic(Any::class) { - default(defaultSerializerProvider) + defaultDeserializer(defaultSerializerProvider) } } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 7d194f2ad8..09a56bfb3f 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -26,6 +26,7 @@ In this chapter we'll see how Kotlin Serialization deals with polymorphic class * [Polymorphism and generic classes](#polymorphism-and-generic-classes) * [Merging library serializers modules](#merging-library-serializers-modules) * [Default polymorphic type handler for deserialization](#default-polymorphic-type-handler-for-deserialization) + * [Default polymorphic type handler for serialization](#default-polymorphic-type-handler-for-serialization) @@ -854,7 +855,7 @@ data class BasicProject(override val name: String, val type: String): Project() data class OwnedProject(override val name: String, val owner: String) : Project() ``` -We register a default handler using the [`default`][PolymorphicModuleBuilder.default] function in +We register a default deserializer handler using the [`defaultDeserializer`][PolymorphicModuleBuilder.defaultDeserializer] function in the [`polymorphic { ... }`][PolymorphicModuleBuilder] DSL that defines a strategy which maps the `type` string from the input to the [deserialization strategy][DeserializationStrategy]. In the below example we don't use the type, but always return the [Plugin-generated serializer](serializers.md#plugin-generated-serializer) @@ -864,7 +865,7 @@ of the `BasicProject` class. val module = SerializersModule { polymorphic(Project::class) { subclass(OwnedProject::class) - default { BasicProject.serializer() } + defaultDeserializer { BasicProject.serializer() } } } ``` @@ -895,11 +896,111 @@ Notice, how `BasicProject` had also captured the specified type key in its `type -We used a plugin-generated serializer as a default serializer, implying that +We used a plugin-generated serializer as a default serializer, implying that the structure of the "unknown" data is known in advance. In a real-world API it's rarely the case. For that purpose a custom, less-structured serializer is needed. You will see the example of such serializer in the future section on [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attributes). +### Default polymorphic type handler for serialization + +Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you +don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can use serializer defaults. + + + +```kotlin +interface Animal { +} + +interface Cat : Animal { + val catType: String +} + +interface Dog : Animal { + val dogType: String +} + +private class CatImpl : Cat { + override val catType: String = "Tabby" +} + +private class DogImpl : Dog { + override val dogType: String = "Husky" +} + +object AnimalProvider { + fun createCat(): Cat = CatImpl() + fun createDog(): Dog = DogImpl() +} +``` + +We register a default serializer handler using the [`defaultSerializer`][PolymorphicModuleBuilder.defaultSerializer] function in +the [`polymorphic { ... }`][PolymorphicModuleBuilder] DSL that defines a strategy which takes an instance of the base class and +provides a [serialization strategy][SerializationStrategy]. In the below example we use a `when` block to check the type of the +instance, without ever having to refer to the private implementation classes. + +```kotlin +val module = SerializersModule { + polymorphic(Animal::class) { + @Suppress("UNCHECKED_CAST") + defaultSerializer { instance: Animal -> + when (instance) { + is Cat -> CatSerializer as SerializationStrategy + is Dog -> DogSerializer as SerializationStrategy + else -> null + } + } + } +} + +object CatSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Cat") { + element("catType") + } + + override fun serialize(encoder: Encoder, value: Cat) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.catType) + } + } +} + +object DogSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Dog") { + element("dogType") + } + + override fun serialize(encoder: Encoder, value: Dog) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.dogType) + } + } +} +``` + +Using this module we can now serialize instances of `Cat` and `Dog`. + +```kotlin +val format = Json { serializersModule = module } + +fun main() { + println(format.encodeToString(AnimalProvider.createCat())) +} +``` + +> You can get the full code [here](../guide/example/example-poly-20.kt) + +```text +{"type":"Cat","catType":"Tabby"} +``` + + + + --- The next chapter covers [JSON features](json.md). @@ -911,6 +1012,7 @@ The next chapter covers [JSON features](json.md). [Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html [Polymorphic]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization/-polymorphic/index.html [DeserializationStrategy]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html +[SerializationStrategy]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html [SerializersModule]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html [SerializersModule()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html @@ -918,8 +1020,9 @@ The next chapter covers [JSON features](json.md). [subclass]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html [plus]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/plus.html [SerializersModuleBuilder.include]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/include.html -[PolymorphicModuleBuilder.default]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default.html +[PolymorphicModuleBuilder.defaultDeserializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default-deserializer.html [PolymorphicModuleBuilder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/index.html +[PolymorphicModuleBuilder.defaultSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default-serializer.html [Json.encodeToString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md index 0563391cdf..ef65696e95 100644 --- a/docs/serialization-guide.md +++ b/docs/serialization-guide.md @@ -99,6 +99,7 @@ Once the project is set up, we can start serializing some classes. * [Polymorphism and generic classes](polymorphism.md#polymorphism-and-generic-classes) * [Merging library serializers modules](polymorphism.md#merging-library-serializers-modules) * [Default polymorphic type handler for deserialization](polymorphism.md#default-polymorphic-type-handler-for-deserialization) + * [Default polymorphic type handler for serialization](polymorphism.md#default-polymorphic-type-handler-for-serialization) **Chapter 5.** [JSON Features](json.md) 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 04cdb045b3..63071e0c0e 100644 --- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt +++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt @@ -74,9 +74,16 @@ internal class PolymorphismValidator( } } - override fun polymorphicDefault( + override fun polymorphicSerializerDefault( baseClass: KClass, - defaultSerializerProvider: (className: String?) -> DeserializationStrategy? + defaultSerializerProvider: (value: Base) -> SerializationStrategy? + ) { + // Nothing here + } + + override fun polymorphicDeserializerDefault( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? ) { // Nothing here } diff --git a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt index 5aaf628b7f..6d4d42f2a8 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt @@ -32,6 +32,8 @@ open class PolyBase(val id: Int) { @Serializable data class PolyDefault(val json: JsonElement) : PolyBase(-1) +class PolyDefaultWithId(id: Int) : PolyBase(id) + @Serializable data class PolyDerived(val s: String) : PolyBase(1) diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt b/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt index 253905a3c8..1ba3627b53 100644 --- a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt +++ b/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt @@ -5,6 +5,8 @@ package kotlinx.serialization.features import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import kotlinx.serialization.json.* import kotlinx.serialization.modules.* import kotlinx.serialization.test.* @@ -47,19 +49,43 @@ class PolymorphismTest : JsonTestBase() { assertEquals("""["kotlinx.serialization.PolyDerived",{"id":1,"s":"b"}]""", s) } - object PolyDefaultSerializer : JsonTransformingSerializer(PolyDefault.serializer()) { + object PolyDefaultDeserializer : JsonTransformingSerializer(PolyDefault.serializer()) { override fun transformDeserialize(element: JsonElement): JsonElement = buildJsonObject { put("json", JsonObject(element.jsonObject.filterKeys { it != "type" })) put("id", 42) } } + object EvenDefaultSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("even") { + element("parity") + } + + override fun serialize(encoder: Encoder, value: PolyBase) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, "even") + } + } + } + + object OddDefaultSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("odd") { + element("parity") + } + + override fun serialize(encoder: Encoder, value: PolyBase) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, "odd") + } + } + } + @Test - fun testDefaultSerializer() = parametrizedTest { jsonTestingMode -> + fun testDefaultDeserializer() = parametrizedTest { jsonTestingMode -> val withDefault = module + SerializersModule { - polymorphicDefault(PolyBase::class) { name -> + polymorphicDeserializerDefault(PolyBase::class) { name -> if (name == "foo") { - PolyDefaultSerializer + PolyDefaultDeserializer } else { null } @@ -78,12 +104,12 @@ class PolymorphismTest : JsonTestBase() { } @Test - fun testDefaultSerializerForMissingDiscriminator() = parametrizedTest { jsonTestingMode -> + fun testDefaultDeserializerForMissingDiscriminator() = parametrizedTest { jsonTestingMode -> val json = Json { serializersModule = module + SerializersModule { - polymorphicDefault(PolyBase::class) { name -> + polymorphicDeserializerDefault(PolyBase::class) { name -> if (name == null) { - PolyDefaultSerializer + PolyDefaultDeserializer } else { null } @@ -96,4 +122,25 @@ class PolymorphismTest : JsonTestBase() { val result = json.decodeFromString(Wrapper.serializer(), string, jsonTestingMode) assertEquals(Wrapper(PolyBase(239), PolyDefault(JsonObject(mapOf("key" to JsonPrimitive(42))))), result) } + + @Test + fun testDefaultSerializer() = parametrizedTest { jsonTestingMode -> + val json = Json { + serializersModule = module + SerializersModule { + polymorphicSerializerDefault(PolyBase::class) { value -> + if (value.id % 2 == 0) { + EvenDefaultSerializer + } else { + OddDefaultSerializer + } + } + } + } + val obj = Wrapper( + PolyDefaultWithId(0), + PolyDefaultWithId(1) + ) + val s = json.encodeToString(Wrapper.serializer(), obj, jsonTestingMode) + assertEquals("""{"polyBase1":{"type":"even","parity":"even"},"polyBase2":{"type":"odd","parity":"odd"}}""", s) + } } diff --git a/guide/example/example-poly-19.kt b/guide/example/example-poly-19.kt index b3ce738c62..83677ebe47 100644 --- a/guide/example/example-poly-19.kt +++ b/guide/example/example-poly-19.kt @@ -21,7 +21,7 @@ data class OwnedProject(override val name: String, val owner: String) : Project( val module = SerializersModule { polymorphic(Project::class) { subclass(OwnedProject::class) - default { BasicProject.serializer() } + defaultDeserializer { BasicProject.serializer() } } } diff --git a/guide/example/example-poly-20.kt b/guide/example/example-poly-20.kt new file mode 100644 index 0000000000..ff1ba3b861 --- /dev/null +++ b/guide/example/example-poly-20.kt @@ -0,0 +1,76 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +package example.examplePoly20 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* + +interface Animal { +} + +interface Cat : Animal { + val catType: String +} + +interface Dog : Animal { + val dogType: String +} + +private class CatImpl : Cat { + override val catType: String = "Tabby" +} + +private class DogImpl : Dog { + override val dogType: String = "Husky" +} + +object AnimalProvider { + fun createCat(): Cat = CatImpl() + fun createDog(): Dog = DogImpl() +} + +val module = SerializersModule { + polymorphic(Animal::class) { + @Suppress("UNCHECKED_CAST") + defaultSerializer { instance: Animal -> + when (instance) { + is Cat -> CatSerializer as SerializationStrategy + is Dog -> DogSerializer as SerializationStrategy + else -> null + } + } + } +} + +object CatSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Cat") { + element("catType") + } + + override fun serialize(encoder: Encoder, value: Cat) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.catType) + } + } +} + +object DogSerializer : SerializationStrategy { + override val descriptor = buildClassSerialDescriptor("Dog") { + element("dogType") + } + + override fun serialize(encoder: Encoder, value: Dog) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.dogType) + } + } +} + +val format = Json { serializersModule = module } + +fun main() { + println(format.encodeToString(AnimalProvider.createCat())) +} diff --git a/guide/test/PolymorphismTest.kt b/guide/test/PolymorphismTest.kt index ae43ed8d86..e82dd6892e 100644 --- a/guide/test/PolymorphismTest.kt +++ b/guide/test/PolymorphismTest.kt @@ -142,4 +142,11 @@ class PolymorphismTest { "[BasicProject(name=example, type=unknown), OwnedProject(name=kotlinx.serialization, owner=kotlin)]" ) } + + @Test + fun testExamplePoly20() { + captureOutput("ExamplePoly20") { example.examplePoly20.main() }.verifyOutputLines( + "{\"type\":\"Cat\",\"catType\":\"Tabby\"}" + ) + } } From d0bd40528ae1f5a7d23a1e290a99939a7d997622 Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 11 Oct 2021 16:54:27 +0100 Subject: [PATCH 2/4] Address review comments --- core/api/kotlinx-serialization-core.api | 8 +++--- .../modules/PolymorphicModuleBuilder.kt | 11 ++++---- .../modules/SerializersModule.kt | 8 +++--- .../modules/SerializersModuleBuilders.kt | 12 ++++++-- .../modules/SerializersModuleCollector.kt | 28 +++++++++++++------ docs/polymorphism.md | 2 +- .../json/internal/PolymorphismValidator.kt | 4 +-- .../features/PolymorphismTest.kt | 6 ++-- 8 files changed, 49 insertions(+), 30 deletions(-) diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 2505d9d1c2..c110dc9fd3 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -1197,8 +1197,8 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl public final fun include (Lkotlinx/serialization/modules/SerializersModule;)V 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 polymorphicDeserializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public fun polymorphicSerializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V + public fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V + public fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } public final class kotlinx/serialization/modules/SerializersModuleBuildersKt { @@ -1213,8 +1213,8 @@ public abstract interface class kotlinx/serialization/modules/SerializersModuleC public abstract fun contextual (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V 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 polymorphicDeserializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public abstract fun polymorphicSerializerDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V + public abstract fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V + public abstract fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V } public final class kotlinx/serialization/modules/SerializersModuleCollector$DefaultImpls { diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt index 79685cc87d..60849b3e62 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt @@ -38,12 +38,13 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons * * Default serializers provider affects only serialization process. */ + @ExperimentalSerializationApi @Suppress("UNCHECKED_CAST") - public fun defaultSerializer(defaultSerializerProvider: (value: T) -> SerializationStrategy?) { + public fun defaultSerializer(defaultSerializerProvider: (value: @UnsafeVariance Base) -> SerializationStrategy<@UnsafeVariance Base>?) { require(this.defaultSerializerProvider == null) { "Default serializer provider is already registered for class $baseClass: ${this.defaultSerializerProvider}" } - this.defaultSerializerProvider = defaultSerializerProvider as ((Base) -> SerializationStrategy?) + this.defaultSerializerProvider = defaultSerializerProvider } /** @@ -61,6 +62,7 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons * * Default deserializers provider affects only deserialization process. */ + @ExperimentalSerializationApi public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) { require(this.defaultDeserializerProvider == null) { "Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}" @@ -85,9 +87,8 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons * * @see defaultDeserializer */ - @Deprecated("Specify whether it's a default serializer/deserializer", - ReplaceWith("defaultDeserializer(defaultSerializerProvider)") - ) + @OptIn(ExperimentalSerializationApi::class) + // TODO: deprecate in 1.4 public fun default(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) { defaultDeserializer(defaultDeserializerProvider) } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt index eb6327e03b..86f66d7afd 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt @@ -113,14 +113,14 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri registerPolymorphicSerializer(baseClass, actualClass, actualSerializer, allowOverwrite = true) } - override fun polymorphicSerializerDefault( + override fun polymorphicDefaultSerializer( baseClass: KClass, defaultSerializerProvider: (value: Base) -> SerializationStrategy? ) { registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, allowOverwrite = true) } - override fun polymorphicDeserializerDefault( + override fun polymorphicDefaultDeserializer( baseClass: KClass, defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? ) { @@ -188,11 +188,11 @@ internal class SerialModuleImpl( } polyBase2DefaultSerializerProvider.forEach { (baseClass, provider) -> - collector.polymorphicSerializerDefault(baseClass as KClass, provider as (PolymorphicSerializerProvider)) + collector.polymorphicDefaultSerializer(baseClass as KClass, provider as (PolymorphicSerializerProvider)) } polyBase2DefaultDeserializerProvider.forEach { (baseClass, provider) -> - collector.polymorphicDeserializerDefault(baseClass as KClass, provider as (PolymorphicDeserializerProvider)) + collector.polymorphicDefaultDeserializer(baseClass as KClass, provider as (PolymorphicDeserializerProvider)) } } } diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 13f4370ff8..36102862eb 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -94,9 +94,12 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser * Adds a default serializers provider associated with the given [baseClass] to the resulting module. * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found. * + * This will not affect deserialization. + * * @see PolymorphicModuleBuilder.defaultSerializer */ - public override fun polymorphicSerializerDefault( + @ExperimentalSerializationApi + public override fun polymorphicDefaultSerializer( baseClass: KClass, defaultSerializerProvider: (value: Base) -> SerializationStrategy? ) { @@ -107,11 +110,14 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` * were found. `className` could be `null` for formats that support nullable class discriminators - * (currently only `Json` with `useArrayPolymorphism` set to `false`) + * (currently only `Json` with `useArrayPolymorphism` set to `false`). + * + * This will not affect serialization. * * @see PolymorphicModuleBuilder.defaultDeserializer */ - public override fun polymorphicDeserializerDefault( + @ExperimentalSerializationApi + public override fun polymorphicDefaultDeserializer( baseClass: KClass, defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? ) { diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt index 6097b7ffd0..c4af77f85a 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt @@ -48,10 +48,13 @@ public interface SerializersModuleCollector { /** * Accept a default serializer provider, associated with the [baseClass] for polymorphic serialization. * - * @see SerializersModuleBuilder.polymorphicSerializerDefault + * This will not affect deserialization. + * + * @see SerializersModuleBuilder.polymorphicDefaultSerializer * @see PolymorphicModuleBuilder.defaultSerializer */ - public fun polymorphicSerializerDefault( + @ExperimentalSerializationApi + public fun polymorphicDefaultSerializer( baseClass: KClass, defaultSerializerProvider: (value: Base) -> SerializationStrategy? ) @@ -59,21 +62,30 @@ public interface SerializersModuleCollector { /** * Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization. * - * @see SerializersModuleBuilder.polymorphicDeserializerDefault + * This will not affect serialization. + * + * @see SerializersModuleBuilder.polymorphicDefaultDeserializer * @see PolymorphicModuleBuilder.defaultDeserializer */ - public fun polymorphicDeserializerDefault( + @ExperimentalSerializationApi + public fun polymorphicDefaultDeserializer( baseClass: KClass, defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? ) - @Deprecated("Specify whether using deserializer or serializer", - ReplaceWith("polymorphicDeserializerDefault(baseClass, defaultSerializerProvider)") - ) + /** + * Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization. + * + * This will not affect serialization. + * + * @see SerializersModuleBuilder.polymorphicDefaultDeserializer + * @see PolymorphicModuleBuilder.defaultDeserializer + */ + // TODO: deprecate in 1.4 public fun polymorphicDefault( baseClass: KClass, defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? ) { - polymorphicDeserializerDefault(baseClass, defaultDeserializerProvider) + polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider) } } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 09a56bfb3f..96e2792737 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -904,7 +904,7 @@ on [Maintaining custom JSON attributes](json.md#maintaining-custom-json-attribut ### Default polymorphic type handler for serialization Sometimes you need to dynamically choose which serializer to use for a polymorphic type based on the instance, for example if you -don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can use serializer defaults. +don't have access to the full type hierarchy, or if it changes a lot. For this situation, you can register a default serializer. [Json.encodeToString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html diff --git a/guide/example/example-poly-20.kt b/guide/example/example-poly-20.kt index ff1ba3b861..b597fbeb3d 100644 --- a/guide/example/example-poly-20.kt +++ b/guide/example/example-poly-20.kt @@ -33,14 +33,12 @@ object AnimalProvider { } val module = SerializersModule { - polymorphic(Animal::class) { + polymorphicDefaultSerializer(Animal::class) { instance -> @Suppress("UNCHECKED_CAST") - defaultSerializer { instance: Animal -> - when (instance) { - is Cat -> CatSerializer as SerializationStrategy - is Dog -> DogSerializer as SerializationStrategy - else -> null - } + when (instance) { + is Cat -> CatSerializer as SerializationStrategy + is Dog -> DogSerializer as SerializationStrategy + else -> null } } } From 5bed0dbe0d8c414754778bdbcb7cc9969285c5d8 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 12 Nov 2021 19:38:34 +0000 Subject: [PATCH 4/4] Address review comments --- .../modules/PolymorphicModuleBuilder.kt | 13 ++++++++----- .../modules/SerializersModuleBuilders.kt | 2 -- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt index 546940677e..31ce457409 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt @@ -55,25 +55,28 @@ public class PolymorphicModuleBuilder @PublishedApi internal cons /** * Adds a default deserializers provider associated with the given [baseClass] to the resulting module. - * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className` + * [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className` * were found. `className` could be `null` for formats that support nullable class discriminators * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`) * - * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically. + * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically. + * + * [defaultSerializerProvider] is named as such for backwards compatibility reasons; it provides deserializers. * * Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown * type and have a precise serializer, so the default serializer has limited capabilities. * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer] * or [JsonContentPolymorphicSerializer] classes. * - * Default deserializers provider affects only deserialization process. + * Default deserializers provider affects only deserialization process. To affect serialization process, use + * [SerializersModuleBuilder.polymorphicDefaultSerializer]. * * @see defaultDeserializer */ @OptIn(ExperimentalSerializationApi::class) // TODO: deprecate in 1.4 - public fun default(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy?) { - defaultDeserializer(defaultDeserializerProvider) + public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy?) { + defaultDeserializer(defaultSerializerProvider) } @Suppress("UNCHECKED_CAST") diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt index 36102862eb..530f42647d 100644 --- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt +++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt @@ -95,8 +95,6 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found. * * This will not affect deserialization. - * - * @see PolymorphicModuleBuilder.defaultSerializer */ @ExperimentalSerializationApi public override fun polymorphicDefaultSerializer(