summaryrefslogtreecommitdiff
path: root/runtime/commonMain/src/kotlinx/serialization/SerialDescriptor.kt
blob: 5c517f2790b6faf00a698384484249f28e0f80ab (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
/*
 * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.serialization

/**
 * 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 uniquely identified by its name and consists of kind, potentially empty set of
 * children elements and additional metadata.
 * * [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, optionality
 *   and serial annotations.
 *
 * ### 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, index 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. In such cases, valid indices range is not known statically
 * and implementations of descriptor should provide consistent and unbounded names and indices.
 * See [kotlinx.serialization.internal.ListLikeSerializer] for the reference.
 *
 * ### Thread-safety and mutability
 * Serial descriptor implementation should be immutable after the publication and thread-safe.
 *
 * ### 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,
 * [PrimitiveDescriptor] builder function can be used instead.
 */
public interface SerialDescriptor {
    /**
     * Serial name of the descriptor that uniquely 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.
     */
    public val serialName: String

    @Deprecated(
        message = "name property deprecated in the favour of serialName",
        level = DeprecationLevel.ERROR,
        replaceWith = ReplaceWith("serialName")
    )
    public val name: String
        get() = serialName

    /**
     * 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 a 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.
     */
    public val kind: SerialKind

    /**
     * Whether the descriptor describes nullable element.
     * Returns `true` if associated serializer can serialize/deserialize nullable elements of the described type.
     */
    public val isNullable: 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.
     */
    public val elementsCount: Int

    /**
     * Returns a serial annotations of the associated class.
     * Serial annotations can be used to specify an additional
     * metadata that may be used during serialization, for example a [serial id][SerialId].
     */
    public val annotations: List<Annotation> get() = emptyList()

    @Deprecated(message = "Deprecated in the favour of 'annotations' property", replaceWith = ReplaceWith("annotations"))
    public fun getEntityAnnotations(): List<Annotation> = emptyList()

    /**
     * Returns a _positional_ name of the child at the given [index].
     * Positional name usually represents a corresponding property name in the class, associated with
     * the current descriptor.
     *
     * This method may be inconsistent with [elementsCount] for collection and map types. Refer to its documentation
     * for more details.
     *
     * @throws IndexOutOfBoundsException for an illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive)
     */
    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].
     *
     * This method may be inconsistent with [elementsCount] for collection and map types. Refer to its documentation
     * for more details.
     */
    public fun getElementIndex(name: String): Int

    /**
     * Returns serial annotations of the child element at the given [index].
     * This method differs from `getElementDescriptor(index).annotations` as it reports only
     * declaration-specific annotations:
     * ```
     * @Serializable
     * @SerialName("_nested")
     * class Nested(...)
     * @Serializable
     * class Outer(@SerialId(1) val nested: Nested)
     *
     * outerDescriptor.getElementAnnotations(0) // Returns [@SerialId(1)]
     * outerDescriptor.getElementDescriptor(0).annotations // Returns [@SerialName("_nested")]
     * ```
     * This method is guaranteed to be consistent with [elementsCount].
     *
     * @throws IndexOutOfBoundsException for an illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
     */
    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 overriden
     * with `@Serializable(with = ...`)`, [Polymorphic] or [ContextualSerialization].
     * This method can be used to completely introspect the type that the current descriptor describes.
     * This method is guaranteed to be consistent with [elementsCount].
     *
     * @throws IndexOutOfBoundsException for illegal [index] values.
     * @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
     */
    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
     * )
     * ```
     * This method is guaranteed to be consistent with [elementsCount] for non-collection like classes.
     * 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).
     */
    public fun isElementOptional(index: Int): Boolean
}