Decoder

interface Decoder

Decoder is a core deserialization primitive that encapsulates the knowledge of the underlying format and an underlying storage, exposing only structural methods to the deserializer, making it completely format-agnostic. Deserialization process takes a decoder and asks him for a sequence of primitive elements, defined by a deserializer serial form, while decoder knows how to retrieve these primitive elements from an actual format representations.

Decoder provides high-level API that operates with basic primitive types, collections and nested structures. Internally, the decoder represents input storage, and operates with its state and lower level format-specific details.

To be more specific, serialization asks a decoder for a sequence of "give me an int, give me a double, give me a list of strings and give me another object that is a nested int", while decoding transforms this sequence into a format-specific commands such as "parse the part of the string until the next quotation mark as an int to retrieve an int, parse everything within the next curly braces to retrieve elements of a nested object etc."

The symmetric interface for the serialization process is Encoder.

Deserialization. Primitives

If a class is represented as a single primitive value in its serialized form, then one of the decode* methods (e.g. decodeInt) can be used directly.

Deserialization. Structured types

If a class is represented as a structure or has multiple values in its serialized form, decode* methods are not that helpful, because format may not require a strict order of data (e.g. JSON or XML), do not allow working with collection types or establish structure boundaries. All these capabilities are delegated to the CompositeDecoder interface with a more specific API surface. To denote a structure start, beginStructure should be used.

// Denote the structure start,
val composite = decoder.beginStructure(descriptor)
// Decode all elements within the structure using 'composite'
...
// Denote the structure end
composite.endStructure(descriptor)

E.g. if the decoder belongs to JSON format, then beginStructure will parse an opening bracket ({ or [, depending on the descriptor kind), returning the CompositeDecoder that is aware of colon separator, that should be read after each key-value pair, whilst CompositeDecoder.endStructure will parse a closing bracket.

Exception guarantees.

For the regular exceptions, such as invalid input, missing control symbols or attributes and unknown symbols, SerializationException can be thrown by any decoder methods. It is recommended to declare a format-specific subclass of SerializationException and throw it.

Format encapsulation

For example, for the following deserializer:

class StringHolder(val stringValue: String)

object StringPairDeserializer : DeserializationStrategy<StringHolder> {
override val descriptor = ...

override fun deserializer(decoder: Decoder): StringHolder {
// Denotes start of the structure, StringHolder is not a "plain" data type
val composite = decoder.beginStructure(descriptor)
if (composite.decodeElementIndex(descriptor) != 0)
throw MissingFieldException("Field 'stringValue' is missing")
// Decode the nested string value
val value = composite.decodeStringElement(descriptor, index = 0)
// Denotes end of the structure
composite.endStructure(descriptor)
}
}

Exception safety

In general, catching SerializationException from any of decode* methods is not allowed and produces unspecified behaviour. After thrown exception, current decoder is left in an arbitrary state, no longer suitable for further decoding.

This deserializer does not know anything about the underlying data and will work with any properly-implemented decoder. JSON, for example, parses an opening bracket { during the beginStructure call, checks that the next key after this bracket is stringValue (using the descriptor), returns the value after the colon as string value and parses closing bracket } during the endStructure. XML would do roughly the same, but with different separators and parsing structures, while ProtoBuf machinery could be completely different. In any case, all these parsing details are encapsulated by a decoder.

Decoder 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:

StringFormatDecoder : Decoder {

...
override fun decodeDouble(): Double = decodeString().toDouble()
override fun decodeInt(): Int = decodeString().toInt()
...
}

Not stable for inheritance

Decoder 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.

Functions

Link copied to clipboard
abstract fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder

Decodes the beginning of the nested structure in a serialized form and returns CompositeDecoder responsible for decoding this very structure.

Link copied to clipboard
abstract fun decodeBoolean(): Boolean

Decodes a boolean value. Corresponding kind is PrimitiveKind.BOOLEAN.

Link copied to clipboard
abstract fun decodeByte(): Byte

Decodes a single byte value. Corresponding kind is PrimitiveKind.BYTE.

Link copied to clipboard
abstract fun decodeChar(): Char

Decodes a 16-bit unicode character value. Corresponding kind is PrimitiveKind.CHAR.

Link copied to clipboard
abstract fun decodeDouble(): Double

Decodes a 64-bit IEEE 754 floating point value. Corresponding kind is PrimitiveKind.DOUBLE.

Link copied to clipboard
abstract fun decodeEnum(enumDescriptor: SerialDescriptor): Int

Decodes a enum value and returns its index in enumDescriptor elements collection. Corresponding kind is SerialKind.ENUM.

Link copied to clipboard
abstract fun decodeFloat(): Float

Decodes a 32-bit IEEE 754 floating point value. Corresponding kind is PrimitiveKind.FLOAT.

Link copied to clipboard
abstract fun decodeInline(descriptor: SerialDescriptor): Decoder

Returns Decoder for decoding an underlying type of a value class in an inline manner. descriptor describes a target value class.

Link copied to clipboard
abstract fun decodeInt(): Int

Decodes a 32-bit integer value. Corresponding kind is PrimitiveKind.INT.

Link copied to clipboard
abstract fun decodeLong(): Long

Decodes a 64-bit integer value. Corresponding kind is PrimitiveKind.LONG.

Link copied to clipboard
abstract fun decodeNotNullMark(): Boolean

Returns true if the current value in decoder is not null, false otherwise. This method is usually used to decode potentially nullable data:

Link copied to clipboard
abstract fun decodeNull(): Nothing?

Decodes the null value and returns it.

Link copied to clipboard
open fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T?

Decodes the nullable value of type T by delegating the decoding process to the given deserializer.

Link copied to clipboard
open fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T

Decodes the value of type T by delegating the decoding process to the given deserializer. For example, decodeInt call us equivalent to delegating integer decoding to Int.serializer: decodeSerializableValue(IntSerializer)

Link copied to clipboard
abstract fun decodeShort(): Short

Decodes a 16-bit short value. Corresponding kind is PrimitiveKind.SHORT.

Link copied to clipboard
abstract fun decodeString(): String

Decodes a string value. Corresponding kind is PrimitiveKind.STRING.

Properties

Link copied to clipboard
abstract val serializersModule: SerializersModule

Context of the current serialization process, including contextual and polymorphic serialization and, potentially, a format-specific configuration.

Inheritors

Link copied to clipboard

Extensions

Link copied to clipboard
inline fun <T> Decoder.decodeStructure(descriptor: SerialDescriptor, crossinline block: CompositeDecoder.() -> T): T

Begins a structure, decodes it using the given block, ends it and returns decoded element.