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

modules.mddocs/

Serializers Modules

Complete reference for SerializersModule and contextual serialization APIs that enable runtime serializer configuration in kotlinx.serialization-core-js.

Core Module Class

SerializersModule

Sealed class that holds a collection of serializers for runtime lookup.

public sealed class SerializersModule {
    @ExperimentalSerializationApi
    public abstract fun <T : Any> getContextual(
        kClass: KClass<T>,
        typeArgumentsSerializers: List<KSerializer<*>> = emptyList()
    ): KSerializer<T>?
    
    @ExperimentalSerializationApi  
    public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>?
    
    @ExperimentalSerializationApi
    public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>?
    
    @ExperimentalSerializationApi
    public abstract fun dumpTo(collector: SerializersModuleCollector)
}

{ .api }

Usage:

// Basic module lookup
const module = SerializersModule {
  contextual(Date::class, CustomDateSerializer)
};

// Get contextual serializer
const dateSerializer = module.getContextual(Date::class);
if (dateSerializer !== null) {
  const json = format.encodeToString(dateSerializer, new Date());
}

// Polymorphic lookup
const shape = new Circle(5);
const shapeSerializer = module.getPolymorphic(Shape::class, shape);

Module Factory Functions

SerializersModule Builder

Primary DSL function for creating modules.

fun SerializersModule(builderAction: SerializersModuleBuilder.() -> Unit): SerializersModule

{ .api }

Usage:

const module = SerializersModule {
  // Add contextual serializers
  contextual(Date::class, CustomDateSerializer)
  contextual(BigDecimal::class, BigDecimalStringSerializer)
  
  // Add polymorphic hierarchies
  polymorphic(Shape::class) {
    subclass(Circle::class)
    subclass(Rectangle::class)
    subclass(Triangle::class)
  }
  
  // Include other modules
  include(otherModule)
};

Empty Module

Factory for creating an empty module.

fun EmptySerializersModule(): SerializersModule

{ .api }

Usage:

// Create empty module
const emptyModule = EmptySerializersModule();

// Use as base for building
const customModule = SerializersModule {
  include(emptyModule)
  contextual(String::class, CustomStringSerializer)
};

Single Serializer Module

Factory functions for modules with a single serializer.

fun <T : Any> serializersModuleOf(
    kClass: KClass<T>, 
    serializer: KSerializer<T>
): SerializersModule

inline fun <reified T : Any> serializersModuleOf(
    serializer: KSerializer<T>
): SerializersModule

{ .api }

Usage:

// Create module with single contextual serializer
const dateModule = serializersModuleOf(Date::class, CustomDateSerializer);
const reifiedDateModule = serializersModuleOf<Date>(CustomDateSerializer);

// Use in format configuration
const format = Json {
  serializersModule = dateModule
};

Module Builder

SerializersModuleBuilder

Builder class for constructing SerializersModule instances.

class SerializersModuleBuilder {
    fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>)
    fun <T : Any> contextual(kClass: KClass<T>, provider: (typeArgumentsSerializers: List<KSerializer<*>>) -> KSerializer<*>)
    
    fun <Base : Any> polymorphic(
        baseClass: KClass<Base>,
        actualClass: KClass<out Base>,  
        actualSerializer: KSerializer<out Base>
    )
    
    fun <Base : Any> polymorphicDefaultSerializer(
        baseClass: KClass<Base>,
        defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
    )
    
    fun <Base : Any> polymorphicDefaultDeserializer(
        baseClass: KClass<Base>,
        defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
    )
    
    fun include(module: SerializersModule)
}

{ .api }

Contextual Serialization

Basic Contextual Registration

// Register contextual serializers
const module = SerializersModule {
  // Simple contextual registration
  contextual(Date::class, CustomDateSerializer)
  contextual(BigInteger::class, BigIntegerStringSerializer)
  contextual(UUID::class, UUIDStringSerializer)
};

// Use in serializable classes
@Serializable
class Event {
  name;
  
  @Contextual
  startTime; // Uses Date serializer from module
  
  @Contextual
  eventId; // Uses UUID serializer from module
  
  constructor(name, startTime, eventId) {
    this.name = name;
    this.startTime = startTime;
    this.eventId = eventId;
  }
}

Reified Contextual Registration

inline fun <reified T : Any> SerializersModuleBuilder.contextual(
    serializer: KSerializer<T>
)

{ .api }

Usage:

const module = SerializersModule {
  // Reified contextual registration
  contextual<Date>(CustomDateSerializer)
  contextual<BigDecimal>(BigDecimalSerializer)
  contextual<LocalDateTime>(LocalDateTimeSerializer)
};

Generic Contextual Serializers

fun <T : Any> SerializersModuleBuilder.contextual(
    kClass: KClass<T>, 
    provider: (typeArgumentsSerializers: List<KSerializer<*>>) -> KSerializer<*>
)

{ .api }

Usage:

// Generic contextual serializer with type arguments
const module = SerializersModule {
  contextual(Optional::class) { typeArgs ->
    OptionalSerializer(typeArgs[0]) // Use first type argument
  }
  
  contextual(Result::class) { typeArgs ->
    ResultSerializer(typeArgs[0]) // Generic Result<T> serializer
  }
};

// Usage with generic types
@Serializable
class ApiResponse<T> {
  @Contextual
  data; // Optional<T> - serializer determined at runtime
  
  status;
  
  constructor(data, status) {
    this.data = data;
    this.status = status;
  }
}

Polymorphic Serialization

Basic Polymorphic Registration

const module = SerializersModule {
  polymorphic(Shape::class) {
    subclass(Circle::class)
    subclass(Rectangle::class)
    subclass(Triangle::class)
  }
  
  // Alternative syntax
  polymorphic(Animal::class, Cat::class, Cat.serializer())
  polymorphic(Animal::class, Dog::class, Dog.serializer())
};

Polymorphic Module Builder

class PolymorphicModuleBuilder<Base : Any> {
    fun <T : Base> subclass(kClass: KClass<T>)
    fun <T : Base> subclass(kClass: KClass<T>, serializer: KSerializer<T>)
    
    fun default(defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?)
    fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?)
}

{ .api }

Usage:

const module = SerializersModule {
  polymorphic(Shape::class) {
    // Register subclasses (uses generated serializers)
    subclass(Circle::class)
    subclass(Rectangle::class)
    
    // Register with custom serializer
    subclass(Triangle::class, TriangleCustomSerializer)
    
    // Default serializer for unknown types
    default { value ->
      when (value) {
        is UnknownShape -> UnknownShapeSerializer
        else -> null
      }
    }
    
    // Default deserializer for unknown class names
    defaultDeserializer { className ->
      when (className) {
        "LegacyShape" -> LegacyShapeSerializer
        else -> null
      }
    }
  }
};

Reified Polymorphic Registration

inline fun <reified T : Base> PolymorphicModuleBuilder<Base>.subclass()
inline fun <reified T : Base> PolymorphicModuleBuilder<Base>.subclass(serializer: KSerializer<T>)

{ .api }

Usage:

const module = SerializersModule {
  polymorphic(Shape::class) {
    // Reified subclass registration
    subclass<Circle>()
    subclass<Rectangle>()
    subclass<Triangle>(TriangleCustomSerializer)
  }
};

Advanced Polymorphic Configuration

Custom Base Serializer

const module = SerializersModule {
  // Register polymorphic hierarchy with custom base serializer
  polymorphic(
    Shape::class,
    Shape::class,  // base class
    ShapeBaseSerializer // custom base serializer
  ) {
    subclass(Circle::class)
    subclass(Rectangle::class)
  }
};

Default Providers

const module = SerializersModule {
  polymorphic(ApiResponse::class) {
    subclass(SuccessResponse::class)
    subclass(ErrorResponse::class)
    
    // Handle serialization of unknown subtypes
    default { value ->
      if (value instanceof UnknownResponse) {
        return UnknownResponseSerializer;
      }
      return null; // Will throw if no serializer found
    }
    
    // Handle deserialization of unknown type names
    defaultDeserializer { className ->
      if (className === "LegacyResponse") {
        return LegacyResponseSerializer;
      }
      if (className === null) {
        return DefaultResponseSerializer;
      }
      return null;
    }
  }
};

Module Operations

Module Combination

operator fun SerializersModule.plus(other: SerializersModule): SerializersModule

fun SerializersModule.overwriteWith(other: SerializersModule): SerializersModule

{ .api }

Usage:

// Combine modules (first module takes precedence for conflicts)
const baseModule = SerializersModule {
  contextual(Date::class, DefaultDateSerializer)
  polymorphic(Shape::class) {
    subclass(Circle::class)
  }
};

const extensionModule = SerializersModule {
  contextual(Date::class, CustomDateSerializer) // This will be ignored
  contextual(BigDecimal::class, BigDecimalSerializer)
  polymorphic(Shape::class) {
    subclass(Rectangle::class)
  }
};

val combinedModule = baseModule + extensionModule;

// Overwrite combination (second module takes precedence)
const overwrittenModule = baseModule.overwriteWith(extensionModule);
// Now uses CustomDateSerializer instead of DefaultDateSerializer

Module Inclusion

// Include existing modules in new ones
const coreModule = SerializersModule {
  contextual(Date::class, DateSerializer)
  contextual(UUID::class, UUIDSerializer)
};

const extendedModule = SerializersModule {
  include(coreModule) // Include all registrations from coreModule
  
  contextual(BigDecimal::class, BigDecimalSerializer)
  polymorphic(Shape::class) {
    subclass(Circle::class)
  }
};

Module Introspection

SerializersModuleCollector

Interface for visiting module contents (Experimental).

@ExperimentalSerializationApi
interface SerializersModuleCollector {
    fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>)
    fun <Base : Any, Sub : Base> polymorphic(
        baseClass: KClass<Base>, 
        actualClass: KClass<Sub>, 
        actualSerializer: KSerializer<Sub>
    )
    fun <Base : Any> polymorphicDefaultSerializer(
        baseClass: KClass<Base>, 
        defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
    )
    fun <Base : Any> polymorphicDefaultDeserializer(
        baseClass: KClass<Base>, 
        defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
    )
}

{ .api }

Usage:

// Collect and inspect module contents
class ModuleInspector {
  constructor() {
    this.contextualSerializers = new Map();
    this.polymorphicSerializers = new Map();
  }
  
  contextual(kClass, serializer) {
    console.log(`Contextual: ${kClass.simpleName} -> ${serializer.constructor.name}`);
    this.contextualSerializers.set(kClass, serializer);
  }
  
  polymorphic(baseClass, actualClass, actualSerializer) {
    console.log(`Polymorphic: ${baseClass.simpleName} -> ${actualClass.simpleName}`);
    
    if (!this.polymorphicSerializers.has(baseClass)) {
      this.polymorphicSerializers.set(baseClass, new Map());
    }
    this.polymorphicSerializers.get(baseClass).set(actualClass, actualSerializer);
  }
  
  polymorphicDefaultSerializer(baseClass, provider) {
    console.log(`Default serializer for ${baseClass.simpleName}`);
  }
  
  polymorphicDefaultDeserializer(baseClass, provider) {
    console.log(`Default deserializer for ${baseClass.simpleName}`);
  }
}

// Inspect module
const inspector = new ModuleInspector();
module.dumpTo(inspector);

console.log("Contextual serializers:", inspector.contextualSerializers);
console.log("Polymorphic serializers:", inspector.polymorphicSerializers);

Format Integration

Using Modules with Formats

// Configure JSON format with module
const module = SerializersModule {
  contextual(Date::class, ISO8601DateSerializer)
  contextual(BigDecimal::class, BigDecimalStringSerializer)
  
  polymorphic(ApiResponse::class) {
    subclass(SuccessResponse::class)
    subclass(ErrorResponse::class)
  }
};

const json = Json {
  serializersModule = module
  ignoreUnknownKeys = true
  encodeDefaults = false
};

// All serialization operations use the module
@Serializable
class ApiCall {
  @Contextual
  timestamp; // Uses ISO8601DateSerializer
  
  @Contextual
  amount; // Uses BigDecimalStringSerializer
  
  @Polymorphic
  response; // Uses polymorphic serialization
  
  constructor(timestamp, amount, response) {
    this.timestamp = timestamp;
    this.amount = amount;
    this.response = response;
  }
}

const call = new ApiCall(new Date(), new BigDecimal("123.45"), new SuccessResponse("OK"));
const jsonString = json.encodeToString(ApiCall.serializer(), call);

Real-World Examples

Complete Application Module

// Comprehensive module for a real application
const applicationModule = SerializersModule {
  // Date/time serialization
  contextual(Date::class, ISO8601DateSerializer)
  contextual(LocalDateTime::class, LocalDateTimeComponentSerializer)
  contextual(Duration::class, DurationStringSerializer)
  
  // Numeric types for precision
  contextual(BigDecimal::class, BigDecimalStringSerializer)
  contextual(Long::class, LongAsStringSerializer)
  
  // Identity types
  contextual(UUID::class, UUIDStringSerializer)
  
  // API response hierarchy
  polymorphic(ApiResponse::class) {
    subclass(SuccessResponse::class)
    subclass(ErrorResponse::class)
    subclass(ValidationErrorResponse::class)
    
    default { response ->
      when (response.type) {
        "unknown" -> UnknownResponseSerializer
        else -> null
      }
    }
  }
  
  // Domain model hierarchy
  polymorphic(DomainEvent::class) {
    subclass(UserCreatedEvent::class)
    subclass(OrderPlacedEvent::class)
    subclass(PaymentProcessedEvent::class)
  }
  
  // Generic result type
  contextual(Result::class) { typeArgs ->
    ResultSerializer(
      successSerializer = typeArgs[0],
      errorSerializer = typeArgs.getOrNull(1) ?: String.serializer()
    )
  }
};

Module Composition Pattern

// Modular approach with separate concerns
const dateTimeModule = SerializersModule {
  contextual(Date::class, ISO8601DateSerializer)
  contextual(LocalDateTime::class, LocalDateTimeSerializer)
  contextual(Duration::class, DurationISOSerializer)
};

const numericModule = SerializersModule {
  contextual(BigDecimal::class, BigDecimalStringSerializer)
  contextual(Long::class, LongAsStringSerializer)
};

const apiModule = SerializersModule {
  polymorphic(ApiResponse::class) {
    subclass(SuccessResponse::class)
    subclass(ErrorResponse::class)
  }
};

const domainModule = SerializersModule {
  polymorphic(DomainEvent::class) {
    subclass(UserCreatedEvent::class)
    subclass(OrderPlacedEvent::class)
  }
};

// Compose final application module
const applicationModule = SerializersModule {
  include(dateTimeModule)
  include(numericModule)
  include(apiModule)  
  include(domainModule)
};

// Use in different configurations
const jsonConfig = Json {
  serializersModule = applicationModule
  ignoreUnknownKeys = true
};

const protobufConfig = ProtoBuf {
  serializersModule = applicationModule
};

SerializersModule provides a powerful and flexible system for runtime serializer configuration, enabling complex serialization scenarios while maintaining type safety and performance across different formats.

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