Kotlin multiplatform serialization runtime library core module with JVM target support
—
The encoding and decoding system provides the core interfaces that serialization formats use to read and write structured data. These interfaces abstract away format-specific details, allowing serializers to work with any format that implements the encoder/decoder contracts.
The fundamental interfaces for writing serialized data in a format-agnostic way.
/**
* Core serialization primitive that encapsulates format-specific knowledge for writing data.
* Provides methods for encoding primitive values and initiating structured encoding.
*/
interface Encoder {
/** The serializers module for resolving contextual and polymorphic serializers */
val serializersModule: SerializersModule
/** Begin encoding a structured value, returning a CompositeEncoder for the structure */
fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder
/** Encode a boolean value */
fun encodeBoolean(value: Boolean)
/** Encode a byte value */
fun encodeByte(value: Byte)
/** Encode a char value */
fun encodeChar(value: Char)
/** Encode a double value */
fun encodeDouble(value: Double)
/** Encode an enum value */
fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int)
/** Encode a float value */
fun encodeFloat(value: Float)
/** Encode an inline value (value class) */
fun encodeInline(descriptor: SerialDescriptor): Encoder
/** Encode an int value */
fun encodeInt(value: Int)
/** Encode a long value */
fun encodeLong(value: Long)
/** Encode a null value */
fun encodeNull()
/** Encode a short value */
fun encodeShort(value: Short)
/** Encode a string value */
fun encodeString(value: String)
}
/**
* Part of the encoding process that handles structured data like classes, lists, and maps.
* Bound to a specific structured part of the serialized form.
*/
interface CompositeEncoder {
/** The serializers module for resolving contextual and polymorphic serializers */
val serializersModule: SerializersModule
/** Finish encoding the structure */
fun endStructure(descriptor: SerialDescriptor)
/** Check if element at given index should be encoded */
fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean
/** Encode a boolean element at the specified index */
fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean)
/** Encode a byte element at the specified index */
fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte)
/** Encode a char element at the specified index */
fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char)
/** Encode a double element at the specified index */
fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double)
/** Encode a float element at the specified index */
fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float)
/** Encode an inline element at the specified index */
fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder
/** Encode an int element at the specified index */
fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int)
/** Encode a long element at the specified index */
fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long)
/** Encode a null element at the specified index */
fun encodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
serializer: SerializationStrategy<T>,
value: T?
)
/** Encode a serializable element at the specified index */
fun encodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
serializer: SerializationStrategy<T>,
value: T
)
/** Encode a short element at the specified index */
fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short)
/** Encode a string element at the specified index */
fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String)
}The fundamental interfaces for reading serialized data in a format-agnostic way.
/**
* Core deserialization primitive for format-agnostic decoding of structured data.
* Provides methods for decoding primitive values and initiating structured decoding.
*/
interface Decoder {
/** The serializers module for resolving contextual and polymorphic serializers */
val serializersModule: SerializersModule
/** Begin decoding a structured value, returning a CompositeDecoder for the structure */
fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder
/** Decode a boolean value */
fun decodeBoolean(): Boolean
/** Decode a byte value */
fun decodeByte(): Byte
/** Decode a char value */
fun decodeChar(): Char
/** Decode a double value */
fun decodeDouble(): Double
/** Decode an enum value, returning the index */
fun decodeEnum(enumDescriptor: SerialDescriptor): Int
/** Decode a float value */
fun decodeFloat(): Float
/** Decode an inline value (value class) */
fun decodeInline(descriptor: SerialDescriptor): Decoder
/** Decode an int value */
fun decodeInt(): Int
/** Decode a long value */
fun decodeLong(): Long
/** Decode a null value, returning false if null */
@ExperimentalSerializationApi
fun decodeNotNullMark(): Boolean
/** Decode a null value */
@ExperimentalSerializationApi
fun decodeNull(): Nothing?
/** Decode a short value */
fun decodeShort(): Short
/** Decode a string value */
fun decodeString(): String
}
/**
* Part of the decoding process that handles structured data like classes, lists, and maps.
* Bound to a specific structured part of the serialized form.
*/
interface CompositeDecoder {
/** The serializers module for resolving contextual and polymorphic serializers */
val serializersModule: SerializersModule
/** Finish decoding the structure */
fun endStructure(descriptor: SerialDescriptor)
/** Decode the next element index, returning CompositeDecoder.DECODE_DONE when finished */
fun decodeElementIndex(descriptor: SerialDescriptor): Int
/** Decode a boolean element at the specified index */
fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean
/** Decode a byte element at the specified index */
fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte
/** Decode a char element at the specified index */
fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char
/** Decode a double element at the specified index */
fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double
/** Decode a float element at the specified index */
fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float
/** Decode an inline element at the specified index */
fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder
/** Decode an int element at the specified index */
fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int
/** Decode a long element at the specified index */
fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long
/** Decode a nullable serializable element at the specified index */
fun decodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T? = null
): T?
/** Decode a serializable element at the specified index */
fun decodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T>,
previousValue: T? = null
): T
/** Decode a short element at the specified index */
fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short
/** Decode a string element at the specified index */
fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String
companion object {
/** Constant indicating that decoding is complete */
const val DECODE_DONE: Int = -1
/** Constant indicating unknown element index */
const val UNKNOWN_NAME: Int = -3
}
}Skeleton implementations that provide common functionality for format implementations.
/**
* Skeleton implementation of both Encoder and CompositeEncoder.
* Provides default implementations for common encoding patterns.
*/
abstract class AbstractEncoder : Encoder, CompositeEncoder {
/** Default implementation delegates to CompositeEncoder methods */
final override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder = this
/** Default implementation calls endStructure */
override fun endStructure(descriptor: SerialDescriptor) {}
/** Default implementation returns true for required elements, false for optional */
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean =
!descriptor.isElementOptional(index)
}
/**
* Skeleton implementation of both Decoder and CompositeDecoder.
* Provides default implementations for common decoding patterns.
*/
abstract class AbstractDecoder : Decoder, CompositeDecoder {
/** Default implementation delegates to CompositeDecoder methods */
final override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = this
/** Default implementation calls endStructure */
override fun endStructure(descriptor: SerialDescriptor) {}
}Additional interfaces for specific encoding/decoding scenarios.
/**
* Decoder interface that supports consuming large strings by chunks.
* Useful for streaming formats that need to process large text data efficiently.
*/
interface ChunkedDecoder {
/**
* Decodes a string value by chunks, calling the consumer for each chunk.
* @param consumeChunk Function to consume each string chunk
*/
fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit)
}Convenient extension functions that simplify common encoding patterns.
/**
* Encodes structured values using CompositeEncoder in a convenient DSL style.
* @param descriptor The descriptor for the structure being encoded
* @param block Lambda that performs the encoding using CompositeEncoder
*/
inline fun Encoder.encodeStructure(
descriptor: SerialDescriptor,
crossinline block: CompositeEncoder.() -> Unit
)
/**
* Convenience method for encoding collections with consistent element handling.
* @param descriptor The descriptor for the collection
* @param collection The collection to encode
* @param block Lambda to encode each collection element
*/
inline fun <T> Encoder.encodeCollection(
descriptor: SerialDescriptor,
collection: Collection<T>,
crossinline block: CompositeEncoder.(index: Int, element: T) -> Unit
)Convenient extension functions that simplify common decoding patterns.
/**
* Decodes structured values using CompositeDecoder in a convenient DSL style.
* @param descriptor The descriptor for the structure being decoded
* @param block Lambda that performs the decoding using CompositeDecoder
* @return The decoded value
*/
inline fun <T> Decoder.decodeStructure(
descriptor: SerialDescriptor,
crossinline block: CompositeDecoder.() -> T
): TUsage Examples:
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
// Custom serializer using encoding interfaces
object PointSerializer : KSerializer<Point> {
override val descriptor = buildClassSerialDescriptor("Point") {
element("x", PrimitiveSerialDescriptor("kotlin.Int", PrimitiveKind.INT))
element("y", PrimitiveSerialDescriptor("kotlin.Int", PrimitiveKind.INT))
}
override fun serialize(encoder: Encoder, value: Point) {
encoder.encodeStructure(descriptor) {
encodeIntElement(descriptor, 0, value.x)
encodeIntElement(descriptor, 1, value.y)
}
}
override fun deserialize(decoder: Decoder): Point {
return decoder.decodeStructure(descriptor) {
var x = 0
var y = 0
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> x = decodeIntElement(descriptor, index)
1 -> y = decodeIntElement(descriptor, index)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
Point(x, y)
}
}
}When implementing a new format, you typically extend the abstract base classes:
import kotlinx.serialization.encoding.*
import kotlinx.serialization.modules.*
class MyFormatEncoder : AbstractEncoder() {
override val serializersModule: SerializersModule = EmptySerializersModule
override fun encodeString(value: String) {
// Format-specific string encoding
}
override fun encodeInt(value: Int) {
// Format-specific int encoding
}
// Implement other encoding methods...
}
class MyFormatDecoder : AbstractDecoder() {
override val serializersModule: SerializersModule = EmptySerializersModule
override fun decodeString(): String {
// Format-specific string decoding
}
override fun decodeInt(): Int {
// Format-specific int decoding
}
// Implement other decoding methods...
}Different formats may require different decoding approaches:
// Sequential decoding (streaming formats)
override fun deserialize(decoder: Decoder): MyClass {
return decoder.decodeStructure(descriptor) {
var field1: String? = null
var field2: Int? = null
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> field1 = decodeStringElement(descriptor, index)
1 -> field2 = decodeIntElement(descriptor, index)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
MyClass(field1!!, field2!!)
}
}
// Random access decoding (structured formats like JSON objects)
override fun deserialize(decoder: Decoder): MyClass {
return decoder.decodeStructure(descriptor) {
MyClass(
decodeStringElement(descriptor, 0),
decodeIntElement(descriptor, 1)
)
}
}Common encoding/decoding errors and their causes:
Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-core-jvm