Encoder
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 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<StringHolder> {
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 'stringValuekey along with its value in
encodeStringElementand 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.
Inheritors
Properties
Functions
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.
Encodes the beginning of the nested structure in a serialized form and returns CompositeDecoder responsible for encoding this very structure. E.g the hierarchy:
Encodes a boolean value. Corresponding kind is PrimitiveKind.BOOLEAN.
Encodes a single byte value. Corresponding kind is PrimitiveKind.BYTE.
Encodes a 16-bit unicode character value. Corresponding kind is PrimitiveKind.CHAR.
Begins a collection, encodes it using the given block and ends it.
Begins a collection, calls block with each item and ends the collections.
Encodes a 64-bit IEEE 754 floating point value. Corresponding kind is PrimitiveKind.DOUBLE.
Encodes a enum value that is stored at the index in enumDescriptor elements collection. Corresponding kind is SerialKind.ENUM.
Encodes a 32-bit IEEE 754 floating point value. Corresponding kind is PrimitiveKind.FLOAT.
Returns Encoder for encoding an underlying type of a value class in an inline manner. descriptor describes a serializable value class.
Encodes a 32-bit integer value. Corresponding kind is PrimitiveKind.INT.
Encodes a 64-bit integer value. Corresponding kind is PrimitiveKind.LONG.
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:
Encodes null
value.
Encodes the nullable value of type T by delegating the encoding process to the given serializer.
Encodes the value of type T by delegating the encoding process to the given serializer. For example, encodeInt
call is equivalent to delegating integer encoding to Int.serializer: encodeSerializableValue(Int.serializer())
Encodes a 16-bit short value. Corresponding kind is PrimitiveKind.SHORT.
Encodes a string value. Corresponding kind is PrimitiveKind.STRING.
Begins a structure, encodes it using the given block and ends it.