Skip to content

Commit 8fc9453

Browse files
committed
Stabilize most of SerialDescriptor and SerialKind API
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
1 parent 85b3294 commit 8fc9453

File tree

2 files changed

+59
-51
lines changed

2 files changed

+59
-51
lines changed

core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt

+58-45
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ import kotlinx.serialization.encoding.*
1111
/**
1212
* Serial descriptor is an inherent property of [KSerializer] that describes the structure of the serializable type.
1313
* The structure of the serializable type is not only the characteristic of the type itself, but also of the serializer as well,
14-
* meaning that one type can have multiple descriptors that have completely different structure.
14+
* meaning that one type can have multiple descriptors that have completely different structures.
1515
*
1616
* For example, the class `class Color(val rgb: Int)` can have multiple serializable representations,
1717
* such as `{"rgb": 255}`, `"#0000FF"`, `[0, 0, 255]` and `{"red": 0, "green": 0, "blue": 255}`.
18-
* Representations are determined by serializers and each such serializer has its own descriptor that identifies
18+
* Representations are determined by serializers, and each such serializer has its own descriptor that identifies
1919
* each structure in a distinguishable and format-agnostic manner.
2020
*
2121
* ### Structure
2222
* Serial descriptor is identified by its [name][serialName] and consists of a kind, potentially empty set of
23-
* children elements and additional metadata.
23+
* children elements, and additional metadata.
2424
*
2525
* * [serialName] uniquely identifies the descriptor (and the corresponding serializer) for non-generic types.
2626
* For generic types, the actual type substitution is omitted from the string representation, and the name
@@ -29,7 +29,7 @@ import kotlinx.serialization.encoding.*
2929
* arguments are not equal to each other.
3030
* [serialName] is typically used to specify the type of the target class during serialization of polymorphic and sealed
3131
* classes, for observability and diagnostics.
32-
* * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection etc.
32+
* * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection, etc.
3333
* * Children elements are represented as serial descriptors as well and define the structure of the type's elements.
3434
* * Metadata carries additional information, such as [nullability][nullable], [optionality][isElementOptional]
3535
* and [serial annotations][getElementAnnotations].
@@ -40,7 +40,7 @@ import kotlinx.serialization.encoding.*
4040
* #### Serialization
4141
* Serial descriptor is used as a bridge between decoders/encoders and serializers.
4242
* When asking for a next element, the serializer provides an expected descriptor to the decoder, and,
43-
* based on the descriptor content, decoder decides how to parse its input.
43+
* based on the descriptor content, the decoder decides how to parse its input.
4444
* In JSON, for example, when the encoder is asked to encode the next element and this element
4545
* is a subtype of [List], the encoder receives a descriptor with [StructureKind.LIST] and, based on that,
4646
* first writes an opening square bracket before writing the content of the list.
@@ -51,7 +51,7 @@ import kotlinx.serialization.encoding.*
5151
*
5252
* #### Introspection
5353
* Another usage of a serial descriptor is type introspection without its serialization.
54-
* Introspection can be used to check, whether the given serializable class complies the
54+
* Introspection can be used to check whether the given serializable class complies the
5555
* corresponding scheme and to generate JSON or ProtoBuf schema from the given class.
5656
*
5757
* ### Indices
@@ -60,13 +60,13 @@ import kotlinx.serialization.encoding.*
6060
* the range from zero to [elementsCount] and represent and index of the property in this class.
6161
* Consequently, primitives do not have children and their element count is zero.
6262
*
63-
* For collections and maps indices don't have fixed bound. Regular collections descriptors usually
63+
* For collections and maps indices do not have a fixed bound. Regular collections descriptors usually
6464
* have one element (`T`, maps have two, one for keys and one for values), but potentially unlimited
65-
* number of actual children values. Valid indices range is not known statically
66-
* and implementations of such descriptor should provide consistent and unbounded names and indices.
65+
* number of actual children values. Valid indices range is not known statically,
66+
* and implementations of such a descriptor should provide consistent and unbounded names and indices.
6767
*
6868
* In practice, for regular classes it is allowed to invoke `getElement*(index)` methods
69-
* with an index from `0` to [elementsCount] range and element at the particular index corresponds to the
69+
* with an index from `0` to [elementsCount] range and the element at the particular index corresponds to the
7070
* serializable property at the given position.
7171
* For collections and maps, index parameter for `getElement*(index)` methods is effectively bounded
7272
* by the maximal number of collection/map elements.
@@ -80,12 +80,12 @@ import kotlinx.serialization.encoding.*
8080
*
8181
* An [equals] implementation should use both [serialName] and elements structure.
8282
* Comparing [elementDescriptors] directly is discouraged,
83-
* because it may cause a stack overflow error, e.g. if a serializable class `T` contains elements of type `T`.
83+
* because it may cause a stack overflow error, e.g., if a serializable class `T` contains elements of type `T`.
8484
* To avoid it, a serial descriptor implementation should compare only descriptors
8585
* of class' type parameters, in a way that `serializer<Box<Int>>().descriptor != serializer<Box<String>>().descriptor`.
86-
* If type parameters are equal, descriptors structure should be compared by using children elements
86+
* If type parameters are equal, descriptor structure should be compared by using children elements
8787
* descriptors' [serialName]s, which correspond to class names
88-
* (do not confuse with elements own names, which correspond to properties names); and/or other [SerialDescriptor]
88+
* (do not confuse with elements' own names, which correspond to properties' names); and/or other [SerialDescriptor]
8989
* properties, such as [kind].
9090
* An example of [equals] implementation:
9191
* ```
@@ -128,31 +128,29 @@ import kotlinx.serialization.encoding.*
128128
* }
129129
* ```
130130
*
131-
* For a classes that are represented as a single primitive value, [PrimitiveSerialDescriptor] builder function can be used instead.
131+
* For classes that are represented as a single primitive value, [PrimitiveSerialDescriptor] builder function can be used instead.
132132
*
133133
* ### Consistency violations
134134
* An implementation of [SerialDescriptor] should be consistent with the implementation of the corresponding [KSerializer].
135-
* Yet it is not type-checked statically, thus making it possible to declare a non-consistent implementations of descriptor and serializer.
136-
* In such cases, the behaviour of an underlying format is unspecified and may lead to both runtime errors and encoding of
135+
* Yet it is not type-checked statically, thus making it possible to declare a non-consistent implementation of descriptor and serializer.
136+
* In such cases, the behavior of an underlying format is unspecified and may lead to both runtime errors and encoding of
137137
* corrupted data that is impossible to decode back.
138138
*
139-
* ### Not stable for inheritance
139+
* ### Not for implementation
140140
*
141-
* `SerialDescriptor` interface is not stable for inheritance in 3rd party libraries, as new methods
142-
* might be added to this interface or contracts of the existing methods can be changed.
143-
* This interface is safe to build using [buildClassSerialDescriptor] and [PrimitiveSerialDescriptor],
144-
* and is safe to delegate implementation to existing instances.
141+
* `SerialDescriptor` interface should not be implemented in 3rd party libraries, as new methods
142+
* might be added to this interface when kotlinx.serialization adds support for new Kotlin features.
143+
* This interface is safe to use and construct via [buildClassSerialDescriptor], [PrimitiveSerialDescriptor], and `SerialDescriptor` factory function.
145144
*/
146145
public interface SerialDescriptor {
147146
/**
148147
* Serial name of the descriptor that identifies a pair of the associated serializer and target class.
149148
*
150-
* For generated and default serializers, the serial name should be equal to the corresponding class's fully-qualified name
149+
* For generated and default serializers, the serial name is equal to the corresponding class's fully qualified name
151150
* or, if overridden, [SerialName].
152151
* Custom serializers should provide a unique serial name that identifies both the serializable class and
153-
* the serializer itself, ignoring type arguments, if they are present, for example: `my.package.LongAsTrimmedString`
152+
* the serializer itself, ignoring type arguments if they are present, for example: `my.package.LongAsTrimmedString`.
154153
*/
155-
@ExperimentalSerializationApi
156154
public val serialName: String
157155

158156
/**
@@ -163,16 +161,14 @@ public interface SerialDescriptor {
163161
* brackets, while ProtoBuf just serialize these types in separate ways.
164162
*
165163
* Kind should be consistent with the implementation, for example, if it is a [primitive][PrimitiveKind],
166-
* then its elements count should be zero and vice versa.
164+
* then its element count should be zero and vice versa.
167165
*/
168-
@ExperimentalSerializationApi
169166
public val kind: SerialKind
170167

171168
/**
172-
* Whether the descriptor describes nullable element.
169+
* Whether the descriptor describes a nullable type.
173170
* Returns `true` if associated serializer can serialize/deserialize nullable elements of the described type.
174171
*/
175-
@ExperimentalSerializationApi
176172
public val isNullable: Boolean get() = false
177173

178174
/**
@@ -192,15 +188,26 @@ public interface SerialDescriptor {
192188
* the corresponding descriptor has a single element (`IntDescriptor`, the type of list element),
193189
* but from zero up to `Int.MAX_VALUE` values in the serialized form.
194190
*/
195-
@ExperimentalSerializationApi
196191
public val elementsCount: Int
197192

198193
/**
199194
* Returns serial annotations of the associated class.
200-
* Serial annotations can be used to specify an additional metadata that may be used during serialization.
195+
* Serial annotations can be used to specify additional metadata that may be used during serialization.
201196
* Only annotations marked with [SerialInfo] are added to the resulting list.
197+
*
198+
* Do not confuse with [getElementAnnotations]:
199+
* ```
200+
* @Serializable
201+
* @OnClassSerialAnnotation
202+
* class Nested(...)
203+
*
204+
* @Serializable
205+
* class Outer(@OnPropertySerialAnnotation val nested: Nested)
206+
*
207+
* outerDescriptor.getElementAnnotations(0) // Returns [@OnPropertySerialAnnotation]
208+
* outerDescriptor.getElementDescriptor(0).annotations // Returns [@OnClassSerialAnnotation]
209+
* ```
202210
*/
203-
@ExperimentalSerializationApi
204211
public val annotations: List<Annotation> get() = emptyList()
205212

206213
/**
@@ -211,38 +218,35 @@ public interface SerialDescriptor {
211218
* @throws IndexOutOfBoundsException for an illegal [index] values.
212219
* @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive)
213220
*/
214-
@ExperimentalSerializationApi
215221
public fun getElementName(index: Int): String
216222

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

225230
/**
226231
* Returns serial annotations of the child element at the given [index].
227232
* This method differs from `getElementDescriptor(index).annotations` by reporting only
228-
* declaration-specific annotations:
233+
* element-specific annotations:
229234
* ```
230235
* @Serializable
231-
* @SomeSerialAnnotation
236+
* @OnClassSerialAnnotation
232237
* class Nested(...)
233238
*
234239
* @Serializable
235-
* class Outer(@AnotherSerialAnnotation val nested: Nested)
240+
* class Outer(@OnPropertySerialAnnotation val nested: Nested)
236241
*
237-
* outerDescriptor.getElementAnnotations(0) // Returns [@AnotherSerialAnnotation]
238-
* outerDescriptor.getElementDescriptor(0).annotations // Returns [@SomeSerialAnnotation]
242+
* outerDescriptor.getElementAnnotations(0) // Returns [@OnPropertySerialAnnotation]
243+
* outerDescriptor.getElementDescriptor(0).annotations // Returns [@OnClassSerialAnnotation]
239244
* ```
240245
* Only annotations marked with [SerialInfo] are added to the resulting list.
241246
*
242247
* @throws IndexOutOfBoundsException for an illegal [index] values.
243248
* @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
244249
*/
245-
@ExperimentalSerializationApi
246250
public fun getElementAnnotations(index: Int): List<Annotation>
247251

248252
/**
@@ -252,17 +256,29 @@ public interface SerialDescriptor {
252256
* with `@Serializable(with = ...`)`, [Polymorphic] or [Contextual].
253257
* This method can be used to completely introspect the type that the current descriptor describes.
254258
*
259+
* Example:
260+
* ```
261+
* @Serializable
262+
* @OnClassSerialAnnotation
263+
* class Nested(...)
264+
*
265+
* @Serializable
266+
* class Outer(val nested: Nested)
267+
*
268+
* outerDescriptor.getElementDescriptor(0).serialName // Returns "Nested"
269+
* outerDescriptor.getElementDescriptor(0).annotations // Returns [@OnClassSerialAnnotation]
270+
* ```
271+
*
255272
* @throws IndexOutOfBoundsException for illegal [index] values.
256273
* @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
257274
*/
258-
@ExperimentalSerializationApi
259275
public fun getElementDescriptor(index: Int): SerialDescriptor
260276

261277
/**
262278
* Whether the element at the given [index] is optional (can be absent in serialized form).
263279
* For generated descriptors, all elements that have a corresponding default parameter value are
264280
* marked as optional. Custom serializers can treat optional values in a serialization-specific manner
265-
* without default parameters constraint.
281+
* without a default parameters constraint.
266282
*
267283
* Example of optionality:
268284
* ```
@@ -275,19 +291,17 @@ public interface SerialDescriptor {
275291
* val e: List<Int> = listOf(1), // Optional == true
276292
* )
277293
* ```
278-
* Returns `false` for valid indices of collections, maps and enums.
294+
* Returns `false` for valid indices of collections, maps, and enums.
279295
*
280296
* @throws IndexOutOfBoundsException for an illegal [index] values.
281297
* @throws IllegalStateException if the current descriptor does not support children elements (e.g. is a primitive).
282298
*/
283-
@ExperimentalSerializationApi
284299
public fun isElementOptional(index: Int): Boolean
285300
}
286301

287302
/**
288303
* Returns an iterable of all descriptor [elements][SerialDescriptor.getElementDescriptor].
289304
*/
290-
@ExperimentalSerializationApi
291305
public val SerialDescriptor.elementDescriptors: Iterable<SerialDescriptor>
292306
get() = Iterable {
293307
object : Iterator<SerialDescriptor> {
@@ -303,7 +317,6 @@ public val SerialDescriptor.elementDescriptors: Iterable<SerialDescriptor>
303317
/**
304318
* Returns an iterable of all descriptor [element names][SerialDescriptor.getElementName].
305319
*/
306-
@ExperimentalSerializationApi
307320
public val SerialDescriptor.elementNames: Iterable<String>
308321
get() = Iterable {
309322
object : Iterator<String> {

core/commonMain/src/kotlinx/serialization/descriptors/SerialKinds.kt

+1-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import kotlinx.serialization.modules.*
2525
* as a single `Long` value, its descriptor should have [PrimitiveKind.LONG] without nested elements even though the class itself
2626
* represents a structure with two primitive fields.
2727
*/
28-
@ExperimentalSerializationApi
2928
public sealed class SerialKind {
3029

3130
/**
@@ -37,7 +36,6 @@ public sealed class SerialKind {
3736
*
3837
* Corresponding encoder and decoder methods are [Encoder.encodeEnum] and [Decoder.decodeEnum].
3938
*/
40-
@ExperimentalSerializationApi
4139
public object ENUM : SerialKind()
4240

4341
/**
@@ -50,7 +48,6 @@ public sealed class SerialKind {
5048
* However, if possible options are known statically (e.g. for sealed classes), they can be
5149
* enumerated in child descriptors similarly to [ENUM].
5250
*/
53-
@ExperimentalSerializationApi
5451
public object CONTEXTUAL : SerialKind()
5552

5653
override fun toString(): String {
@@ -85,7 +82,6 @@ public sealed class SerialKind {
8582
* For the `Color` example, represented as single [Int], its descriptor should have [INT] kind, zero elements and serial name **not equals**
8683
* to `kotlin.Int`: `PrimitiveDescriptor("my.package.ColorAsInt", PrimitiveKind.INT)`
8784
*/
88-
@OptIn(ExperimentalSerializationApi::class) // May be @Experimental, but break clients + makes impossible to use stable PrimitiveSerialDescriptor
8985
public sealed class PrimitiveKind : SerialKind() {
9086
/**
9187
* Primitive kind that represents a boolean `true`/`false` value.
@@ -188,7 +184,6 @@ public sealed class PrimitiveKind : SerialKind() {
188184
* For example, provided serializer for [Map.Entry] represents it as [Map] type, so it is serialized
189185
* as `{"actualKey": "actualValue"}` map directly instead of `{"key": "actualKey", "value": "actualValue"}`
190186
*/
191-
@ExperimentalSerializationApi
192187
public sealed class StructureKind : SerialKind() {
193188

194189
/**
@@ -239,7 +234,7 @@ public sealed class StructureKind : SerialKind() {
239234
* bounded and sealed polymorphism common property: not knowing the actual type statically and requiring
240235
* formats to additionally encode it.
241236
*/
242-
@ExperimentalSerializationApi
237+
@ExperimentalSerializationApi // Intentionally left experimental to sort out things with buildSerialDescriptor(PolymorphicKind.SEALED)
243238
public sealed class PolymorphicKind : SerialKind() {
244239
/**
245240
* Sealed kind represents Kotlin sealed classes, where all subclasses are known statically at the moment of declaration.

0 commit comments

Comments
 (0)