commonMain.kotlinx.serialization.encoding.Encoding.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.encoding
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.modules.*
/**
* Encoder is a core serialization primitive that encapsulates the knowledge of the underlying
* format and its storage, exposing only structural methods to the serializer, making it completely
* format-agnostic. Serialization process transforms a single value into the sequence of its
* primitive elements, also called its serial form, while encoding transforms these primitive elements into an actual
* format representation: JSON string, ProtoBuf ByteArray, in-memory map representation etc.
*
* Encoder provides high-level API that operates with basic primitive types, collections
* and nested structures. Internally, encoder represents output storage and operates with its state
* and lower level format-specific details.
*
* To be more specific, serialization transforms a value into a sequence of "here is an int, here is
* a double, here a list of strings and here is another object that is a nested int", while encoding
* transforms this sequence into a format-specific commands such as "insert opening curly bracket
* for a nested object start, insert a name of the value, and the value separated with colon for an int etc."
*
* The symmetric interface for the deserialization process is [Decoder].
*
* ### Serialization. Primitives
*
* If a class is represented as a single [primitive][PrimitiveKind] value in its serialized form,
* then one of the `encode*` methods (e.g. [encodeInt]) can be used directly.
*
* ### Serialization. Structured types.
*
* If a class is represented as a structure or has multiple values in its serialized form,
* `encode*` methods are not that helpful, because they do not allow working with collection types or establish structure boundaries.
* All these capabilities are delegated to the [CompositeEncoder] interface with a more specific API surface.
* To denote a structure start, [beginStructure] should be used.
* ```
* // Denote the structure start,
* val composite = encoder.beginStructure(descriptor)
* // Encoding all elements within the structure using 'composite'
* ...
* // Denote the structure end
* composite.endStructure(descriptor)
* ```
*
* E.g. if the encoder belongs to JSON format, then [beginStructure] will write an opening bracket
* (`{` or `[`, depending on the descriptor kind), returning the [CompositeEncoder] that is aware of colon separator,
* that should be appended between each key-value pair, whilst [CompositeEncoder.endStructure] will write a closing bracket.
*
* ### Exception guarantees.
* For the regular exceptions, such as invalid input, conflicting serial names,
* [SerializationException] can be thrown by any encoder methods.
* It is recommended to declare a format-specific subclass of [SerializationException] and throw it.
*
* ### Format encapsulation
*
* For example, for the following serializer:
* ```
* class StringHolder(val stringValue: String)
*
* object StringPairDeserializer : SerializationStrategy {
* override val descriptor = ...
*
* override fun serializer(encoder: Encoder, value: StringHolder) {
* // Denotes start of the structure, StringHolder is not a "plain" data type
* val composite = encoder.beginStructure(descriptor)
* // Encode the nested string value
* composite.encodeStringElement(descriptor, index = 0)
* // Denotes end of the structure
* composite.endStructure(descriptor)
* }
* }
* ```
*
* This serializer does not know anything about the underlying storage and will work with any properly-implemented encoder.
* JSON, for example, writes an opening bracket `{` during the `beginStructure` call, writes 'stringValue` key along
* with its value in `encodeStringElement` and writes the closing bracket `}` during the `endStructure`.
* XML would do roughly the same, but with different separators and structures, while ProtoBuf
* machinery could be completely different.
* In any case, all these parsing details are encapsulated by an encoder.
*
* ### Exception safety
*
* In general, catching [SerializationException] from any of `encode*` methods is not allowed and produces unspecified behaviour.
* After thrown exception, current encoder is left in an arbitrary state, no longer suitable for further encoding.
*
* ### Encoder implementation.
*
* While being strictly typed, an underlying format can transform actual types in the way it wants.
* For example, a format can support only string types and encode/decode all primitives in a string form:
* ```
* StringFormatEncoder : Encoder {
*
* ...
* override fun encodeDouble(value: Double) = encodeString(value.toString())
* override fun encodeInt(value: Int) = encodeString(value.toString())
* ...
* }
* ```
*
* ### Not stable for inheritance
*
* `Encoder` 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.
*/
public interface Encoder {
/**
* Context of the current serialization process, including contextual and polymorphic serialization and,
* potentially, a format-specific configuration.
*/
public val serializersModule: SerializersModule
/**
* Notifies the encoder that value of a nullable type that is
* being serialized is not null. It should be called before writing a non-null value
* of nullable type:
* ```
* // Could be String? serialize method
* if (value != null) {
* encoder.encodeNotNullMark()
* encoder.encodeStringValue(value)
* } else {
* encoder.encodeNull()
* }
* ```
*
* This method has a use in highly-performant binary formats and can
* be safely ignore by most of the regular formats.
*/
@ExperimentalSerializationApi
public fun encodeNotNullMark() {}
/**
* Encodes `null` value.
*/
@ExperimentalSerializationApi
public fun encodeNull()
/**
* Encodes a boolean value.
* Corresponding kind is [PrimitiveKind.BOOLEAN].
*/
public fun encodeBoolean(value: Boolean)
/**
* Encodes a single byte value.
* Corresponding kind is [PrimitiveKind.BYTE].
*/
public fun encodeByte(value: Byte)
/**
* Encodes a 16-bit short value.
* Corresponding kind is [PrimitiveKind.SHORT].
*/
public fun encodeShort(value: Short)
/**
* Encodes a 16-bit unicode character value.
* Corresponding kind is [PrimitiveKind.CHAR].
*/
public fun encodeChar(value: Char)
/**
* Encodes a 32-bit integer value.
* Corresponding kind is [PrimitiveKind.INT].
*/
public fun encodeInt(value: Int)
/**
* Encodes a 64-bit integer value.
* Corresponding kind is [PrimitiveKind.LONG].
*/
public fun encodeLong(value: Long)
/**
* Encodes a 32-bit IEEE 754 floating point value.
* Corresponding kind is [PrimitiveKind.FLOAT].
*/
public fun encodeFloat(value: Float)
/**
* Encodes a 64-bit IEEE 754 floating point value.
* Corresponding kind is [PrimitiveKind.DOUBLE].
*/
public fun encodeDouble(value: Double)
/**
* Encodes a string value.
* Corresponding kind is [PrimitiveKind.STRING].
*/
public fun encodeString(value: String)
/**
* Encodes a enum value that is stored at the [index] in [enumDescriptor] elements collection.
* Corresponding kind is [SerialKind.ENUM].
*
* E.g. for the enum `enum class Letters { A, B, C, D }` and
* serializable value "C", [encodeEnum] method should be called with `2` as am index.
*
* This method does not imply any restrictions on the output format,
* the format is free to store the enum by its name, index, ordinal or any other
*/
public fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int)
/**
* Returns [Encoder] for encoding an underlying type of a value class in an inline manner.
* [descriptor] describes a serializable value class.
*
* Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`,
* the following sequence is used:
* ```
* thisEncoder.encodeInline(MyInt.serializer().descriptor).encodeInt(my)
* ```
*
* Current encoder may return any other instance of [Encoder] class, depending on the provided [descriptor].
* For example, when this function is called on Json encoder with `UInt.serializer().descriptor`, the returned encoder is able
* to encode unsigned integers.
*
* Note that this function returns [Encoder] instead of the [CompositeEncoder]
* because value classes always have the single property.
* Calling [Encoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited.
*/
public fun encodeInline(descriptor: SerialDescriptor): Encoder
/**
* Encodes the beginning of the nested structure in a serialized form
* and returns [CompositeDecoder] responsible for encoding this very structure.
* E.g the hierarchy:
* ```
* class StringHolder(val stringValue: String)
* class Holder(val stringHolder: StringHolder)
* ```
*
* with the following serialized form in JSON:
* ```
* {
* "stringHolder" : { "stringValue": "value" }
* }
* ```
*
* will be roughly represented as the following sequence of calls:
* ```
* // Holder serializer
* fun serialize(encoder: Encoder, value: Holder) {
* val composite = encoder.beginStructure(descriptor) // the very first opening bracket '{'
* composite.encodeSerializableElement(descriptor, 0, value.stringHolder) // Serialize nested StringHolder
* composite.endStructure(descriptor) // The very last closing bracket
* }
*
* // StringHolder serializer
* fun serialize(encoder: Encoder, value: StringHolder) {
* val composite = encoder.beginStructure(descriptor) // One more '{' when the key "stringHolder" is already written
* composite.encodeStringElement(descriptor, 0, value.stringValue) // Serialize actual value
* composite.endStructure(descriptor) // Closing bracket
* }
* ```
*/
public fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder
/**
* Encodes the beginning of the collection with size [collectionSize] and the given serializer of its type parameters.
* This method has to be implemented only if you need to know collection size in advance, otherwise, [beginStructure] can be used.
*/
public fun beginCollection(
descriptor: SerialDescriptor,
collectionSize: Int
): CompositeEncoder = beginStructure(descriptor)
/**
* Encodes the [value] of type [T] by delegating the encoding process to the given [serializer].
* For example, `encodeInt` call us equivalent to delegating integer encoding to [Int.serializer][Int.Companion.serializer]:
* `encodeSerializableValue(Int.serializer())`
*/
public fun encodeSerializableValue(serializer: SerializationStrategy, value: T) {
serializer.serialize(this, value)
}
/**
* Encodes the nullable [value] of type [T] by delegating the encoding process to the given [serializer].
*/
@Suppress("UNCHECKED_CAST")
@ExperimentalSerializationApi
public fun encodeNullableSerializableValue(serializer: SerializationStrategy, value: T?) {
val isNullabilitySupported = serializer.descriptor.isNullable
if (isNullabilitySupported) {
// Instead of `serializer.serialize` to be able to intercept this
return encodeSerializableValue(serializer as SerializationStrategy, value)
}
// Else default path used to avoid allocation of NullableSerializer
if (value == null) {
encodeNull()
} else {
encodeNotNullMark()
encodeSerializableValue(serializer, value)
}
}
}
/**
* [CompositeEncoder] is a part of encoding process that is bound to a particular structured part of
* the serialized form, described by the serial descriptor passed to [Encoder.beginStructure].
*
* All `encode*` methods have `index` and `serialDescriptor` parameters with a strict semantics and constraints:
* * `descriptor` is always the same as one used in [Encoder.beginStructure]. While this parameter may seem redundant,
* it is required for efficient serialization process to avoid excessive field spilling.
* If you are writing your own format, you can safely ignore this parameter and use one used in `beginStructure`
* for simplicity.
* * `index` of the element being encoded. This element at this index in the descriptor should be associated with
* the one being written.
*
* The symmetric interface for the deserialization process is [CompositeDecoder].
*
* ### Not stable for inheritance
*
* `CompositeEncoder` 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.
*/
public interface CompositeEncoder {
/**
* Context of the current serialization process, including contextual and polymorphic serialization and,
* potentially, a format-specific configuration.
*/
public val serializersModule: SerializersModule
/**
* Denotes the end of the structure associated with current encoder.
* For example, composite encoder of JSON format will write
* a closing bracket in the underlying input and reduce the number of nesting for pretty printing.
*/
public fun endStructure(descriptor: SerialDescriptor)
/**
* Whether the format should encode values that are equal to the default values.
* This method is used by plugin-generated serializers for properties with default values:
* ```
* @Serializable
* class WithDefault(val int: Int = 42)
* // serialize method
* if (value.int != 42 || output.shouldEncodeElementDefault(serialDesc, 0)) {
* encoder.encodeIntElement(serialDesc, 0, value.int);
* }
* ```
*
* This method is never invoked for properties annotated with [EncodeDefault].
*/
@ExperimentalSerializationApi
public fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = true
/**
* Encodes a boolean [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.BOOLEAN] kind.
*/
public fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean)
/**
* Encodes a single byte [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.BYTE] kind.
*/
public fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte)
/**
* Encodes a 16-bit short [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.SHORT] kind.
*/
public fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short)
/**
* Encodes a 16-bit unicode character [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.CHAR] kind.
*/
public fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char)
/**
* Encodes a 32-bit integer [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.INT] kind.
*/
public fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int)
/**
* Encodes a 64-bit integer [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.LONG] kind.
*/
public fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long)
/**
* Encodes a 32-bit IEEE 754 floating point [value] associated with an element
* at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.FLOAT] kind.
*/
public fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float)
/**
* Encodes a 64-bit IEEE 754 floating point [value] associated with an element
* at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.DOUBLE] kind.
*/
public fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double)
/**
* Encodes a string [value] associated with an element at the given [index] in [serial descriptor][descriptor].
* The element at the given [index] should have [PrimitiveKind.STRING] kind.
*/
public fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String)
/**
* Returns [Encoder] for decoding an underlying type of a value class in an inline manner.
* Serializable value class is described by the [child descriptor][SerialDescriptor.getElementDescriptor]
* of given [descriptor] at [index].
*
* Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`,
* and `@Serializable class MyData(val myInt: MyInt)` the following sequence is used:
* ```
* thisEncoder.encodeInlineElement(MyData.serializer.descriptor, 0).encodeInt(my)
* ```
*
* This method provides an opportunity for the optimization to avoid boxing of a carried value
* and its invocation should be equivalent to the following:
* ```
* thisEncoder.encodeSerializableElement(MyData.serializer.descriptor, 0, MyInt.serializer(), myInt)
* ```
*
* Current encoder may return any other instance of [Encoder] class, depending on provided descriptor.
* For example, when this function is called on Json encoder with descriptor that has
* `UInt.serializer().descriptor` at the given [index], the returned encoder is able
* to encode unsigned integers.
*
* Note that this function returns [Encoder] instead of the [CompositeEncoder]
* because value classes always have the single property.
* Calling [Encoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited.
*
* @see Encoder.encodeInline
* @see SerialDescriptor.getElementDescriptor
*/
public fun encodeInlineElement(
descriptor: SerialDescriptor,
index: Int
): Encoder
/**
* Delegates [value] encoding of the type [T] to the given [serializer].
* [value] is associated with an element at the given [index] in [serial descriptor][descriptor].
*/
public fun encodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
serializer: SerializationStrategy,
value: T
)
/**
* Delegates nullable [value] encoding of the type [T] to the given [serializer].
* [value] is associated with an element at the given [index] in [serial descriptor][descriptor].
*/
@ExperimentalSerializationApi
public fun encodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
serializer: SerializationStrategy,
value: T?
)
}
/**
* Begins a structure, encodes it using the given [block] and ends it.
*/
public inline fun Encoder.encodeStructure(
descriptor: SerialDescriptor,
crossinline block: CompositeEncoder.() -> Unit
) {
val composite = beginStructure(descriptor)
composite.block()
composite.endStructure(descriptor)
}
/**
* Begins a collection, encodes it using the given [block] and ends it.
*/
public inline fun Encoder.encodeCollection(
descriptor: SerialDescriptor,
collectionSize: Int,
crossinline block: CompositeEncoder.() -> Unit
) {
val composite = beginCollection(descriptor, collectionSize)
composite.block()
composite.endStructure(descriptor)
}
/**
* Begins a collection, calls [block] with each item and ends the collections.
*/
public inline fun Encoder.encodeCollection(
descriptor: SerialDescriptor,
collection: Collection,
crossinline block: CompositeEncoder.(index: Int, E) -> Unit
) {
encodeCollection(descriptor, collection.size) {
collection.forEachIndexed { index, e ->
block(index, e)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy