Kotlin multiplatform reflectionless serialization library core module for JavaScript platform
—
Complete reference for SerialDescriptor and related APIs that describe the structure of serializable types in kotlinx.serialization-core-js.
Primary interface for describing the structure of serializable types.
interface SerialDescriptor {
val serialName: String
val kind: SerialKind
val elementsCount: Int
fun getElementName(index: Int): String
fun getElementIndex(name: String): Int
fun isElementOptional(index: Int): Boolean
fun getElementDescriptor(index: Int): SerialDescriptor
fun getElementAnnotations(index: Int): List<Annotation>
fun isInline: Boolean
val annotations: List<Annotation>
val isNullable: Boolean
}{ .api }
Usage:
// Get descriptor from serializer
const userSerializer = User.serializer();
const descriptor = userSerializer.descriptor;
// Inspect structure
console.log(descriptor.serialName); // "User"
console.log(descriptor.kind); // StructureKind.CLASS
console.log(descriptor.elementsCount); // 3
// Iterate elements
for (let i = 0; i < descriptor.elementsCount; i++) {
const name = descriptor.getElementName(i);
const elementDesc = descriptor.getElementDescriptor(i);
const optional = descriptor.isElementOptional(i);
console.log(`${name}: ${elementDesc.serialName} (optional: ${optional})`);
}Base sealed class for categorizing different types of serializable structures.
sealed class SerialKind {
final override fun toString(): String
}{ .api }
Represents primitive types that cannot be decomposed further.
sealed class PrimitiveKind : SerialKind() {
object BOOLEAN : PrimitiveKind()
object BYTE : PrimitiveKind()
object CHAR : PrimitiveKind()
object SHORT : PrimitiveKind()
object INT : PrimitiveKind()
object LONG : PrimitiveKind()
object FLOAT : PrimitiveKind()
object DOUBLE : PrimitiveKind()
object STRING : PrimitiveKind()
}{ .api }
Usage:
// Check for primitive kinds
const stringDesc = String.serializer().descriptor;
console.log(stringDesc.kind === PrimitiveKind.STRING); // true
// Pattern matching on kind
function describePrimitive(descriptor) {
switch (descriptor.kind) {
case PrimitiveKind.BOOLEAN:
return "Boolean primitive";
case PrimitiveKind.STRING:
return "String primitive";
case PrimitiveKind.INT:
return "Integer primitive";
// ... other cases
default:
return "Not a primitive";
}
}Represents composite types with internal structure.
sealed class StructureKind : SerialKind() {
object CLASS : StructureKind()
object LIST : StructureKind()
object MAP : StructureKind()
object OBJECT : StructureKind()
}{ .api }
Usage:
// Check structure types
const userDesc = User.serializer().descriptor;
console.log(userDesc.kind === StructureKind.CLASS); // true
const listDesc = ListSerializer(String.serializer()).descriptor;
console.log(listDesc.kind === StructureKind.LIST); // true
const mapDesc = MapSerializer(String.serializer(), Int.serializer()).descriptor;
console.log(mapDesc.kind === StructureKind.MAP); // trueRepresents types that support polymorphic serialization (Experimental).
@ExperimentalSerializationApi
sealed class PolymorphicKind : SerialKind() {
object SEALED : PolymorphicKind()
object OPEN : PolymorphicKind()
}{ .api }
Usage:
// Sealed class descriptor
const resultDesc = Result.serializer().descriptor;
console.log(resultDesc.kind === PolymorphicKind.SEALED); // true
// Open polymorphic descriptor
const shapeDesc = PolymorphicSerializer(Shape::class).descriptor;
console.log(shapeDesc.kind === PolymorphicKind.OPEN); // trueFactory function for creating class descriptors with a builder DSL.
fun buildClassSerialDescriptor(
serialName: String,
typeParameters: Array<SerialDescriptor> = emptyArray(),
builderAction: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor{ .api }
Usage:
const userDescriptor = buildClassSerialDescriptor("User") {
element("name", String.serializer().descriptor)
element("age", Int.serializer().descriptor, isOptional = true)
element("email", String.serializer().descriptor)
};
// With type parameters (for generic classes)
const pairDescriptor = buildClassSerialDescriptor("Pair", [
String.serializer().descriptor, // First type parameter
Int.serializer().descriptor // Second type parameter
]) {
element("first", getTypeParameter(0)) // Use first type parameter
element("second", getTypeParameter(1)) // Use second type parameter
};Builder class for constructing class descriptors.
class ClassSerialDescriptorBuilder(val serialName: String) {
fun element(
elementName: String,
descriptor: SerialDescriptor,
annotations: List<Annotation> = emptyList(),
isOptional: Boolean = false
)
fun getTypeParameter(index: Int): SerialDescriptor
}{ .api }
Usage:
// Advanced builder usage
const advancedDescriptor = buildClassSerialDescriptor("AdvancedClass") {
// Required element
element("id", String.serializer().descriptor)
// Optional element
element("description", String.serializer().descriptor, isOptional = true)
// Element with annotations
element("timestamp", Long.serializer().descriptor, [
SerialName("created_at")
])
// Nested structure
element("address", buildClassSerialDescriptor("Address") {
element("street", String.serializer().descriptor)
element("city", String.serializer().descriptor)
})
};Factory for primitive descriptors.
fun PrimitiveSerialDescriptor(
serialName: String,
kind: PrimitiveKind
): SerialDescriptor{ .api }
Usage:
// Create custom primitive descriptor
const customStringDescriptor = PrimitiveSerialDescriptor(
"CustomString",
PrimitiveKind.STRING
);
// Use in serializer
class CustomStringSerializer {
constructor() {
this.descriptor = customStringDescriptor;
}
serialize(encoder, value) {
encoder.encodeString(value.toString().toUpperCase());
}
deserialize(decoder) {
return decoder.decodeString().toLowerCase();
}
}Creates a descriptor that wraps another descriptor.
fun SerialDescriptor(
serialName: String,
original: SerialDescriptor
): SerialDescriptor{ .api }
Usage:
// Create wrapper descriptor with different name
const originalDesc = String.serializer().descriptor;
const wrappedDesc = SerialDescriptor("WrappedString", originalDesc);
console.log(wrappedDesc.serialName); // "WrappedString"
console.log(wrappedDesc.kind); // PrimitiveKind.STRING (from original)Specialized factory functions for collection descriptors (Experimental).
@ExperimentalSerializationApi
fun listSerialDescriptor(elementDescriptor: SerialDescriptor): SerialDescriptor
@ExperimentalSerializationApi
inline fun <reified T> listSerialDescriptor(): SerialDescriptor
@ExperimentalSerializationApi
fun mapSerialDescriptor(
keyDescriptor: SerialDescriptor,
valueDescriptor: SerialDescriptor
): SerialDescriptor
@ExperimentalSerializationApi
inline fun <reified K, reified V> mapSerialDescriptor(): SerialDescriptor
@ExperimentalSerializationApi
fun setSerialDescriptor(elementDescriptor: SerialDescriptor): SerialDescriptor
@ExperimentalSerializationApi
inline fun <reified T> setSerialDescriptor(): SerialDescriptor{ .api }
Usage:
// Collection descriptors
const stringListDesc = listSerialDescriptor(String.serializer().descriptor);
const reifiedListDesc = listSerialDescriptor<String>();
const stringIntMapDesc = mapSerialDescriptor(
String.serializer().descriptor,
Int.serializer().descriptor
);
const reifiedMapDesc = mapSerialDescriptor<String, Int>();
const stringSetDesc = setSerialDescriptor(String.serializer().descriptor);
const reifiedSetDesc = setSerialDescriptor<String>();Retrieve descriptors for types.
inline fun <reified T> serialDescriptor(): SerialDescriptor
fun serialDescriptor(type: KType): SerialDescriptor{ .api }
Usage:
// Get descriptor for reified type
const userDesc = serialDescriptor<User>();
const listDesc = serialDescriptor<List<String>>();
// Get descriptor for KType
const nullableStringType = typeOf<String?>();
const nullableStringDesc = serialDescriptor(nullableStringType);val SerialDescriptor.nullable: SerialDescriptor
@ExperimentalSerializationApi
val SerialDescriptor.nonNullOriginal: SerialDescriptor{ .api }
Usage:
const stringDesc = String.serializer().descriptor;
const nullableStringDesc = stringDesc.nullable;
console.log(stringDesc.isNullable); // false
console.log(nullableStringDesc.isNullable); // true
// Get original non-null descriptor
const originalDesc = nullableStringDesc.nonNullOriginal;
console.log(originalDesc === stringDesc); // trueval SerialDescriptor.elementDescriptors: Iterable<SerialDescriptor>
val SerialDescriptor.elementNames: Iterable<String>{ .api }
Usage:
const userDesc = User.serializer().descriptor;
// Iterate element names
for (const name of userDesc.elementNames) {
console.log(`Element: ${name}`);
}
// Iterate element descriptors
for (const [index, elementDesc] of userDesc.elementDescriptors.entries()) {
console.log(`Element ${index}: ${elementDesc.serialName}`);
}
// Combined iteration
const names = Array.from(userDesc.elementNames);
const descriptors = Array.from(userDesc.elementDescriptors);
names.forEach((name, index) => {
const desc = descriptors[index];
console.log(`${name}: ${desc.kind}`);
});Access to the original KClass for contextual and polymorphic types (Experimental).
@ExperimentalSerializationApi
val SerialDescriptor.capturedKClass: KClass<*>?{ .api }
Usage:
const polymorphicDesc = PolymorphicSerializer(Shape::class).descriptor;
const capturedClass = polymorphicDesc.capturedKClass;
console.log(capturedClass === Shape::class); // true
const contextualDesc = ContextualSerializer(Date::class).descriptor;
const dateClass = contextualDesc.capturedKClass;
console.log(dateClass === Date::class); // trueRetrieve contextual information from SerializersModule (Experimental).
@ExperimentalSerializationApi
fun SerializersModule.getContextualDescriptor(
descriptor: SerialDescriptor
): SerialDescriptor?
@ExperimentalSerializationApi
fun SerializersModule.getPolymorphicDescriptors(
baseDescriptor: SerialDescriptor
): Collection<SerialDescriptor>{ .api }
Usage:
const module = SerializersModule {
contextual(Date::class, CustomDateSerializer)
polymorphic(Shape::class) {
subclass(Circle::class)
subclass(Rectangle::class)
}
};
// Get contextual descriptor
const dateDesc = Date::class.serializer().descriptor;
const contextualDesc = module.getContextualDescriptor(dateDesc);
// Get polymorphic descriptors
const shapeDesc = Shape::class.serializer().descriptor;
const subDescriptors = module.getPolymorphicDescriptors(shapeDesc);
console.log(subDescriptors.length); // 2 (Circle, Rectangle)function inspectDescriptor(descriptor, indent = 0) {
const spaces = ' '.repeat(indent);
console.log(`${spaces}Name: ${descriptor.serialName}`);
console.log(`${spaces}Kind: ${descriptor.kind}`);
console.log(`${spaces}Nullable: ${descriptor.isNullable}`);
console.log(`${spaces}Inline: ${descriptor.isInline}`);
console.log(`${spaces}Elements: ${descriptor.elementsCount}`);
if (descriptor.annotations.length > 0) {
console.log(`${spaces}Annotations: ${descriptor.annotations.map(a => a.constructor.name)}`);
}
// Inspect elements for structured types
if (descriptor.elementsCount > 0) {
for (let i = 0; i < descriptor.elementsCount; i++) {
const name = descriptor.getElementName(i);
const optional = descriptor.isElementOptional(i);
const elementDesc = descriptor.getElementDescriptor(i);
const annotations = descriptor.getElementAnnotations(i);
console.log(`${spaces} [${i}] ${name}${optional ? '?' : ''}:`);
if (annotations.length > 0) {
console.log(`${spaces} Annotations: ${annotations.map(a => a.constructor.name)}`);
}
// Recursive inspection for nested structures
if (elementDesc.elementsCount > 0) {
inspectDescriptor(elementDesc, indent + 4);
} else {
console.log(`${spaces} Type: ${elementDesc.serialName} (${elementDesc.kind})`);
}
}
}
}
// Usage
const userDescriptor = User.serializer().descriptor;
inspectDescriptor(userDescriptor);// Type guards for descriptor kinds
function isPrimitive(descriptor: SerialDescriptor): boolean {
return descriptor.kind instanceof PrimitiveKind;
}
function isStructured(descriptor: SerialDescriptor): boolean {
return descriptor.kind instanceof StructureKind;
}
function isPolymorphic(descriptor: SerialDescriptor): boolean {
return descriptor.kind instanceof PolymorphicKind;
}
// Specific structure checks
function isClass(descriptor: SerialDescriptor): boolean {
return descriptor.kind === StructureKind.CLASS;
}
function isList(descriptor: SerialDescriptor): boolean {
return descriptor.kind === StructureKind.LIST;
}
function isMap(descriptor: SerialDescriptor): boolean {
return descriptor.kind === StructureKind.MAP;
}Serial descriptors provide complete introspection capabilities for serializable types, enabling format implementors and advanced users to understand and work with the structure of any serializable type at runtime.
Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-core-js