1 /*
2  * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.json
6 
7 import kotlinx.serialization.*
8 import kotlinx.serialization.descriptors.*
9 import kotlinx.serialization.encoding.*
10 import kotlinx.serialization.modules.*
11 import kotlin.reflect.*
12 
13 /**
14  * Base class for custom serializers that allows selecting polymorphic serializer
15  * without a dedicated class discriminator, on a content basis.
16  *
17  * Usually, polymorphic serialization (represented by [PolymorphicSerializer] and [SealedClassSerializer])
18  * requires a dedicated `"type"` property in the JSON to
19  * determine actual serializer that is used to deserialize Kotlin class.
20  *
21  * However, sometimes (e.g. when interacting with external API) type property is not present in the input
22  * and it is expected to guess the actual type by the shape of JSON, for example by the presence of specific key.
23  * [JsonContentPolymorphicSerializer] provides a skeleton implementation for such strategy. Please note that
24  * since JSON content is represented by [JsonElement] class and could be read only with [JsonDecoder] decoder,
25  * this class works only with [Json] format.
26  *
27  * Deserialization happens in two stages: first, a value from the input JSON is read
28  * to as a [JsonElement]. Second, [selectDeserializer] function is called to determine which serializer should be used.
29  * The returned serializer is used to deserialize [JsonElement] back to Kotlin object.
30  *
31  * It is possible to serialize values this serializer. In that case, class discriminator property won't
32  * be added to JSON stream, i.e., deserializing a class from the string and serializing it back yields the original string.
33  * However, to determine a serializer, a standard polymorphic mechanism represented by [SerializersModule] is used.
34  * For convenience, [serialize] method can lookup default serializer, but it is recommended to follow
35  * standard procedure with [registering][SerializersModuleBuilder.polymorphic].
36  *
37  * Usage example:
38  * ```
39  * interface Payment {
40  *     val amount: String
41  * }
42  *
43  * @Serializable
44  * data class SuccessfulPayment(override val amount: String, val date: String) : Payment
45  *
46  * @Serializable
47  * data class RefundedPayment(override val amount: String, val date: String, val reason: String) : Payment
48  *
49  * object PaymentSerializer : JsonContentPolymorphicSerializer<Payment>(Payment::class) {
50  *     override fun selectDeserializer(content: JsonElement) = when {
51  *         "reason" in content.jsonObject -> RefundedPayment.serializer()
52  *         else -> SuccessfulPayment.serializer()
53  *     }
54  * }
55  *
56  * // Now both statements will yield different subclasses of Payment:
57  *
58  * Json.decodeFromString(PaymentSerializer, """{"amount":"1.0","date":"03.02.2020"}""")
59  * Json.decodeFromString(PaymentSerializer, """{"amount":"2.0","date":"03.02.2020","reason":"complaint"}""")
60  * ```
61  *
62  * @param T A root type for all classes that could be possibly encountered during serialization and deserialization.
63  * Must be non-final class or interface.
64  * @param baseClass A class token for [T].
65  */
66 @OptIn(ExperimentalSerializationApi::class)
67 public abstract class JsonContentPolymorphicSerializer<T : Any>(private val baseClass: KClass<T>) : KSerializer<T> {
68     /**
69      * A descriptor for this set of content-based serializers.
70      * By default, it uses the name composed of [baseClass] simple name,
71      * kind is set to [PolymorphicKind.SEALED] and contains 0 elements.
72      *
73      * However, this descriptor can be overridden to achieve better representation of custom transformed JSON shape
74      * for schema generating/introspection purposes.
75      */
76     override val descriptor: SerialDescriptor =
77         buildSerialDescriptor("JsonContentPolymorphicSerializer<${baseClass.simpleName}>", PolymorphicKind.SEALED)
78 
serializenull79     final override fun serialize(encoder: Encoder, value: T) {
80         val actualSerializer =
81             encoder.serializersModule.getPolymorphic(baseClass, value)
82                     ?: value::class.serializerOrNull()
83                     ?: throwSubtypeNotRegistered(value::class, baseClass)
84         @Suppress("UNCHECKED_CAST")
85         (actualSerializer as KSerializer<T>).serialize(encoder, value)
86     }
87 
deserializenull88     final override fun deserialize(decoder: Decoder): T {
89         val input = decoder.asJsonDecoder()
90         val tree = input.decodeJsonElement()
91 
92         @Suppress("UNCHECKED_CAST")
93         val actualSerializer = selectDeserializer(tree) as KSerializer<T>
94         return input.json.decodeFromJsonElement(actualSerializer, tree)
95     }
96 
97     /**
98      * Determines a particular strategy for deserialization by looking on a parsed JSON [element].
99      */
selectDeserializernull100     protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T>
101 
102     private fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing {
103         val subClassName = subClass.simpleName ?: "$subClass"
104         val scope = "in the scope of '${baseClass.simpleName}'"
105         throw SerializationException(
106                     "Class '${subClassName}' is not registered for polymorphic serialization $scope.\n" +
107                             "Mark the base class as 'sealed' or register the serializer explicitly.")
108     }
109 
110 }
111