summaryrefslogtreecommitdiff
path: root/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
blob: 136df8c317170aafea06ab098cc924ffa3972c9b (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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/*
 * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.serialization.descriptors

import kotlinx.serialization.*
import kotlinx.serialization.encoding.*

/**
 * Serial descriptor is an inherent property of [KSerializer] that describes the structure of the serializable type.
 * The structure of the serializable type is not only the property of the type, but also of the serializer as well,
 * meaning that one type can have multiple descriptors that have completely different structure.
 *
 * For example, the class `class Color(val rgb: Int)` can have multiple serializable representations,
 * such as `{"rgb": 255}`, `"#0000FF"`, `[0, 0, 255]` and `{"red": 0, "green": 0, "blue": 255}`.
 * Representations are determined by serializers and each such serializer has its own descriptor that identifies
 * each structure in a distinguishable and format-agnostic manner.
 *
 * ### Structure
 * Serial descriptor is identified by its [name][serialName] and consists of kind, potentially empty set of
 * children elements and additional metadata.
 *
 * * [serialName] uniquely identifies the descriptor (and the corresponding serializer) for non-generic types.
 *   For generic types, the actual type substitution is omitted from the string representation and the name
 *   identifies the family of the serializers without type substitutions. However, type substitution is accounted
 *   in [equals] and [hashCode] operations, meaning that descriptors of generic classes with the same name, but different type
 *   parameters, are not equal to each other.
 *   [serialName] is typically used to specify the type of the target class during serialization of polymorphic and sealed
 *   classes, for observability and diagnostics.
 * * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection et cetera.
 * * Children elements are represented as serial descriptors as well and define the structure of the type's elements.
 * * Metadata carries additional potentially useful information, such as [nullability][nullable], [optionality][isElementOptional]
 *   and [serial annotations][getElementAnnotations].
 *
 * ### Usages
 * There are two general usages of the descriptors: THE serialization process and serialization introspection.
 *
 * #### Serialization
 * Serial descriptor is used as bridge between decoders/encoders and serializers.
 * When asking for a next element, the serializer provides an expected descriptor to the decoder, and,
 * based on the descriptor content, decoder decides how to parse its input.
 * In JSON, for example, when the encoder is asked to encode the next element and this element
 * is a subtype of [List], the encoder receives a descriptor with [StructureKind.LIST] and, based on that,
 * first writes an opening square bracket before writing the content of the list.
 *
 * Serial descriptor _encapsulates_ the structure of the data, so serializers can be free from
 * format-specific details. `ListSerializer` knows nothing about JSON and square brackets, providing
 * only the structure of the data and delegating encoding decision to the format itself.
 *
 * #### Introspection
 * Another usage of a serial descriptor is type introspection without its serialization.
 * Introspection can be used to check, whether the given serializable class complies the
 * corresponding scheme and to generate JSON or ProtoBuf schema from the given class.
 *
 * ### Indices
 * Serial descriptor API operates with children indices.
 * For the fixed-size structures, such as regular classes, index is represented by a value in
 * the range from zero to [elementsCount] and represent and index of the property in this class.
 * Consequently, primitives do not have children and their element count is zero.
 *
 * For collections and maps, though, indices does not have fixed bound. Regular collections descriptors usually
 * have one element (`T`, maps have two, one for keys and one for values), but potentially unlimited
 * number of actual children values. Valid indices range is not known statically
 * and implementations of descriptor should provide consistent and unbounded names and indices.
 *
 * In practice, for regular classes it is allowed to invoke `getElement*(index)` methods
 * with an index within `0 until elementsCount` range and element at the particular index corresponds to the
 * serializable property at the given position.
 * For collections and maps, index parameter for `getElement*(index)` methods is effectively bound
 * by the maximal number of collection/map elements.
 *
 * ### Thread-safety and mutability
 * Serial descriptor implementation should be immutable and, thus, thread-safe.
 *
 * ### Equality and caching
 * Serial descriptor can be used as a unique identifier for format-specific data or schemas and
 * this implies the following restrictions on its `equals` and `hashCode`:
 *   *
 *
 * An [equals] implementation should use both [serialName] and elements structure.
 * Comparing [elementDescriptors] directly is discouraged,
 * because it may cause a stack overflow error, e.g. if a serializable class `T` contains elements of type `T`.
 * To avoid it, a serial descriptor implementation should compare only descriptors
 * of class' type parameters, in a way that `serializer<Box<Int>>().descriptor != serializer<Box<String>>().descriptor`.
 * If type parameters are equal, descriptors structure should be compared by using children elements
 * descriptors' [serialName]s, which correspond to class names
 * (do not confuse with elements own names, which correspond to properties names); and/or other [SerialDescriptor]
 * properties, such as [kind].
 * An example of [equals] implementation:
 * ```
 * if (this === other) return true
 * if (other::class != this::class) return false
 * if (serialName != other.serialName) return false
 * if (!typeParametersAreEqual(other)) return false
 * if (this.elementDescriptors().map { it.serialName } != other.elementDescriptors().map { it.serialName }) return false
 * return true
 * ```
 *
 * [hashCode] implementation should use the same properties for computing the result.
 *
 * ### User-defined serial descriptors
 * The best way to define a custom descriptor is to use [SerialDescriptor] builder function, where
 * for each serializable property corresponding element is declared.
 *
 * Example:
 * ```
 * // Class with custom serializer and custom serial descriptor
 * class Data(
 *     val intField: Int, // This field is ignored by custom serializer
 *     val longField: Long, // This field is written as long, but in serialized form is named as "_longField"
 *     val stringList: List<String> // This field is written as regular list of strings
 * )
 *
 * // Descriptor for such class:
 * SerialDescriptor("my.package.Data") {
 *     // intField is deliberately ignored by serializer -- not present in the descriptor as well
 *     element<Long>("_longField") // longField is named as _longField
 *     element("stringField", listDescriptor<String>())
 * }
 * ```
 *
 * For a classes that are represented as a single primitive value, [PrimitiveSerialDescriptor] builder function can be used instead.
 *
 * ### Not stable for inheritance
 *
 * `SerialDescriptor` interface is not stable for inheritance in 3rd party libraries, as new methods
 * might be added to this interface or contracts of the existing methods can be changed.
 * This interface is safe to build using [buildClassSerialDescriptor] and [PrimitiveSerialDescriptor],
 * and is safe to delegate implementation to existing instances.
 */
public interface SerialDescriptor {
    /**
     * Serial name of the descriptor that identifies pair of the associated serializer and target class.
     *
     * For generated serializers, serial name is equal to the corresponding class's fully-qualified name
     * or, if overridden, [SerialName].
     * Custom serializers should provide a unique serial name that identify both the serializable class and
     * the serializer itself, ignoring type arguments, if they are present.
     */
    @ExperimentalSerializationApi
    public val serialName: String

    /**
     * The kind of the serialized form that determines **the shape** of the serialized data.
     * Formats use serial kind to add and parse serializer-agnostic metadata to the result.
     *
     * For example, JSON format wraps [classes][StructureKind.CLASS] and [StructureKind.MAP] into
     * brackets, while ProtoBuf just serialize these types in separate ways.
     *
     * Kind should be consistent with the implementation, for example, if it is a [primitive][PrimitiveKind],
     * then its elements count should be zero and vice versa.
     */
    @ExperimentalSerializationApi
    public val kind: SerialKind

    /**
     * Whether the descriptor describes nullable element.
     * Returns `true` if associated serializer can serialize/deserialize nullable elements of the described type.
     */
    @ExperimentalSerializationApi
    public val isNullable: Boolean get() = false

    /**
     * Returns `true` if this descriptor describes a serializable inline class.
     */
    @ExperimentalSerializationApi
    public val isInline: Boolean get() = false

    /**
     * The number of elements this descriptor describes, besides from the class itself.
     * [elementsCount] describes the number of **semantic** elements, not the number
     * of actual fields/properties in the serialized form, even though they frequently match.
     *
     * For example, for the following class
     * `class Complex(val real: Long, val imaginary: Long)` the corresponding descriptor
     * and the serialized form both have two elements, while for `class IntList : ArrayList<Int>()`
     * the corresponding descriptor has a single element (`IntDescriptor`, the type of list element),
     * but from zero up to `Int.MAX_VALUE` values in the serialized form.
     */
    @ExperimentalSerializationApi
    public val elementsCount: Int

    /**
     * Returns serial annotations of the associated class.
     * Serial annotations can be used to specify an additional metadata that may be used during serialization.
     * Only annotations marked with [SerialInfo] are added to the resulting list.
     */
    @ExperimentalSerializationApi
    public val annotations: List<Annotation> get() = emptyList()

    /**
     * Returns a positional name of the child at the given [index].
     * Positional name represents a corresponding property name in the class, associated with
     * the current descriptor.
     *
     * @throws IndexOutOfBoundsException for an illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive)
     */
    @ExperimentalSerializationApi
    public fun getElementName(index: Int): String

    /**
     * Returns an index in the children list of the given element by its name or [CompositeDecoder.UNKNOWN_NAME]
     * if there is no such element.
     * The resulting index, if it is not [CompositeDecoder.UNKNOWN_NAME], is guaranteed to be usable with [getElementName].
     */
    @ExperimentalSerializationApi
    public fun getElementIndex(name: String): Int

    /**
     * Returns serial annotations of the child element at the given [index].
     * This method differs from `getElementDescriptor(index).annotations` by reporting only
     * declaration-specific annotations:
     * ```
     * @Serializable
     * @SomeSerialAnnotation
     * class Nested(...)
     *
     * @Serializable
     * class Outer(@AnotherSerialAnnotation val nested: Nested)
     *
     * outerDescriptor.getElementAnnotations(0) // Returns [@AnotherSerialAnnotation]
     * outerDescriptor.getElementDescriptor(0).annotations // Returns [@SomeSerialAnnotation]
     * ```
     * Only annotations marked with [SerialInfo] are added to the resulting list.
     *
     * @throws IndexOutOfBoundsException for an illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
     */
    @ExperimentalSerializationApi
    public fun getElementAnnotations(index: Int): List<Annotation>

    /**
     * Retrieves the descriptor of the child element for the given [index].
     * For the property of type `T` on the position `i`, `getElementDescriptor(i)` yields the same result
     * as for `T.serializer().descriptor`, if the serializer for this property is not explicitly overridden
     * with `@Serializable(with = ...`)`, [Polymorphic] or [Contextual].
     * This method can be used to completely introspect the type that the current descriptor describes.
     *
     * @throws IndexOutOfBoundsException for illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
     */
    @ExperimentalSerializationApi
    public fun getElementDescriptor(index: Int): SerialDescriptor

    /**
     * Whether the element at the given [index] is optional (can be absent is serialized form).
     * For generated descriptors, all elements that have a corresponding default parameter value are
     * marked as optional. Custom serializers can treat optional values in a serialization-specific manner
     * without default parameters constraint.
     *
     * Example of optionality:
     * ```
     * @Serializable
     * class Holder(
     *     val a: Int, // Optional == false
     *     val b: Int?, // Optional == false
     *     val c: Int? = null, // Optional == true
     *     val d: List<Int>, // Optional == false
     *     val e: List<Int> = listOf(1), // Optional == true
     * )
     * ```
     * Returns `false` for valid indices of collections, maps and enums.
     *
     * @throws IndexOutOfBoundsException for an illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
     */
    @ExperimentalSerializationApi
    public fun isElementOptional(index: Int): Boolean
}

/**
 * Returns an iterable of all descriptor [elements][SerialDescriptor.getElementDescriptor].
 */
@ExperimentalSerializationApi
public val SerialDescriptor.elementDescriptors: Iterable<SerialDescriptor>
    get() = Iterable {
        object : Iterator<SerialDescriptor> {
            private var elementsLeft = elementsCount
            override fun hasNext(): Boolean = elementsLeft > 0

            override fun next(): SerialDescriptor {
                return getElementDescriptor(elementsCount - (elementsLeft--))
            }
        }
    }

/**
 * Returns an iterable of all descriptor [element names][SerialDescriptor.getElementName].
 */
@ExperimentalSerializationApi
public val SerialDescriptor.elementNames: Iterable<String>
    get() = Iterable {
        object : Iterator<String> {
            private var elementsLeft = elementsCount
            override fun hasNext(): Boolean = elementsLeft > 0

            override fun next(): String {
                return getElementName(elementsCount - (elementsLeft--))
            }
        }
    }