0
# Encoding and Decoding
1
2
Complete reference for the low-level encoding and decoding APIs used by format implementors in kotlinx.serialization-core-js.
3
4
## Core Encoding Interfaces
5
6
### Encoder
7
8
Primary interface for encoding values into a specific format.
9
10
```kotlin
11
interface Encoder {
12
val serializersModule: SerializersModule
13
14
fun encodeBoolean(value: Boolean)
15
fun encodeByte(value: Byte)
16
fun encodeChar(value: Char)
17
fun encodeShort(value: Short)
18
fun encodeInt(value: Int)
19
fun encodeLong(value: Long)
20
fun encodeFloat(value: Float)
21
fun encodeDouble(value: Double)
22
fun encodeString(value: String)
23
24
fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int)
25
fun encodeNull()
26
fun encodeNotNullMark()
27
fun encodeInline(descriptor: SerialDescriptor): Encoder
28
29
fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder
30
fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder
31
32
fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T)
33
fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?)
34
}
35
```
36
{ .api }
37
38
**Usage:**
39
40
```javascript
41
class JsonEncoder {
42
constructor(serializersModule) {
43
this.serializersModule = serializersModule;
44
this.output = "";
45
}
46
47
encodeString(value) {
48
this.output += `"${this.escapeString(value)}"`;
49
}
50
51
encodeInt(value) {
52
this.output += value.toString();
53
}
54
55
encodeBoolean(value) {
56
this.output += value ? "true" : "false";
57
}
58
59
encodeNull() {
60
this.output += "null";
61
}
62
63
beginStructure(descriptor) {
64
this.output += "{";
65
return new JsonCompositeEncoder(this);
66
}
67
68
beginCollection(descriptor, collectionSize) {
69
this.output += "[";
70
return new JsonCompositeEncoder(this);
71
}
72
73
escapeString(str) {
74
return str.replace(/\\/g, "\\\\")
75
.replace(/"/g, '\\"')
76
.replace(/\n/g, "\\n");
77
}
78
}
79
```
80
81
### CompositeEncoder
82
83
Interface for encoding structured data (objects, arrays, maps).
84
85
```kotlin
86
interface CompositeEncoder {
87
val serializersModule: SerializersModule
88
89
fun endStructure(descriptor: SerialDescriptor)
90
91
fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean
92
93
fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean)
94
fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte)
95
fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char)
96
fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short)
97
fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int)
98
fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long)
99
fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float)
100
fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double)
101
fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String)
102
103
fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder
104
fun <T> encodeSerializableElement(descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T)
105
fun <T : Any> encodeNullableSerializableElement(descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T?)
106
}
107
```
108
{ .api }
109
110
**Usage:**
111
112
```javascript
113
class JsonCompositeEncoder {
114
constructor(encoder) {
115
this.encoder = encoder;
116
this.serializersModule = encoder.serializersModule;
117
this.elementCount = 0;
118
}
119
120
encodeBooleanElement(descriptor, index, value) {
121
this.writeElementSeparator();
122
this.writeElementName(descriptor, index);
123
this.encoder.encodeBoolean(value);
124
}
125
126
encodeStringElement(descriptor, index, value) {
127
this.writeElementSeparator();
128
this.writeElementName(descriptor, index);
129
this.encoder.encodeString(value);
130
}
131
132
endStructure(descriptor) {
133
if (descriptor.kind === StructureKind.CLASS) {
134
this.encoder.output += "}";
135
} else if (descriptor.kind === StructureKind.LIST) {
136
this.encoder.output += "]";
137
}
138
}
139
140
writeElementSeparator() {
141
if (this.elementCount++ > 0) {
142
this.encoder.output += ",";
143
}
144
}
145
146
writeElementName(descriptor, index) {
147
if (descriptor.kind === StructureKind.CLASS) {
148
const name = descriptor.getElementName(index);
149
this.encoder.output += `"${name}":`;
150
}
151
}
152
153
shouldEncodeElementDefault(descriptor, index) {
154
return true; // Always encode defaults in this example
155
}
156
}
157
```
158
159
## Core Decoding Interfaces
160
161
### Decoder
162
163
Primary interface for decoding values from a specific format.
164
165
```kotlin
166
interface Decoder {
167
val serializersModule: SerializersModule
168
169
fun decodeBoolean(): Boolean
170
fun decodeByte(): Byte
171
fun decodeChar(): Char
172
fun decodeShort(): Short
173
fun decodeInt(): Int
174
fun decodeLong(): Long
175
fun decodeFloat(): Float
176
fun decodeDouble(): Double
177
fun decodeString(): String
178
179
fun decodeEnum(enumDescriptor: SerialDescriptor): Int
180
fun decodeNull(): Nothing?
181
fun decodeNotNullMark(): Boolean
182
fun decodeInline(descriptor: SerialDescriptor): Decoder
183
184
fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder
185
186
fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T
187
fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T>): T?
188
}
189
```
190
{ .api }
191
192
**Usage:**
193
194
```javascript
195
class JsonDecoder {
196
constructor(input, serializersModule) {
197
this.input = input;
198
this.serializersModule = serializersModule;
199
this.position = 0;
200
}
201
202
decodeString() {
203
this.skipWhitespace();
204
if (this.input[this.position] !== '"') {
205
throw new Error("Expected '\"' at position " + this.position);
206
}
207
208
this.position++; // Skip opening quote
209
const start = this.position;
210
211
while (this.position < this.input.length && this.input[this.position] !== '"') {
212
if (this.input[this.position] === '\\') {
213
this.position++; // Skip escape character
214
}
215
this.position++;
216
}
217
218
const value = this.input.substring(start, this.position);
219
this.position++; // Skip closing quote
220
return this.unescapeString(value);
221
}
222
223
decodeInt() {
224
this.skipWhitespace();
225
const start = this.position;
226
227
if (this.input[this.position] === '-') {
228
this.position++;
229
}
230
231
while (this.position < this.input.length &&
232
this.input[this.position] >= '0' &&
233
this.input[this.position] <= '9') {
234
this.position++;
235
}
236
237
const value = this.input.substring(start, this.position);
238
return parseInt(value, 10);
239
}
240
241
decodeBoolean() {
242
this.skipWhitespace();
243
if (this.input.substring(this.position, this.position + 4) === "true") {
244
this.position += 4;
245
return true;
246
} else if (this.input.substring(this.position, this.position + 5) === "false") {
247
this.position += 5;
248
return false;
249
} else {
250
throw new Error("Expected boolean at position " + this.position);
251
}
252
}
253
254
beginStructure(descriptor) {
255
this.skipWhitespace();
256
257
if (descriptor.kind === StructureKind.CLASS) {
258
this.expectChar('{');
259
} else if (descriptor.kind === StructureKind.LIST) {
260
this.expectChar('[');
261
}
262
263
return new JsonCompositeDecoder(this, descriptor);
264
}
265
266
skipWhitespace() {
267
while (this.position < this.input.length &&
268
/\s/.test(this.input[this.position])) {
269
this.position++;
270
}
271
}
272
273
expectChar(expected) {
274
if (this.input[this.position] !== expected) {
275
throw new Error(`Expected '${expected}' at position ${this.position}`);
276
}
277
this.position++;
278
}
279
}
280
```
281
282
### CompositeDecoder
283
284
Interface for decoding structured data with special constants for control flow.
285
286
```kotlin
287
interface CompositeDecoder {
288
val serializersModule: SerializersModule
289
290
fun endStructure(descriptor: SerialDescriptor)
291
fun decodeElementIndex(descriptor: SerialDescriptor): Int
292
293
fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean
294
fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte
295
fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char
296
fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short
297
fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int
298
fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long
299
fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float
300
fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double
301
fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String
302
303
fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder
304
fun <T> decodeSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T
305
fun <T : Any> decodeNullableSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T?
306
307
companion object {
308
const val DECODE_DONE = -1
309
const val UNKNOWN_NAME = -3
310
}
311
}
312
```
313
{ .api }
314
315
**Usage:**
316
317
```javascript
318
class JsonCompositeDecoder {
319
constructor(decoder, descriptor) {
320
this.decoder = decoder;
321
this.serializersModule = decoder.serializersModule;
322
this.descriptor = descriptor;
323
this.elementIndex = 0;
324
this.namesRead = new Set();
325
}
326
327
decodeElementIndex(descriptor) {
328
this.decoder.skipWhitespace();
329
330
// Check for end of structure
331
if ((descriptor.kind === StructureKind.CLASS && this.decoder.input[this.decoder.position] === '}') ||
332
(descriptor.kind === StructureKind.LIST && this.decoder.input[this.decoder.position] === ']')) {
333
return CompositeDecoder.DECODE_DONE;
334
}
335
336
// Handle comma separator
337
if (this.elementIndex > 0) {
338
this.decoder.expectChar(',');
339
this.decoder.skipWhitespace();
340
}
341
342
if (descriptor.kind === StructureKind.CLASS) {
343
// Read property name
344
const name = this.decoder.decodeString();
345
this.decoder.skipWhitespace();
346
this.decoder.expectChar(':');
347
348
const index = descriptor.getElementIndex(name);
349
if (index === CompositeDecoder.UNKNOWN_NAME) {
350
// Skip unknown property
351
this.skipValue();
352
return this.decodeElementIndex(descriptor);
353
}
354
355
this.elementIndex++;
356
return index;
357
} else if (descriptor.kind === StructureKind.LIST) {
358
// For arrays, return sequential index
359
return this.elementIndex++;
360
}
361
362
return CompositeDecoder.DECODE_DONE;
363
}
364
365
decodeStringElement(descriptor, index) {
366
return this.decoder.decodeString();
367
}
368
369
decodeIntElement(descriptor, index) {
370
return this.decoder.decodeInt();
371
}
372
373
endStructure(descriptor) {
374
this.decoder.skipWhitespace();
375
376
if (descriptor.kind === StructureKind.CLASS) {
377
this.decoder.expectChar('}');
378
} else if (descriptor.kind === StructureKind.LIST) {
379
this.decoder.expectChar(']');
380
}
381
}
382
383
skipValue() {
384
// Implementation to skip JSON value
385
this.decoder.skipWhitespace();
386
const ch = this.decoder.input[this.decoder.position];
387
388
if (ch === '"') {
389
this.decoder.decodeString();
390
} else if (ch === '{') {
391
this.skipObject();
392
} else if (ch === '[') {
393
this.skipArray();
394
} else {
395
this.skipPrimitive();
396
}
397
}
398
}
399
```
400
401
## Inline Extension Functions
402
403
### Encoder Extensions
404
405
Convenient inline functions for encoding structures and collections.
406
407
```kotlin
408
inline fun <T> Encoder.encodeStructure(
409
descriptor: SerialDescriptor,
410
crossinline block: CompositeEncoder.() -> T
411
): T
412
413
inline fun <T> Encoder.encodeCollection(
414
descriptor: SerialDescriptor,
415
collectionSize: Int,
416
crossinline block: CompositeEncoder.() -> T
417
): T
418
419
inline fun <T> Encoder.encodeCollection(
420
descriptor: SerialDescriptor,
421
collection: Collection<*>,
422
crossinline block: CompositeEncoder.() -> T
423
): T
424
```
425
{ .api }
426
427
**Usage:**
428
429
```javascript
430
// Custom serializer using encodeStructure
431
class PersonSerializer {
432
constructor() {
433
this.descriptor = buildClassSerialDescriptor("Person") {
434
element("name", String.serializer().descriptor)
435
element("age", Int.serializer().descriptor)
436
};
437
}
438
439
serialize(encoder, value) {
440
encoder.encodeStructure(this.descriptor) {
441
encodeStringElement(this.descriptor, 0, value.name)
442
encodeIntElement(this.descriptor, 1, value.age)
443
};
444
}
445
}
446
447
// Custom collection serializer
448
class CustomListSerializer {
449
constructor(elementSerializer) {
450
this.elementSerializer = elementSerializer;
451
this.descriptor = listSerialDescriptor(elementSerializer.descriptor);
452
}
453
454
serialize(encoder, value) {
455
encoder.encodeCollection(this.descriptor, value.length) {
456
value.forEach((element, index) => {
457
encodeSerializableElement(this.descriptor, index, this.elementSerializer, element)
458
})
459
};
460
}
461
}
462
```
463
464
### Decoder Extensions
465
466
Convenient inline function for decoding structures.
467
468
```kotlin
469
inline fun <T> Decoder.decodeStructure(
470
descriptor: SerialDescriptor,
471
crossinline block: CompositeDecoder.() -> T
472
): T
473
```
474
{ .api }
475
476
**Usage:**
477
478
```javascript
479
class PersonSerializer {
480
deserialize(decoder) {
481
return decoder.decodeStructure(this.descriptor) {
482
let name = "";
483
let age = 0;
484
485
while (true) {
486
const index = decodeElementIndex(this.descriptor);
487
if (index === CompositeDecoder.DECODE_DONE) break;
488
489
switch (index) {
490
case 0:
491
name = decodeStringElement(this.descriptor, 0);
492
break;
493
case 1:
494
age = decodeIntElement(this.descriptor, 1);
495
break;
496
default:
497
throw new SerializationException(`Unexpected index: ${index}`);
498
}
499
}
500
501
return new Person(name, age);
502
};
503
}
504
}
505
```
506
507
## Abstract Base Classes
508
509
### AbstractEncoder
510
511
Base implementation providing common encoder functionality (Experimental).
512
513
```kotlin
514
@ExperimentalSerializationApi
515
abstract class AbstractEncoder : Encoder, CompositeEncoder {
516
override fun encodeInline(descriptor: SerialDescriptor): Encoder = this
517
override fun encodeNotNullMark() {}
518
override fun encodeNull() = throw SerializationException("null is not supported")
519
520
// Default implementations for composite encoding
521
override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) = encodeBoolean(value)
522
override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) = encodeByte(value)
523
// ... similar for other primitive types
524
525
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = true
526
527
override fun <T> encodeSerializableElement(descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T) {
528
serializer.serialize(this, value)
529
}
530
}
531
```
532
{ .api }
533
534
**Usage:**
535
536
```javascript
537
class CustomEncoder extends AbstractEncoder {
538
constructor(output, serializersModule = EmptySerializersModule()) {
539
super();
540
this.output = output;
541
this.serializersModule = serializersModule;
542
}
543
544
encodeString(value) {
545
this.output.write(`"${value}"`);
546
}
547
548
encodeInt(value) {
549
this.output.write(value.toString());
550
}
551
552
beginStructure(descriptor) {
553
this.output.write('{');
554
return this;
555
}
556
557
endStructure(descriptor) {
558
this.output.write('}');
559
}
560
561
// Inherit default implementations for element encoding
562
}
563
```
564
565
### AbstractDecoder
566
567
Base implementation providing common decoder functionality (Experimental).
568
569
```kotlin
570
@ExperimentalSerializationApi
571
abstract class AbstractDecoder : Decoder, CompositeDecoder {
572
override fun decodeInline(descriptor: SerialDescriptor): Decoder = this
573
override fun decodeNotNullMark(): Boolean = true
574
575
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = CompositeDecoder.DECODE_DONE
576
577
// Default implementations for composite decoding
578
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean = decodeBoolean()
579
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte = decodeByte()
580
// ... similar for other primitive types
581
582
override fun <T> decodeSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T {
583
return deserializer.deserialize(this)
584
}
585
}
586
```
587
{ .api }
588
589
**Usage:**
590
591
```javascript
592
class CustomDecoder extends AbstractDecoder {
593
constructor(input, serializersModule = EmptySerializersModule()) {
594
super();
595
this.input = input;
596
this.position = 0;
597
this.serializersModule = serializersModule;
598
}
599
600
decodeString() {
601
// Implementation to read string from input
602
return this.readQuotedString();
603
}
604
605
decodeInt() {
606
// Implementation to read integer from input
607
return this.readNumber();
608
}
609
610
beginStructure(descriptor) {
611
this.skipChar('{');
612
return this;
613
}
614
615
endStructure(descriptor) {
616
this.skipChar('}');
617
}
618
619
// Inherit default implementations for element decoding
620
}
621
```
622
623
## Specialized Interfaces
624
625
### ChunkedDecoder
626
627
Interface for efficient decoding of large strings in chunks (Experimental).
628
629
```kotlin
630
@ExperimentalSerializationApi
631
interface ChunkedDecoder {
632
fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit): Boolean
633
}
634
```
635
{ .api }
636
637
**Usage:**
638
639
```javascript
640
class StreamingJsonDecoder extends AbstractDecoder {
641
decodeStringChunked(consumeChunk) {
642
const bufferSize = 1024;
643
let totalChunks = 0;
644
645
while (this.hasMoreData()) {
646
const chunk = this.readChunk(bufferSize);
647
consumeChunk(chunk);
648
totalChunks++;
649
}
650
651
return totalChunks > 0;
652
}
653
654
// Use for large string processing
655
decodeString() {
656
let result = "";
657
658
this.decodeStringChunked(chunk => {
659
result += chunk;
660
});
661
662
return result;
663
}
664
}
665
```
666
667
## Format Implementation Example
668
669
Here's a complete minimal format implementation:
670
671
```javascript
672
class SimpleFormat {
673
constructor(serializersModule = EmptySerializersModule()) {
674
this.serializersModule = serializersModule;
675
}
676
677
encodeToString(serializer, value) {
678
const encoder = new SimpleEncoder(this.serializersModule);
679
serializer.serialize(encoder, value);
680
return encoder.getResult();
681
}
682
683
decodeFromString(serializer, string) {
684
const decoder = new SimpleDecoder(string, this.serializersModule);
685
return serializer.deserialize(decoder);
686
}
687
}
688
689
class SimpleEncoder extends AbstractEncoder {
690
constructor(serializersModule) {
691
super();
692
this.serializersModule = serializersModule;
693
this.result = [];
694
}
695
696
encodeString(value) {
697
this.result.push(`S:${value}`);
698
}
699
700
encodeInt(value) {
701
this.result.push(`I:${value}`);
702
}
703
704
beginStructure(descriptor) {
705
this.result.push(`{${descriptor.serialName}`);
706
return this;
707
}
708
709
endStructure(descriptor) {
710
this.result.push('}');
711
}
712
713
getResult() {
714
return this.result.join(',');
715
}
716
}
717
718
class SimpleDecoder extends AbstractDecoder {
719
constructor(input, serializersModule) {
720
super();
721
this.serializersModule = serializersModule;
722
this.tokens = input.split(',');
723
this.position = 0;
724
}
725
726
decodeString() {
727
const token = this.tokens[this.position++];
728
if (!token.startsWith('S:')) {
729
throw new Error('Expected string token');
730
}
731
return token.substring(2);
732
}
733
734
decodeInt() {
735
const token = this.tokens[this.position++];
736
if (!token.startsWith('I:')) {
737
throw new Error('Expected int token');
738
}
739
return parseInt(token.substring(2), 10);
740
}
741
742
beginStructure(descriptor) {
743
const token = this.tokens[this.position++];
744
if (!token.startsWith('{')) {
745
throw new Error('Expected structure start');
746
}
747
return this;
748
}
749
750
endStructure(descriptor) {
751
const token = this.tokens[this.position++];
752
if (token !== '}') {
753
throw new Error('Expected structure end');
754
}
755
}
756
}
757
758
// Usage
759
const format = new SimpleFormat();
760
const user = new User("John", 30);
761
const encoded = format.encodeToString(User.serializer(), user);
762
const decoded = format.decodeFromString(User.serializer(), encoded);
763
```
764
765
The encoding/decoding APIs provide the foundation for implementing any serialization format while maintaining compatibility with the kotlinx.serialization ecosystem.