1 /*
<lambda>null2 * 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.descriptors
6
7 import kotlinx.serialization.*
8 import kotlinx.serialization.internal.*
9 import kotlinx.serialization.modules.*
10 import kotlin.jvm.*
11 import kotlin.reflect.*
12
13
14 /**
15 * Retrieves [KClass] associated with serializer and its descriptor, if it was captured.
16 *
17 * For schema introspection purposes, [capturedKClass] can be used in [SerializersModule] as a key
18 * to retrieve registered descriptor at runtime.
19 * This property is intended to be used on [SerialKind.CONTEXTUAL] and [PolymorphicKind.OPEN] kinds of descriptors,
20 * where actual serializer used for a property can be determined only at runtime.
21 * Serializers which represent contextual serialization and open polymorphism (namely, [ContextualSerializer] and
22 * [PolymorphicSerializer]) capture statically known KClass in a descriptor and can expose it via this property.
23 *
24 * This property is `null` for descriptors that are not of [SerialKind.CONTEXTUAL] or [PolymorphicKind.OPEN] kinds.
25 * It _may_ be `null` for descriptors of these kinds, if captured class information is unavailable for various reasons.
26 * It means that schema introspection should be performed in an application-specific manner.
27 *
28 * ### Example
29 * Imagine we need to find all distinct properties names, which may occur in output after serializing a given class
30 * with respect to [`@Contextual`][Contextual] annotation and all possible inheritors when the class is
31 * serialized polymorphically.
32 * Then we can write following function:
33 * ```
34 * fun allDistinctNames(descriptor: SerialDescriptor, module: SerialModule) = when (descriptor.kind) {
35 * is PolymorphicKind.OPEN -> module.getPolymorphicDescriptors(descriptor)
36 * .map { it.elementNames() }.flatten().toSet()
37 * is SerialKind.CONTEXTUAL -> module.getContextualDescriptor(descriptor)
38 * ?.elementNames().orEmpty().toSet()
39 * else -> descriptor.elementNames().toSet()
40 * }
41 * ```
42 * @see SerializersModule.getContextualDescriptor
43 * @see SerializersModule.getPolymorphicDescriptors
44 */
45 @ExperimentalSerializationApi
46 public val SerialDescriptor.capturedKClass: KClass<*>?
47 get() = when (this) {
48 is ContextDescriptor -> kClass
49 is SerialDescriptorForNullable -> original.capturedKClass
50 else -> null
51 }
52
53 /**
54 * Looks up a descriptor of serializer registered for contextual serialization in [this],
55 * using [SerialDescriptor.capturedKClass] as a key.
56 *
57 * @see SerializersModuleBuilder.contextual
58 */
59 @ExperimentalSerializationApi
getContextualDescriptornull60 public fun SerializersModule.getContextualDescriptor(descriptor: SerialDescriptor): SerialDescriptor? =
61 descriptor.capturedKClass?.let { klass -> getContextual(klass)?.descriptor }
62
63 /**
64 * Retrieves a collection of descriptors which serializers are registered for polymorphic serialization in [this]
65 * with base class equal to [descriptor]'s [SerialDescriptor.capturedKClass].
66 * This method does not retrieve serializers registered with [PolymorphicModuleBuilder.defaultDeserializer]
67 * or [PolymorphicModuleBuilder.defaultSerializer].
68 *
69 * @see SerializersModule.getPolymorphic
70 * @see SerializersModuleBuilder.polymorphic
71 */
72 @ExperimentalSerializationApi
getPolymorphicDescriptorsnull73 public fun SerializersModule.getPolymorphicDescriptors(descriptor: SerialDescriptor): List<SerialDescriptor> {
74 val kClass = descriptor.capturedKClass ?: return emptyList()
75 // SerializersModule is sealed class with the only implementation
76 return (this as SerialModuleImpl).polyBase2Serializers[kClass]?.values.orEmpty().map { it.descriptor }
77 }
78
79 /**
80 * Wraps [this] in [ContextDescriptor].
81 */
withContextnull82 internal fun SerialDescriptor.withContext(context: KClass<*>): SerialDescriptor =
83 ContextDescriptor(this, context)
84
85 /**
86 * Descriptor that captures [kClass] and allows retrieving additional runtime information,
87 * if proper [SerializersModule] is provided.
88 */
89 @OptIn(ExperimentalSerializationApi::class)
90 private class ContextDescriptor(
91 private val original: SerialDescriptor,
92 @JvmField val kClass: KClass<*>
93 ) : SerialDescriptor by original {
94 override val serialName = "${original.serialName}<${kClass.simpleName}>"
95
96 override fun equals(other: Any?): Boolean {
97 val another = other as? ContextDescriptor ?: return false
98 return original == another.original && another.kClass == this.kClass
99 }
100
101 override fun hashCode(): Int {
102 var result = kClass.hashCode()
103 result = 31 * result + serialName.hashCode()
104 return result
105 }
106
107 override fun toString(): String {
108 return "ContextDescriptor(kClass: $kClass, original: $original)"
109 }
110 }
111