summaryrefslogtreecommitdiff
path: root/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
blob: 2b1dd09cc889ef5764358ca43616a1e6ee4a3e59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
/*
 * 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<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 '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 <T : Any?> encodeSerializableValue(serializer: SerializationStrategy<T>, 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 <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?) {
        val isNullabilitySupported = serializer.descriptor.isNullable
        if (isNullabilitySupported) {
            // Instead of `serializer.serialize` to be able to intercept this
            return encodeSerializableValue(serializer as SerializationStrategy<T?>, 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 <T : Any?> encodeSerializableElement(
        descriptor: SerialDescriptor,
        index: Int,
        serializer: SerializationStrategy<T>,
        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 <T : Any> encodeNullableSerializableElement(
        descriptor: SerialDescriptor,
        index: Int,
        serializer: SerializationStrategy<T>,
        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 <E> Encoder.encodeCollection(
    descriptor: SerialDescriptor,
    collection: Collection<E>,
    crossinline block: CompositeEncoder.(index: Int, E) -> Unit
) {
    encodeCollection(descriptor, collection.size) {
        collection.forEachIndexed { index, e ->
            block(index, e)
        }
    }
}