CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-core-js

Kotlin multiplatform reflectionless serialization library core module for JavaScript platform

Pending
Overview
Eval results
Files

encoding.mddocs/

Encoding and Decoding

Complete reference for the low-level encoding and decoding APIs used by format implementors in kotlinx.serialization-core-js.

Core Encoding Interfaces

Encoder

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");
  }
}

CompositeEncoder

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
  }
}

Core Decoding Interfaces

Decoder

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++;
  }
}

CompositeDecoder

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();
    }
  }
}

Inline Extension Functions

Encoder Extensions

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)
      })
    };
  }
}

Decoder Extensions

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);
    };
  }
}

Abstract Base Classes

AbstractEncoder

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
}

AbstractDecoder

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
}

Specialized Interfaces

ChunkedDecoder

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;
  }
}

Format Implementation Example

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

docs

annotations.md

builtins.md

descriptors.md

encoding.md

index.md

modules.md

serializers.md

tile.json