Kotlin multiplatform reflectionless serialization library core module for JavaScript platform
—
Complete reference for the low-level encoding and decoding APIs used by format implementors in kotlinx.serialization-core-js.
Primary interface for encoding values into a specific format.
interface Encoder {
val serializersModule: SerializersModule
fun encodeBoolean(value: Boolean)
fun encodeByte(value: Byte)
fun encodeChar(value: Char)
fun encodeShort(value: Short)
fun encodeInt(value: Int)
fun encodeLong(value: Long)
fun encodeFloat(value: Float)
fun encodeDouble(value: Double)
fun encodeString(value: String)
fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int)
fun encodeNull()
fun encodeNotNullMark()
fun encodeInline(descriptor: SerialDescriptor): Encoder
fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder
fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder
fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T)
fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?)
}{ .api }
Usage:
class JsonEncoder {
constructor(serializersModule) {
this.serializersModule = serializersModule;
this.output = "";
}
encodeString(value) {
this.output += `"${this.escapeString(value)}"`;
}
encodeInt(value) {
this.output += value.toString();
}
encodeBoolean(value) {
this.output += value ? "true" : "false";
}
encodeNull() {
this.output += "null";
}
beginStructure(descriptor) {
this.output += "{";
return new JsonCompositeEncoder(this);
}
beginCollection(descriptor, collectionSize) {
this.output += "[";
return new JsonCompositeEncoder(this);
}
escapeString(str) {
return str.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n");
}
}Interface for encoding structured data (objects, arrays, maps).
interface CompositeEncoder {
val serializersModule: SerializersModule
fun endStructure(descriptor: SerialDescriptor)
fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean
fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean)
fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte)
fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char)
fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short)
fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int)
fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long)
fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float)
fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double)
fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String)
fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder
fun <T> encodeSerializableElement(descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T)
fun <T : Any> encodeNullableSerializableElement(descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T?)
}{ .api }
Usage:
class JsonCompositeEncoder {
constructor(encoder) {
this.encoder = encoder;
this.serializersModule = encoder.serializersModule;
this.elementCount = 0;
}
encodeBooleanElement(descriptor, index, value) {
this.writeElementSeparator();
this.writeElementName(descriptor, index);
this.encoder.encodeBoolean(value);
}
encodeStringElement(descriptor, index, value) {
this.writeElementSeparator();
this.writeElementName(descriptor, index);
this.encoder.encodeString(value);
}
endStructure(descriptor) {
if (descriptor.kind === StructureKind.CLASS) {
this.encoder.output += "}";
} else if (descriptor.kind === StructureKind.LIST) {
this.encoder.output += "]";
}
}
writeElementSeparator() {
if (this.elementCount++ > 0) {
this.encoder.output += ",";
}
}
writeElementName(descriptor, index) {
if (descriptor.kind === StructureKind.CLASS) {
const name = descriptor.getElementName(index);
this.encoder.output += `"${name}":`;
}
}
shouldEncodeElementDefault(descriptor, index) {
return true; // Always encode defaults in this example
}
}Primary interface for decoding values from a specific format.
interface Decoder {
val serializersModule: SerializersModule
fun decodeBoolean(): Boolean
fun decodeByte(): Byte
fun decodeChar(): Char
fun decodeShort(): Short
fun decodeInt(): Int
fun decodeLong(): Long
fun decodeFloat(): Float
fun decodeDouble(): Double
fun decodeString(): String
fun decodeEnum(enumDescriptor: SerialDescriptor): Int
fun decodeNull(): Nothing?
fun decodeNotNullMark(): Boolean
fun decodeInline(descriptor: SerialDescriptor): Decoder
fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder
fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T
fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T>): T?
}{ .api }
Usage:
class JsonDecoder {
constructor(input, serializersModule) {
this.input = input;
this.serializersModule = serializersModule;
this.position = 0;
}
decodeString() {
this.skipWhitespace();
if (this.input[this.position] !== '"') {
throw new Error("Expected '\"' at position " + this.position);
}
this.position++; // Skip opening quote
const start = this.position;
while (this.position < this.input.length && this.input[this.position] !== '"') {
if (this.input[this.position] === '\\') {
this.position++; // Skip escape character
}
this.position++;
}
const value = this.input.substring(start, this.position);
this.position++; // Skip closing quote
return this.unescapeString(value);
}
decodeInt() {
this.skipWhitespace();
const start = this.position;
if (this.input[this.position] === '-') {
this.position++;
}
while (this.position < this.input.length &&
this.input[this.position] >= '0' &&
this.input[this.position] <= '9') {
this.position++;
}
const value = this.input.substring(start, this.position);
return parseInt(value, 10);
}
decodeBoolean() {
this.skipWhitespace();
if (this.input.substring(this.position, this.position + 4) === "true") {
this.position += 4;
return true;
} else if (this.input.substring(this.position, this.position + 5) === "false") {
this.position += 5;
return false;
} else {
throw new Error("Expected boolean at position " + this.position);
}
}
beginStructure(descriptor) {
this.skipWhitespace();
if (descriptor.kind === StructureKind.CLASS) {
this.expectChar('{');
} else if (descriptor.kind === StructureKind.LIST) {
this.expectChar('[');
}
return new JsonCompositeDecoder(this, descriptor);
}
skipWhitespace() {
while (this.position < this.input.length &&
/\s/.test(this.input[this.position])) {
this.position++;
}
}
expectChar(expected) {
if (this.input[this.position] !== expected) {
throw new Error(`Expected '${expected}' at position ${this.position}`);
}
this.position++;
}
}Interface for decoding structured data with special constants for control flow.
interface CompositeDecoder {
val serializersModule: SerializersModule
fun endStructure(descriptor: SerialDescriptor)
fun decodeElementIndex(descriptor: SerialDescriptor): Int
fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean
fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte
fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char
fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short
fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int
fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long
fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float
fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double
fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String
fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder
fun <T> decodeSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T
fun <T : Any> decodeNullableSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T?
companion object {
const val DECODE_DONE = -1
const val UNKNOWN_NAME = -3
}
}{ .api }
Usage:
class JsonCompositeDecoder {
constructor(decoder, descriptor) {
this.decoder = decoder;
this.serializersModule = decoder.serializersModule;
this.descriptor = descriptor;
this.elementIndex = 0;
this.namesRead = new Set();
}
decodeElementIndex(descriptor) {
this.decoder.skipWhitespace();
// Check for end of structure
if ((descriptor.kind === StructureKind.CLASS && this.decoder.input[this.decoder.position] === '}') ||
(descriptor.kind === StructureKind.LIST && this.decoder.input[this.decoder.position] === ']')) {
return CompositeDecoder.DECODE_DONE;
}
// Handle comma separator
if (this.elementIndex > 0) {
this.decoder.expectChar(',');
this.decoder.skipWhitespace();
}
if (descriptor.kind === StructureKind.CLASS) {
// Read property name
const name = this.decoder.decodeString();
this.decoder.skipWhitespace();
this.decoder.expectChar(':');
const index = descriptor.getElementIndex(name);
if (index === CompositeDecoder.UNKNOWN_NAME) {
// Skip unknown property
this.skipValue();
return this.decodeElementIndex(descriptor);
}
this.elementIndex++;
return index;
} else if (descriptor.kind === StructureKind.LIST) {
// For arrays, return sequential index
return this.elementIndex++;
}
return CompositeDecoder.DECODE_DONE;
}
decodeStringElement(descriptor, index) {
return this.decoder.decodeString();
}
decodeIntElement(descriptor, index) {
return this.decoder.decodeInt();
}
endStructure(descriptor) {
this.decoder.skipWhitespace();
if (descriptor.kind === StructureKind.CLASS) {
this.decoder.expectChar('}');
} else if (descriptor.kind === StructureKind.LIST) {
this.decoder.expectChar(']');
}
}
skipValue() {
// Implementation to skip JSON value
this.decoder.skipWhitespace();
const ch = this.decoder.input[this.decoder.position];
if (ch === '"') {
this.decoder.decodeString();
} else if (ch === '{') {
this.skipObject();
} else if (ch === '[') {
this.skipArray();
} else {
this.skipPrimitive();
}
}
}Convenient inline functions for encoding structures and collections.
inline fun <T> Encoder.encodeStructure(
descriptor: SerialDescriptor,
crossinline block: CompositeEncoder.() -> T
): T
inline fun <T> Encoder.encodeCollection(
descriptor: SerialDescriptor,
collectionSize: Int,
crossinline block: CompositeEncoder.() -> T
): T
inline fun <T> Encoder.encodeCollection(
descriptor: SerialDescriptor,
collection: Collection<*>,
crossinline block: CompositeEncoder.() -> T
): T{ .api }
Usage:
// Custom serializer using encodeStructure
class PersonSerializer {
constructor() {
this.descriptor = buildClassSerialDescriptor("Person") {
element("name", String.serializer().descriptor)
element("age", Int.serializer().descriptor)
};
}
serialize(encoder, value) {
encoder.encodeStructure(this.descriptor) {
encodeStringElement(this.descriptor, 0, value.name)
encodeIntElement(this.descriptor, 1, value.age)
};
}
}
// Custom collection serializer
class CustomListSerializer {
constructor(elementSerializer) {
this.elementSerializer = elementSerializer;
this.descriptor = listSerialDescriptor(elementSerializer.descriptor);
}
serialize(encoder, value) {
encoder.encodeCollection(this.descriptor, value.length) {
value.forEach((element, index) => {
encodeSerializableElement(this.descriptor, index, this.elementSerializer, element)
})
};
}
}Convenient inline function for decoding structures.
inline fun <T> Decoder.decodeStructure(
descriptor: SerialDescriptor,
crossinline block: CompositeDecoder.() -> T
): T{ .api }
Usage:
class PersonSerializer {
deserialize(decoder) {
return decoder.decodeStructure(this.descriptor) {
let name = "";
let age = 0;
while (true) {
const index = decodeElementIndex(this.descriptor);
if (index === CompositeDecoder.DECODE_DONE) break;
switch (index) {
case 0:
name = decodeStringElement(this.descriptor, 0);
break;
case 1:
age = decodeIntElement(this.descriptor, 1);
break;
default:
throw new SerializationException(`Unexpected index: ${index}`);
}
}
return new Person(name, age);
};
}
}Base implementation providing common encoder functionality (Experimental).
@ExperimentalSerializationApi
abstract class AbstractEncoder : Encoder, CompositeEncoder {
override fun encodeInline(descriptor: SerialDescriptor): Encoder = this
override fun encodeNotNullMark() {}
override fun encodeNull() = throw SerializationException("null is not supported")
// Default implementations for composite encoding
override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) = encodeBoolean(value)
override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) = encodeByte(value)
// ... similar for other primitive types
override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = true
override fun <T> encodeSerializableElement(descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T) {
serializer.serialize(this, value)
}
}{ .api }
Usage:
class CustomEncoder extends AbstractEncoder {
constructor(output, serializersModule = EmptySerializersModule()) {
super();
this.output = output;
this.serializersModule = serializersModule;
}
encodeString(value) {
this.output.write(`"${value}"`);
}
encodeInt(value) {
this.output.write(value.toString());
}
beginStructure(descriptor) {
this.output.write('{');
return this;
}
endStructure(descriptor) {
this.output.write('}');
}
// Inherit default implementations for element encoding
}Base implementation providing common decoder functionality (Experimental).
@ExperimentalSerializationApi
abstract class AbstractDecoder : Decoder, CompositeDecoder {
override fun decodeInline(descriptor: SerialDescriptor): Decoder = this
override fun decodeNotNullMark(): Boolean = true
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = CompositeDecoder.DECODE_DONE
// Default implementations for composite decoding
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean = decodeBoolean()
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte = decodeByte()
// ... similar for other primitive types
override fun <T> decodeSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T {
return deserializer.deserialize(this)
}
}{ .api }
Usage:
class CustomDecoder extends AbstractDecoder {
constructor(input, serializersModule = EmptySerializersModule()) {
super();
this.input = input;
this.position = 0;
this.serializersModule = serializersModule;
}
decodeString() {
// Implementation to read string from input
return this.readQuotedString();
}
decodeInt() {
// Implementation to read integer from input
return this.readNumber();
}
beginStructure(descriptor) {
this.skipChar('{');
return this;
}
endStructure(descriptor) {
this.skipChar('}');
}
// Inherit default implementations for element decoding
}Interface for efficient decoding of large strings in chunks (Experimental).
@ExperimentalSerializationApi
interface ChunkedDecoder {
fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit): Boolean
}{ .api }
Usage:
class StreamingJsonDecoder extends AbstractDecoder {
decodeStringChunked(consumeChunk) {
const bufferSize = 1024;
let totalChunks = 0;
while (this.hasMoreData()) {
const chunk = this.readChunk(bufferSize);
consumeChunk(chunk);
totalChunks++;
}
return totalChunks > 0;
}
// Use for large string processing
decodeString() {
let result = "";
this.decodeStringChunked(chunk => {
result += chunk;
});
return result;
}
}Here's a complete minimal format implementation:
class SimpleFormat {
constructor(serializersModule = EmptySerializersModule()) {
this.serializersModule = serializersModule;
}
encodeToString(serializer, value) {
const encoder = new SimpleEncoder(this.serializersModule);
serializer.serialize(encoder, value);
return encoder.getResult();
}
decodeFromString(serializer, string) {
const decoder = new SimpleDecoder(string, this.serializersModule);
return serializer.deserialize(decoder);
}
}
class SimpleEncoder extends AbstractEncoder {
constructor(serializersModule) {
super();
this.serializersModule = serializersModule;
this.result = [];
}
encodeString(value) {
this.result.push(`S:${value}`);
}
encodeInt(value) {
this.result.push(`I:${value}`);
}
beginStructure(descriptor) {
this.result.push(`{${descriptor.serialName}`);
return this;
}
endStructure(descriptor) {
this.result.push('}');
}
getResult() {
return this.result.join(',');
}
}
class SimpleDecoder extends AbstractDecoder {
constructor(input, serializersModule) {
super();
this.serializersModule = serializersModule;
this.tokens = input.split(',');
this.position = 0;
}
decodeString() {
const token = this.tokens[this.position++];
if (!token.startsWith('S:')) {
throw new Error('Expected string token');
}
return token.substring(2);
}
decodeInt() {
const token = this.tokens[this.position++];
if (!token.startsWith('I:')) {
throw new Error('Expected int token');
}
return parseInt(token.substring(2), 10);
}
beginStructure(descriptor) {
const token = this.tokens[this.position++];
if (!token.startsWith('{')) {
throw new Error('Expected structure start');
}
return this;
}
endStructure(descriptor) {
const token = this.tokens[this.position++];
if (token !== '}') {
throw new Error('Expected structure end');
}
}
}
// Usage
const format = new SimpleFormat();
const user = new User("John", 30);
const encoded = format.encodeToString(User.serializer(), user);
const decoded = format.decodeFromString(User.serializer(), encoded);The encoding/decoding APIs provide the foundation for implementing any serialization format while maintaining compatibility with the kotlinx.serialization ecosystem.
Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-core-js