summaryrefslogtreecommitdiff
path: root/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
blob: 8a9126d747065d37320cda784b65b0929b12a874 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
 * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.serialization.modules

import kotlinx.serialization.*
import kotlinx.serialization.internal.*
import kotlin.js.*
import kotlin.jvm.*
import kotlin.reflect.*

/**
 * [SerializersModule] is a collection of serializers used by [ContextualSerializer] and [PolymorphicSerializer]
 * to override or provide serializers at the runtime, whereas at the compile-time they provided by the serialization plugin.
 * It can be considered as a map where serializers can be found using their statically known KClasses.
 *
 * To enable runtime serializers resolution, one of the special annotations must be used on target types
 * ([Polymorphic] or [Contextual]), and a serial module with serializers should be used during construction of [SerialFormat].
 *
 * Serializers module can be built with `SerializersModule {}` builder function.
 * Empty module can be obtained with `EmptySerializersModule()` factory function.
 *
 * @see Contextual
 * @see Polymorphic
 */
public sealed class SerializersModule {

    @ExperimentalSerializationApi
    @Deprecated(
        "Deprecated in favor of overload with default parameter",
        ReplaceWith("getContextual(kclass)"),
        DeprecationLevel.HIDDEN
    ) // Was experimental since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner
    public fun <T : Any> getContextual(kclass: KClass<T>): KSerializer<T>? =
        getContextual(kclass, emptyList())

    /**
     * Returns a contextual serializer associated with a given [kClass].
     * If given class has generic parameters and module has provider for [kClass],
     * [typeArgumentsSerializers] are used to create serializer.
     * This method is used in context-sensitive operations on a property marked with [Contextual] by a [ContextualSerializer].
     *
     * @see SerializersModuleBuilder.contextual
     */
    @ExperimentalSerializationApi
    public abstract fun <T : Any> getContextual(
        kClass: KClass<T>,
        typeArgumentsSerializers: List<KSerializer<*>> = emptyList()
    ): KSerializer<T>?

    /**
     * Returns a polymorphic serializer registered for a class of the given [value] in the scope of [baseClass].
     */
    @ExperimentalSerializationApi
    public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>?

    /**
     * Returns a polymorphic deserializer registered for a [serializedClassName] in the scope of [baseClass]
     * or default value constructed from [serializedClassName] if a default serializer provider was registered.
     */
    @ExperimentalSerializationApi
    public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>?

    /**
     * Copies contents of this module to the given [collector].
     */
    @ExperimentalSerializationApi
    public abstract fun dumpTo(collector: SerializersModuleCollector)
}

/**
 * A [SerializersModule] which is empty and always returns `null`.
 */
@Deprecated("Deprecated in the favour of 'EmptySerializersModule()'",
    level = DeprecationLevel.WARNING,
    replaceWith = ReplaceWith("EmptySerializersModule()"))
@JsName("EmptySerializersModuleLegacyJs") // Compatibility with JS
public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap())

/**
 * Returns a combination of two serial modules
 *
 * If serializer for some class presents in both modules, a [SerializerAlreadyRegisteredException] is thrown.
 * To overwrite serializers, use [SerializersModule.overwriteWith] function.
 */
public operator fun SerializersModule.plus(other: SerializersModule): SerializersModule = SerializersModule {
    include(this@plus)
    include(other)
}

/**
 * Returns a combination of two serial modules
 *
 * If serializer for some class presents in both modules, result module
 * will contain serializer from [other] module.
 */
@OptIn(ExperimentalSerializationApi::class)
public infix fun SerializersModule.overwriteWith(other: SerializersModule): SerializersModule = SerializersModule {
    include(this@overwriteWith)
    other.dumpTo(object : SerializersModuleCollector {
        override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
            registerSerializer(kClass, ContextualProvider.Argless(serializer), allowOverwrite = true)
        }

        override fun <T : Any> contextual(
            kClass: KClass<T>,
            provider: (serializers: List<KSerializer<*>>) -> KSerializer<*>
        ) {
            registerSerializer(kClass, ContextualProvider.WithTypeArguments(provider), allowOverwrite = true)
        }

        override fun <Base : Any, Sub : Base> polymorphic(
            baseClass: KClass<Base>,
            actualClass: KClass<Sub>,
            actualSerializer: KSerializer<Sub>
        ) {
            registerPolymorphicSerializer(baseClass, actualClass, actualSerializer, allowOverwrite = true)
        }

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

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

// Implementation details below

/**
 * A default implementation of [SerializersModule]
 * which uses hash maps to store serializers associated with KClasses.
 */
@Suppress("UNCHECKED_CAST")
@OptIn(ExperimentalSerializationApi::class)
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 polyBase2DefaultDeserializerProvider: Map<KClass<*>, PolymorphicDeserializerProvider<*>>
) : SerializersModule() {

    override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>? {
        if (!baseClass.isInstance(value)) return null
        // 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<T>? {
        // Registered
        val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer<out T>
        if (registered != null) return registered
        // Default
        return (polyBase2DefaultDeserializerProvider[baseClass] as? PolymorphicDeserializerProvider<T>)?.invoke(serializedClassName)
    }

    override fun <T : Any> getContextual(kClass: KClass<T>, typeArgumentsSerializers: List<KSerializer<*>>): KSerializer<T>? {
        return (class2ContextualFactory[kClass]?.invoke(typeArgumentsSerializers)) as? KSerializer<T>?
    }

    override fun dumpTo(collector: SerializersModuleCollector) {
        class2ContextualFactory.forEach { (kclass, serial) ->
            when (serial) {
                is ContextualProvider.Argless -> collector.contextual(
                    kclass as KClass<Any>,
                    serial.serializer as KSerializer<Any>
                )
                is ContextualProvider.WithTypeArguments -> collector.contextual(kclass, serial.provider)
            }
        }

        polyBase2Serializers.forEach { (baseClass, classMap) ->
            classMap.forEach { (actualClass, serializer) ->
                collector.polymorphic(
                    baseClass as KClass<Any>,
                    actualClass as KClass<Any>,
                    serializer.cast()
                )
            }
        }

        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 PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<Base>?
internal typealias PolymorphicSerializerProvider<Base> = (value: Base) -> SerializationStrategy<Base>?

/** This class is needed to support re-registering the same static (argless) serializers:
 *
 * ```
 * val m1 = serializersModuleOf(A::class, A.serializer())
 * val m2 = serializersModuleOf(A::class, A.serializer())
 * val aggregate = m1 + m2 // should not throw
 * ```
 */
internal sealed class ContextualProvider {
    abstract operator fun invoke(typeArgumentsSerializers: List<KSerializer<*>>): KSerializer<*>

    class Argless(val serializer: KSerializer<*>) : ContextualProvider() {
        override fun invoke(typeArgumentsSerializers: List<KSerializer<*>>): KSerializer<*> = serializer

        override fun equals(other: Any?): Boolean = other is Argless && other.serializer == this.serializer

        override fun hashCode(): Int = serializer.hashCode()
    }

    class WithTypeArguments(val provider: (typeArgumentsSerializers: List<KSerializer<*>>) -> KSerializer<*>) :
        ContextualProvider() {
        override fun invoke(typeArgumentsSerializers: List<KSerializer<*>>): KSerializer<*> =
            provider(typeArgumentsSerializers)
    }

}