Kotlin multiplatform reflectionless serialization library core module for JavaScript platform
—
Complete reference for SerializersModule and contextual serialization APIs that enable runtime serializer configuration in kotlinx.serialization-core-js.
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);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)
};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)
};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
};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 }
// 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;
}
}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)
};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;
}
}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())
};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
}
}
}
};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)
}
};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)
}
};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;
}
}
};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// 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)
}
};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);// 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);// 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()
)
}
};// 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