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.
Inheritors
Properties
Functions
Decodes the beginning of the nested structure in a serialized form and returns CompositeDecoder responsible for decoding this very structure.
Decodes a boolean value. Corresponding kind is PrimitiveKind.BOOLEAN.
Decodes a single byte value. Corresponding kind is PrimitiveKind.BYTE.
Decodes a 16-bit unicode character value. Corresponding kind is PrimitiveKind.CHAR.
Decodes a 64-bit IEEE 754 floating point value. Corresponding kind is PrimitiveKind.DOUBLE.
Decodes a enum value and returns its index in enumDescriptor elements collection. Corresponding kind is SerialKind.ENUM.
Decodes a 32-bit IEEE 754 floating point value. Corresponding kind is PrimitiveKind.FLOAT.
Returns Decoder for decoding an underlying type of a value class in an inline manner. descriptor describes a target value class.
Decodes a 32-bit integer value. Corresponding kind is PrimitiveKind.INT.
Decodes a 64-bit integer value. Corresponding kind is PrimitiveKind.LONG.
Returns true
if the current value in decoder is not null, false otherwise. This method is usually used to decode potentially nullable data:
Decodes the null
value and returns it.
Decodes the nullable value of type T by delegating the decoding process to the given deserializer.
Decodes the value of type T by delegating the decoding process to the given deserializer. For example, decodeInt
call is equivalent to delegating integer decoding to Int.serializer: decodeSerializableValue(Int.serializer())
Decodes a 16-bit short value. Corresponding kind is PrimitiveKind.SHORT.
Decodes a string value. Corresponding kind is PrimitiveKind.STRING.
Begins a structure, decodes it using the given block, ends it and returns decoded element.