Skip to content

Commit

Permalink
Add polymorphic default serializers (as opposed to deserializers) (#1686
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Earthcomputer authored Nov 22, 2021
1 parent 4c87a43 commit 4597602
Show file tree
Hide file tree
Showing 15 changed files with 412 additions and 51 deletions.
6 changes: 6 additions & 0 deletions core/api/kotlinx-serialization-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,7 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
public fun <init> (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 subclass (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
}

Expand All @@ -1197,6 +1198,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 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 {
Expand All @@ -1211,10 +1214,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 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 {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
private val baseSerializer: KSerializer<Base>? = null
) {
private val subclasses: MutableList<Pair<KClass<out Base>, KSerializer<out Base>>> = mutableListOf()
private var defaultSerializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null
private var defaultSerializerProvider: ((Base) -> SerializationStrategy<Base>?)? = null
private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null

/**
* Registers a [subclass] [serializer] in the resulting module under the [base class][Base].
Expand All @@ -31,24 +32,51 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons

/**
* 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`
* 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.
*/
@ExperimentalSerializationApi
public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
require(this.defaultDeserializerProvider == null) {
"Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}"
}
this.defaultDeserializerProvider = defaultDeserializerProvider
}

/**
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
* [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`)
*
* [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 serializers 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(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
require(this.defaultSerializerProvider == null) {
"Default serializer provider is already registered for class $baseClass: ${this.defaultSerializerProvider}"
}
this.defaultSerializerProvider = defaultSerializerProvider
defaultDeserializer(defaultSerializerProvider)
}

@Suppress("UNCHECKED_CAST")
Expand All @@ -63,9 +91,14 @@ public class PolymorphicModuleBuilder<in Base : Any> @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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -113,12 +113,19 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri
registerPolymorphicSerializer(baseClass, actualClass, actualSerializer, allowOverwrite = true)
}

override fun <Base : Any> polymorphicDefault(
override fun <Base : Any> polymorphicDefaultSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
) {
registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, allowOverwrite = true)
}

override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true)
}
})
}

Expand All @@ -133,21 +140,26 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri
internal class SerialModuleImpl(
private val class2ContextualFactory: Map<KClass<*>, ContextualProvider>,
@JvmField val polyBase2Serializers: Map<KClass<*>, Map<KClass<*>, KSerializer<*>>>,
private val polyBase2DefaultSerializerProvider: Map<KClass<*>, PolymorphicSerializerProvider<*>>,
private val polyBase2NamedSerializers: Map<KClass<*>, Map<String, KSerializer<*>>>,
private val polyBase2DefaultProvider: Map<KClass<*>, PolymorphicProvider<*>>
private val polyBase2DefaultDeserializerProvider: Map<KClass<*>, PolymorphicDeserializerProvider<*>>
) : SerializersModule() {

override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>? {
if (!value.isInstanceOf(baseClass)) return null
return polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy<T>
// Registered
val registered = polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy<T>
if (registered != null) return registered
// Default
return (polyBase2DefaultSerializerProvider[baseClass] as? PolymorphicSerializerProvider<T>)?.invoke(value)
}

override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>? {
// Registered
val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer<out T>
if (registered != null) return registered
// Default
return (polyBase2DefaultProvider[baseClass] as? PolymorphicProvider<T>)?.invoke(serializedClassName)
return (polyBase2DefaultDeserializerProvider[baseClass] as? PolymorphicDeserializerProvider<T>)?.invoke(serializedClassName)
}

override fun <T : Any> getContextual(kClass: KClass<T>, typeArgumentsSerializers: List<KSerializer<*>>): KSerializer<T>? {
Expand Down Expand Up @@ -175,13 +187,18 @@ internal class SerialModuleImpl(
}
}

polyBase2DefaultProvider.forEach { (baseClass, provider) ->
collector.polymorphicDefault(baseClass as KClass<Any>, provider as (PolymorphicProvider<out Any>))
polyBase2DefaultSerializerProvider.forEach { (baseClass, provider) ->
collector.polymorphicDefaultSerializer(baseClass as KClass<Any>, provider as (PolymorphicSerializerProvider<Any>))
}

polyBase2DefaultDeserializerProvider.forEach { (baseClass, provider) ->
collector.polymorphicDefaultDeserializer(baseClass as KClass<Any>, provider as (PolymorphicDeserializerProvider<out Any>))
}
}
}

internal typealias PolymorphicProvider<Base> = (className: String?) -> DeserializationStrategy<out Base>?
internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<out Base>?
internal typealias PolymorphicSerializerProvider<Base> = (value: Base) -> SerializationStrategy<Base>?

/** This class is needed to support re-registering the same static (argless) serializers:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() -
public class SerializersModuleBuilder @PublishedApi internal constructor() : SerializersModuleCollector {
private val class2ContextualProvider: MutableMap<KClass<*>, ContextualProvider> = hashMapOf()
private val polyBase2Serializers: MutableMap<KClass<*>, MutableMap<KClass<*>, KSerializer<*>>> = hashMapOf()
private val polyBase2DefaultSerializerProvider: MutableMap<KClass<*>, PolymorphicSerializerProvider<*>> = hashMapOf()
private val polyBase2NamedSerializers: MutableMap<KClass<*>, MutableMap<String, KSerializer<*>>> = hashMapOf()
private val polyBase2DefaultProvider: MutableMap<KClass<*>, PolymorphicProvider<*>> = hashMapOf()
private val polyBase2DefaultDeserializerProvider: MutableMap<KClass<*>, PolymorphicDeserializerProvider<*>> = hashMapOf()

/**
* Adds [serializer] associated with given [kClass] for contextual serialization.
Expand Down Expand Up @@ -91,19 +92,36 @@ 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`
* were found. `className` could be `null` for formats that support nullable class discriminators
* (currently only `Json` with `useArrayPolymorphism` set to `false`)
* [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found.
*
* @see PolymorphicModuleBuilder.default
* This will not affect deserialization.
*/
public override fun <Base : Any> polymorphicDefault(
@ExperimentalSerializationApi
public override fun <Base : Any> polymorphicDefaultSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
) {
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`).
*
* This will not affect serialization.
*
* @see PolymorphicModuleBuilder.defaultDeserializer
*/
@ExperimentalSerializationApi
public override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false)
}

/**
* Copies the content of [module] module into the current builder.
*/
Expand Down Expand Up @@ -132,14 +150,27 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
@JvmName("registerDefaultPolymorphicSerializer") // Don't mangle method name for prettier stack traces
internal fun <Base : Any> registerDefaultPolymorphicSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?,
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 <Base : Any> registerDefaultPolymorphicDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
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
Expand Down Expand Up @@ -188,7 +219,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser

@PublishedApi
internal fun build(): SerializersModule =
SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2NamedSerializers, polyBase2DefaultProvider)
SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2DefaultSerializerProvider, polyBase2NamedSerializers, polyBase2DefaultDeserializerProvider)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,44 @@ public interface SerializersModuleCollector {
/**
* Accept a default serializer provider, associated with the [baseClass] for polymorphic serialization.
*
* @see SerializersModuleBuilder.polymorphicDefault
* @see PolymorphicModuleBuilder.default
* This will not affect deserialization.
*
* @see SerializersModuleBuilder.polymorphicDefaultSerializer
* @see PolymorphicModuleBuilder.defaultSerializer
*/
public fun <Base : Any> polymorphicDefault(
@ExperimentalSerializationApi
public fun <Base : Any> polymorphicDefaultSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
)

/**
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
*
* This will not affect serialization.
*
* @see SerializersModuleBuilder.polymorphicDefaultDeserializer
* @see PolymorphicModuleBuilder.defaultDeserializer
*/
@ExperimentalSerializationApi
public fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
)

/**
* 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 <Base : Any> polymorphicDefault(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
) {
polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)
}
}
Loading

0 comments on commit 4597602

Please sign in to comment.