1 /*
<lambda>null2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization
6 
7 import kotlinx.serialization.builtins.*
8 import kotlinx.serialization.descriptors.*
9 import kotlinx.serialization.encoding.*
10 import kotlinx.serialization.internal.*
11 import kotlinx.serialization.modules.*
12 import kotlin.reflect.*
13 
14 /**
15  * This class provides support for multiplatform polymorphic serialization of sealed classes.
16  *
17  * In contrary to [PolymorphicSerializer], all known subclasses with serializers must be passed
18  * in `subclasses` and `subSerializers` constructor parameters.
19  * If a subclass is a sealed class itself, all its subclasses are registered as well.
20  *
21  * If a sealed hierarchy is marked with [@Serializable][Serializable], an instance of this class is provided automatically.
22  * In most of the cases, you won't need to perform any manual setup:
23  *
24  * ```
25  * @Serializable
26  * sealed class SimpleSealed {
27  *     @Serializable
28  *     public data class SubSealedA(val s: String) : SimpleSealed()
29  *
30  *     @Serializable
31  *     public data class SubSealedB(val i: Int) : SimpleSealed()
32  * }
33  *
34  * // will perform correct polymorphic serialization and deserialization:
35  * Json.encodeToString(SimpleSealed.serializer(), SubSealedA("foo"))
36  * ```
37  *
38  * However, it is possible to register additional subclasses using regular [SerializersModule].
39  * It is required when one of the subclasses is an abstract class itself:
40  *
41  * ```
42  * @Serializable
43  * sealed class ProtocolWithAbstractClass {
44  *     @Serializable
45  *     abstract class Message : ProtocolWithAbstractClass() {
46  *         @Serializable
47  *         data class StringMessage(val description: String, val message: String) : Message()
48  *
49  *         @Serializable
50  *         data class IntMessage(val description: String, val message: Int) : Message()
51  *     }
52  *
53  *     @Serializable
54  *     data class ErrorMessage(val error: String) : ProtocolWithAbstractClass()
55  * }
56  * ```
57  *
58  * In this case, `ErrorMessage` would be registered automatically by the plugin,
59  * but `StringMessage` and `IntMessage` require manual registration, as described in [PolymorphicSerializer] documentation:
60  *
61  * ```
62  * val abstractContext = SerializersModule {
63  *     polymorphic(ProtocolWithAbstractClass::class) {
64  *         subclass(ProtocolWithAbstractClass.Message.IntMessage::class)
65  *         subclass(ProtocolWithAbstractClass.Message.StringMessage::class)
66  *         // no need to register ProtocolWithAbstractClass.ErrorMessage
67  *     }
68  * }
69  * ```
70  */
71 @InternalSerializationApi
72 @OptIn(ExperimentalSerializationApi::class)
73 public class SealedClassSerializer<T : Any>(
74     serialName: String,
75     override val baseClass: KClass<T>,
76     subclasses: Array<KClass<out T>>,
77     subclassSerializers: Array<KSerializer<out T>>
78 ) : AbstractPolymorphicSerializer<T>() {
79 
80     /**
81      * This constructor is needed to store serial info annotations defined on the sealed class.
82      * Support for such annotations was added in Kotlin 1.5.30; previous plugins used primary constructor of this class
83      * directly, therefore this constructor is secondary.
84      *
85      * This constructor can (and should) became primary when Require-Kotlin-Version is raised to at least 1.5.30
86      * to remove necessity to store annotations separately and calculate descriptor via `lazy {}`.
87      *
88      * When doing this change, also migrate secondary constructors from [PolymorphicSerializer] and [ObjectSerializer].
89      */
90     @PublishedApi
91     internal constructor(
92         serialName: String,
93         baseClass: KClass<T>,
94         subclasses: Array<KClass<out T>>,
95         subclassSerializers: Array<KSerializer<out T>>,
96         classAnnotations: Array<Annotation>
97     ) : this(serialName, baseClass, subclasses, subclassSerializers) {
98         this._annotations = classAnnotations.asList()
99     }
100 
101     private var _annotations: List<Annotation> = emptyList()
102 
103     override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
104         buildSerialDescriptor(serialName, PolymorphicKind.SEALED) {
105             element("type", String.serializer().descriptor)
106             val elementDescriptor =
107                 buildSerialDescriptor("kotlinx.serialization.Sealed<${baseClass.simpleName}>", SerialKind.CONTEXTUAL) {
108                     // serialName2Serializer is guaranteed to have no duplicates — checked in `init`.
109                     serialName2Serializer.forEach { (name, serializer) ->
110                         element(name, serializer.descriptor)
111                     }
112                 }
113             element("value", elementDescriptor)
114             annotations = _annotations
115         }
116     }
117 
118     private val class2Serializer: Map<KClass<out T>, KSerializer<out T>>
119     private val serialName2Serializer: Map<String, KSerializer<out T>>
120 
121     init {
122         if (subclasses.size != subclassSerializers.size) {
123             throw IllegalArgumentException("All subclasses of sealed class ${baseClass.simpleName} should be marked @Serializable")
124         }
125 
126         // Note: we do not check whether different serializers are provided if the same KClass duplicated in the `subclasses`.
127         // Plugin should produce identical serializers, although they are not always strictly equal (e.g. new ObjectSerializer
128         // may be created every time)
129         class2Serializer = subclasses.zip(subclassSerializers).toMap()
130         serialName2Serializer = class2Serializer.entries.groupingBy { it.value.descriptor.serialName }
131             .aggregate<Map.Entry<KClass<out T>, KSerializer<out T>>, String, Map.Entry<KClass<*>, KSerializer<out T>>>
132             { key, accumulator, element, _ ->
133                 if (accumulator != null) {
134                     error(
135                         "Multiple sealed subclasses of '$baseClass' have the same serial name '$key':" +
136                                 " '${accumulator.key}', '${element.key}'"
137                     )
138                 }
139                 element
140             }.mapValues { it.value.value }
141     }
142 
143     override fun findPolymorphicSerializerOrNull(
144         decoder: CompositeDecoder,
145         klassName: String?
146     ): DeserializationStrategy<T>? {
147         return serialName2Serializer[klassName] ?: super.findPolymorphicSerializerOrNull(decoder, klassName)
148     }
149 
150     override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: T): SerializationStrategy<T>? {
151         return (class2Serializer[value::class] ?: super.findPolymorphicSerializerOrNull(encoder, value))?.cast()
152     }
153 }
154