commonMain.kotlinx.serialization.descriptors.SerialDescriptor.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-jupyter-script-classpath-shadowed Show documentation
Show all versions of kotlin-jupyter-script-classpath-shadowed Show documentation
Kotlin Jupyter kernel script classpath with all dependencies inside one artifact
/*
* 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.builtins.*
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 characteristic of the type itself, 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
* arguments, 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 etc.
* * Children elements are represented as serial descriptors as well and define the structure of the type's elements.
* * Metadata carries additional 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 a 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 indices don't 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 such 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 from `0` to [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 bounded
* 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>().descriptor != serializer>().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 [buildClassSerialDescriptor] builder function, where
* for each serializable property the 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 // This field is written as regular list of strings
* )
*
* // Descriptor for such class:
* buildClassSerialDescriptor("my.package.Data") {
* // intField is deliberately ignored by serializer -- not present in the descriptor as well
* element("_longField") // longField is named as _longField
* element("stringField", listSerialDescriptor())
* }
*
* // Example of 'serialize' function for such descriptor
* override fun serialize(encoder: Encoder, value: Data) {
* encoder.encodeStructure(descriptor) {
* encodeLongElement(descriptor, 0, value.longField) // Will be written as "_longField" because descriptor's child at index 0 says so
* encodeSerializableElement(descriptor, 1, ListSerializer(String.serializer()), value.stringList)
* }
* }
* ```
*
* For a classes that are represented as a single primitive value, [PrimitiveSerialDescriptor] builder function can be used instead.
*
* ### Consistency violations
* An implementation of [SerialDescriptor] should be consistent with the implementation of the corresponding [KSerializer].
* Yet it is not type-checked statically, thus making it possible to declare a non-consistent implementations of descriptor and serializer.
* In such cases, the behaviour of an underlying format is unspecified and may lead to both runtime errors and encoding of
* corrupted data that is impossible to decode back.
*
* ### 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 value class which underlying value
* is serialized directly.
*/
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()`
* 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 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
/**
* 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 in 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, // Optional == false
* val e: List = 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
get() = Iterable {
object : Iterator {
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
get() = Iterable {
object : Iterator {
private var elementsLeft = elementsCount
override fun hasNext(): Boolean = elementsLeft > 0
override fun next(): String {
return getElementName(elementsCount - (elementsLeft--))
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy