Skip to content

Commit 4597602

Browse files
Add polymorphic default serializers (as opposed to deserializers) (#1686)
1 parent 4c87a43 commit 4597602

File tree

15 files changed

+412
-51
lines changed

15 files changed

+412
-51
lines changed

core/api/kotlinx-serialization-core.api

+6
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,7 @@ public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
11771177
public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
11781178
public final fun buildTo (Lkotlinx/serialization/modules/SerializersModuleBuilder;)V
11791179
public final fun default (Lkotlin/jvm/functions/Function1;)V
1180+
public final fun defaultDeserializer (Lkotlin/jvm/functions/Function1;)V
11801181
public final fun subclass (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
11811182
}
11821183

@@ -1197,6 +1198,8 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl
11971198
public final fun include (Lkotlinx/serialization/modules/SerializersModule;)V
11981199
public fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
11991200
public fun polymorphicDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
1201+
public fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
1202+
public fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
12001203
}
12011204

12021205
public final class kotlinx/serialization/modules/SerializersModuleBuildersKt {
@@ -1211,10 +1214,13 @@ public abstract interface class kotlinx/serialization/modules/SerializersModuleC
12111214
public abstract fun contextual (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
12121215
public abstract fun polymorphic (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
12131216
public abstract fun polymorphicDefault (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
1217+
public abstract fun polymorphicDefaultDeserializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
1218+
public abstract fun polymorphicDefaultSerializer (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
12141219
}
12151220

12161221
public final class kotlinx/serialization/modules/SerializersModuleCollector$DefaultImpls {
12171222
public static fun contextual (Lkotlinx/serialization/modules/SerializersModuleCollector;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
1223+
public static fun polymorphicDefault (Lkotlinx/serialization/modules/SerializersModuleCollector;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
12181224
}
12191225

12201226
public final class kotlinx/serialization/modules/SerializersModuleKt {

core/commonMain/src/kotlinx/serialization/descriptors/ContextAware.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public fun SerializersModule.getContextualDescriptor(descriptor: SerialDescripto
6363
/**
6464
* Retrieves a collection of descriptors which serializers are registered for polymorphic serialization in [this]
6565
* with base class equal to [descriptor]'s [SerialDescriptor.capturedKClass].
66-
* This method does not retrieve serializers registered with [PolymorphicModuleBuilder.default].
66+
* This method does not retrieve serializers registered with [PolymorphicModuleBuilder.defaultDeserializer]
67+
* or [PolymorphicModuleBuilder.defaultSerializer].
6768
*
6869
* @see SerializersModule.getPolymorphic
6970
* @see SerializersModuleBuilder.polymorphic

core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt

+42-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
2020
private val baseSerializer: KSerializer<Base>? = null
2121
) {
2222
private val subclasses: MutableList<Pair<KClass<out Base>, KSerializer<out Base>>> = mutableListOf()
23-
private var defaultSerializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null
23+
private var defaultSerializerProvider: ((Base) -> SerializationStrategy<Base>?)? = null
24+
private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null
2425

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

3233
/**
3334
* Adds a default serializers provider associated with the given [baseClass] to the resulting module.
35+
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
36+
* were found. `className` could be `null` for formats that support nullable class discriminators
37+
* (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`)
38+
*
39+
* [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
40+
*
41+
* Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown
42+
* type and have a precise serializer, so the default serializer has limited capabilities.
43+
* To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer]
44+
* or [JsonContentPolymorphicSerializer] classes.
45+
*
46+
* Default deserializers provider affects only deserialization process.
47+
*/
48+
@ExperimentalSerializationApi
49+
public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
50+
require(this.defaultDeserializerProvider == null) {
51+
"Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}"
52+
}
53+
this.defaultDeserializerProvider = defaultDeserializerProvider
54+
}
55+
56+
/**
57+
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
3458
* [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className`
3559
* were found. `className` could be `null` for formats that support nullable class discriminators
3660
* (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`)
3761
*
3862
* [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
3963
*
64+
* [defaultSerializerProvider] is named as such for backwards compatibility reasons; it provides deserializers.
65+
*
4066
* Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown
4167
* type and have a precise serializer, so the default serializer has limited capabilities.
4268
* To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer]
4369
* or [JsonContentPolymorphicSerializer] classes.
4470
*
45-
* Default serializers provider affects only deserialization process.
71+
* Default deserializers provider affects only deserialization process. To affect serialization process, use
72+
* [SerializersModuleBuilder.polymorphicDefaultSerializer].
73+
*
74+
* @see defaultDeserializer
4675
*/
76+
@OptIn(ExperimentalSerializationApi::class)
77+
// TODO: deprecate in 1.4
4778
public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
48-
require(this.defaultSerializerProvider == null) {
49-
"Default serializer provider is already registered for class $baseClass: ${this.defaultSerializerProvider}"
50-
}
51-
this.defaultSerializerProvider = defaultSerializerProvider
79+
defaultDeserializer(defaultSerializerProvider)
5280
}
5381

5482
@Suppress("UNCHECKED_CAST")
@@ -63,9 +91,14 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
6391
)
6492
}
6593

66-
val default = defaultSerializerProvider
67-
if (default != null) {
68-
builder.registerDefaultPolymorphicSerializer(baseClass, default, false)
94+
val defaultSerializer = defaultSerializerProvider
95+
if (defaultSerializer != null) {
96+
builder.registerDefaultPolymorphicSerializer(baseClass, defaultSerializer, false)
97+
}
98+
99+
val defaultDeserializer = defaultDeserializerProvider
100+
if (defaultDeserializer != null) {
101+
builder.registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializer, false)
69102
}
70103
}
71104
}

core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt

+26-9
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public sealed class SerializersModule {
7171
*/
7272
@SharedImmutable
7373
@ExperimentalSerializationApi
74-
public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap())
74+
public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap())
7575

7676
/**
7777
* Returns a combination of two serial modules
@@ -113,12 +113,19 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri
113113
registerPolymorphicSerializer(baseClass, actualClass, actualSerializer, allowOverwrite = true)
114114
}
115115

116-
override fun <Base : Any> polymorphicDefault(
116+
override fun <Base : Any> polymorphicDefaultSerializer(
117117
baseClass: KClass<Base>,
118-
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
118+
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
119119
) {
120120
registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, allowOverwrite = true)
121121
}
122+
123+
override fun <Base : Any> polymorphicDefaultDeserializer(
124+
baseClass: KClass<Base>,
125+
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
126+
) {
127+
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true)
128+
}
122129
})
123130
}
124131

@@ -133,21 +140,26 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri
133140
internal class SerialModuleImpl(
134141
private val class2ContextualFactory: Map<KClass<*>, ContextualProvider>,
135142
@JvmField val polyBase2Serializers: Map<KClass<*>, Map<KClass<*>, KSerializer<*>>>,
143+
private val polyBase2DefaultSerializerProvider: Map<KClass<*>, PolymorphicSerializerProvider<*>>,
136144
private val polyBase2NamedSerializers: Map<KClass<*>, Map<String, KSerializer<*>>>,
137-
private val polyBase2DefaultProvider: Map<KClass<*>, PolymorphicProvider<*>>
145+
private val polyBase2DefaultDeserializerProvider: Map<KClass<*>, PolymorphicDeserializerProvider<*>>
138146
) : SerializersModule() {
139147

140148
override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>? {
141149
if (!value.isInstanceOf(baseClass)) return null
142-
return polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy<T>
150+
// Registered
151+
val registered = polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy<T>
152+
if (registered != null) return registered
153+
// Default
154+
return (polyBase2DefaultSerializerProvider[baseClass] as? PolymorphicSerializerProvider<T>)?.invoke(value)
143155
}
144156

145157
override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>? {
146158
// Registered
147159
val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer<out T>
148160
if (registered != null) return registered
149161
// Default
150-
return (polyBase2DefaultProvider[baseClass] as? PolymorphicProvider<T>)?.invoke(serializedClassName)
162+
return (polyBase2DefaultDeserializerProvider[baseClass] as? PolymorphicDeserializerProvider<T>)?.invoke(serializedClassName)
151163
}
152164

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

178-
polyBase2DefaultProvider.forEach { (baseClass, provider) ->
179-
collector.polymorphicDefault(baseClass as KClass<Any>, provider as (PolymorphicProvider<out Any>))
190+
polyBase2DefaultSerializerProvider.forEach { (baseClass, provider) ->
191+
collector.polymorphicDefaultSerializer(baseClass as KClass<Any>, provider as (PolymorphicSerializerProvider<Any>))
192+
}
193+
194+
polyBase2DefaultDeserializerProvider.forEach { (baseClass, provider) ->
195+
collector.polymorphicDefaultDeserializer(baseClass as KClass<Any>, provider as (PolymorphicDeserializerProvider<out Any>))
180196
}
181197
}
182198
}
183199

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

186203
/** This class is needed to support re-registering the same static (argless) serializers:
187204
*

core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt

+42-11
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() -
4040
public class SerializersModuleBuilder @PublishedApi internal constructor() : SerializersModuleCollector {
4141
private val class2ContextualProvider: MutableMap<KClass<*>, ContextualProvider> = hashMapOf()
4242
private val polyBase2Serializers: MutableMap<KClass<*>, MutableMap<KClass<*>, KSerializer<*>>> = hashMapOf()
43+
private val polyBase2DefaultSerializerProvider: MutableMap<KClass<*>, PolymorphicSerializerProvider<*>> = hashMapOf()
4344
private val polyBase2NamedSerializers: MutableMap<KClass<*>, MutableMap<String, KSerializer<*>>> = hashMapOf()
44-
private val polyBase2DefaultProvider: MutableMap<KClass<*>, PolymorphicProvider<*>> = hashMapOf()
45+
private val polyBase2DefaultDeserializerProvider: MutableMap<KClass<*>, PolymorphicDeserializerProvider<*>> = hashMapOf()
4546

4647
/**
4748
* Adds [serializer] associated with given [kClass] for contextual serialization.
@@ -91,19 +92,36 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
9192

9293
/**
9394
* Adds a default serializers provider associated with the given [baseClass] to the resulting module.
94-
* [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className`
95-
* were found. `className` could be `null` for formats that support nullable class discriminators
96-
* (currently only `Json` with `useArrayPolymorphism` set to `false`)
95+
* [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found.
9796
*
98-
* @see PolymorphicModuleBuilder.default
97+
* This will not affect deserialization.
9998
*/
100-
public override fun <Base : Any> polymorphicDefault(
99+
@ExperimentalSerializationApi
100+
public override fun <Base : Any> polymorphicDefaultSerializer(
101101
baseClass: KClass<Base>,
102-
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
102+
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
103103
) {
104104
registerDefaultPolymorphicSerializer(baseClass, defaultSerializerProvider, false)
105105
}
106106

107+
/**
108+
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
109+
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
110+
* were found. `className` could be `null` for formats that support nullable class discriminators
111+
* (currently only `Json` with `useArrayPolymorphism` set to `false`).
112+
*
113+
* This will not affect serialization.
114+
*
115+
* @see PolymorphicModuleBuilder.defaultDeserializer
116+
*/
117+
@ExperimentalSerializationApi
118+
public override fun <Base : Any> polymorphicDefaultDeserializer(
119+
baseClass: KClass<Base>,
120+
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
121+
) {
122+
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false)
123+
}
124+
107125
/**
108126
* Copies the content of [module] module into the current builder.
109127
*/
@@ -132,14 +150,27 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
132150
@JvmName("registerDefaultPolymorphicSerializer") // Don't mangle method name for prettier stack traces
133151
internal fun <Base : Any> registerDefaultPolymorphicSerializer(
134152
baseClass: KClass<Base>,
135-
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
153+
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?,
136154
allowOverwrite: Boolean
137155
) {
138-
val previous = polyBase2DefaultProvider[baseClass]
156+
val previous = polyBase2DefaultDeserializerProvider[baseClass]
139157
if (previous != null && previous != defaultSerializerProvider && !allowOverwrite) {
140158
throw IllegalArgumentException("Default serializers provider for class $baseClass is already registered: $previous")
141159
}
142-
polyBase2DefaultProvider[baseClass] = defaultSerializerProvider
160+
polyBase2DefaultSerializerProvider[baseClass] = defaultSerializerProvider
161+
}
162+
163+
@JvmName("registerDefaultPolymorphicDeserializer") // Don't mangle method name for prettier stack traces
164+
internal fun <Base : Any> registerDefaultPolymorphicDeserializer(
165+
baseClass: KClass<Base>,
166+
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
167+
allowOverwrite: Boolean
168+
) {
169+
val previous = polyBase2DefaultDeserializerProvider[baseClass]
170+
if (previous != null && previous != defaultDeserializerProvider && !allowOverwrite) {
171+
throw IllegalArgumentException("Default deserializers provider for class $baseClass is already registered: $previous")
172+
}
173+
polyBase2DefaultDeserializerProvider[baseClass] = defaultDeserializerProvider
143174
}
144175

145176
@JvmName("registerPolymorphicSerializer") // Don't mangle method name for prettier stack traces
@@ -188,7 +219,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
188219

189220
@PublishedApi
190221
internal fun build(): SerializersModule =
191-
SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2NamedSerializers, polyBase2DefaultProvider)
222+
SerialModuleImpl(class2ContextualProvider, polyBase2Serializers, polyBase2DefaultSerializerProvider, polyBase2NamedSerializers, polyBase2DefaultDeserializerProvider)
192223
}
193224

194225
/**

core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt

+37-4
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,44 @@ public interface SerializersModuleCollector {
4848
/**
4949
* Accept a default serializer provider, associated with the [baseClass] for polymorphic serialization.
5050
*
51-
* @see SerializersModuleBuilder.polymorphicDefault
52-
* @see PolymorphicModuleBuilder.default
51+
* This will not affect deserialization.
52+
*
53+
* @see SerializersModuleBuilder.polymorphicDefaultSerializer
54+
* @see PolymorphicModuleBuilder.defaultSerializer
5355
*/
54-
public fun <Base : Any> polymorphicDefault(
56+
@ExperimentalSerializationApi
57+
public fun <Base : Any> polymorphicDefaultSerializer(
58+
baseClass: KClass<Base>,
59+
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
60+
)
61+
62+
/**
63+
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
64+
*
65+
* This will not affect serialization.
66+
*
67+
* @see SerializersModuleBuilder.polymorphicDefaultDeserializer
68+
* @see PolymorphicModuleBuilder.defaultDeserializer
69+
*/
70+
@ExperimentalSerializationApi
71+
public fun <Base : Any> polymorphicDefaultDeserializer(
5572
baseClass: KClass<Base>,
56-
defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
73+
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
5774
)
75+
76+
/**
77+
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
78+
*
79+
* This will not affect serialization.
80+
*
81+
* @see SerializersModuleBuilder.polymorphicDefaultDeserializer
82+
* @see PolymorphicModuleBuilder.defaultDeserializer
83+
*/
84+
// TODO: deprecate in 1.4
85+
public fun <Base : Any> polymorphicDefault(
86+
baseClass: KClass<Base>,
87+
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
88+
) {
89+
polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)
90+
}
5891
}

0 commit comments

Comments
 (0)