Kotlin multiplatform serialization runtime library core module with JVM target support
—
The descriptor system in kotlinx.serialization provides structural metadata about serializable types. Descriptors describe the shape, names, and characteristics of serialized data, enabling format implementations to understand how to encode and decode objects without needing to know the actual Kotlin types.
The fundamental interface that describes the structure of any serializable type.
/**
* Describes the structure of a serializable type including its name, kind, and elements.
* Used by encoders and decoders to understand the shape of data they're processing.
*/
interface SerialDescriptor {
/** The serial name of the described type */
val serialName: String
/** The kind of serial data (primitive, structured, etc.) */
val kind: SerialKind
/** Number of elements in this descriptor */
val elementsCount: Int
/** Annotations present on the described type */
val annotations: List<Annotation>
/** Whether this descriptor represents an inline value class */
val isInline: Boolean
/** Whether this descriptor is nullable */
val isNullable: Boolean
/** Get the name of element at specified index */
fun getElementName(index: Int): String
/** Get the index of element with specified name */
fun getElementIndex(name: String): Int
/** Get annotations for element at specified index */
fun getElementAnnotations(index: Int): List<Annotation>
/** Get descriptor for element at specified index */
fun getElementDescriptor(index: Int): SerialDescriptor
/** Check if element at specified index is optional */
fun isElementOptional(index: Int): Boolean
}Usage Examples:
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
@Serializable
data class User(val name: String, val age: Int, val email: String? = null)
val descriptor = User.serializer().descriptor
println(descriptor.serialName) // "User"
println(descriptor.kind) // StructureKind.CLASS
println(descriptor.elementsCount) // 3
// Inspect elements
for (i in 0 until descriptor.elementsCount) {
println("${descriptor.getElementName(i)}: ${descriptor.isElementOptional(i)}")
}
// name: false
// age: false
// email: trueType hierarchy that categorizes different kinds of serializable data structures.
/**
* Base sealed class for all serial kinds that categorize serializable types.
*/
sealed class SerialKind
/**
* Kinds for primitive values that can be directly encoded/decoded.
*/
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()
}
/**
* Kinds for structured values that contain multiple elements.
*/
sealed class StructureKind : SerialKind() {
/** Regular class with named properties */
object CLASS : StructureKind()
/** Ordered collection of elements */
object LIST : StructureKind()
/** Key-value mapping */
object MAP : StructureKind()
/** Object instance (singleton) */
object OBJECT : StructureKind()
}
/**
* Kinds for polymorphic types that can have multiple runtime implementations.
*/
sealed class PolymorphicKind : SerialKind() {
/** Sealed class hierarchy with known subclasses */
object SEALED : PolymorphicKind()
/** Open class/interface with potentially unknown subclasses */
object OPEN : PolymorphicKind()
}
/**
* Special kinds for specific serialization scenarios.
*/
object SerialKind {
/** Type that requires contextual serializer lookup */
object CONTEXTUAL : SerialKind()
/** Enum type */
object ENUM : SerialKind()
}Factory functions for creating descriptors programmatically.
/**
* Creates a primitive descriptor for basic types.
* @param serialName The name of the described type
* @param kind The primitive kind
* @return SerialDescriptor for the primitive type
*/
fun PrimitiveSerialDescriptor(serialName: String, kind: PrimitiveKind): SerialDescriptor
/**
* DSL builder for creating class descriptors with elements.
* @param serialName The name of the described type
* @param builderAction Lambda to configure descriptor elements
* @return SerialDescriptor for the class
*/
fun buildClassSerialDescriptor(
serialName: String,
builderAction: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor
/**
* Creates a descriptor identical to original but with different name.
* @param serialName The new name for the descriptor
* @param original The original descriptor to copy
* @return SerialDescriptor with new name
*/
fun SerialDescriptor(serialName: String, original: SerialDescriptor): SerialDescriptor
/**
* Builder class for constructing class descriptors.
*/
class ClassSerialDescriptorBuilder {
/** Add an element to the descriptor */
fun element(
elementName: String,
descriptor: SerialDescriptor,
annotations: List<Annotation> = emptyList(),
isOptional: Boolean = false
)
/** Add annotations to the descriptor itself */
fun annotations(annotations: List<Annotation>)
}Usage Examples:
import kotlinx.serialization.descriptors.*
// Create primitive descriptor
val stringDescriptor = PrimitiveSerialDescriptor("MyString", PrimitiveKind.STRING)
// Create class descriptor with DSL
val personDescriptor = buildClassSerialDescriptor("Person") {
element("name", PrimitiveSerialDescriptor("kotlin.String", PrimitiveKind.STRING))
element("age", PrimitiveSerialDescriptor("kotlin.Int", PrimitiveKind.INT))
element("email", PrimitiveSerialDescriptor("kotlin.String", PrimitiveKind.STRING), isOptional = true)
}
// Copy descriptor with new name
val aliasDescriptor = SerialDescriptor("PersonAlias", personDescriptor)Specialized functions for creating descriptors for collection types.
/**
* Creates a descriptor for List<T> structures.
* @param elementDescriptor Descriptor for list elements
* @return SerialDescriptor for List type
*/
fun listSerialDescriptor(elementDescriptor: SerialDescriptor): SerialDescriptor
/**
* Creates a descriptor for Set<T> structures.
* @param elementDescriptor Descriptor for set elements
* @return SerialDescriptor for Set type
*/
fun setSerialDescriptor(elementDescriptor: SerialDescriptor): SerialDescriptor
/**
* Creates a descriptor for Map<K,V> structures.
* @param keyDescriptor Descriptor for map keys
* @param valueDescriptor Descriptor for map values
* @return SerialDescriptor for Map type
*/
fun mapSerialDescriptor(
keyDescriptor: SerialDescriptor,
valueDescriptor: SerialDescriptor
): SerialDescriptorFunctions for obtaining descriptors at runtime.
/**
* Retrieves a descriptor for the given reified type T.
* @return SerialDescriptor for type T
*/
inline fun <reified T> serialDescriptor(): SerialDescriptor
/**
* Retrieves a descriptor for the given KType.
* @param type The KType to get descriptor for
* @return SerialDescriptor for the specified type
*/
fun serialDescriptor(type: KType): SerialDescriptorUsage Examples:
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlin.reflect.typeOf
@Serializable
data class Product(val name: String, val price: Double)
// Get descriptor for known type
val productDescriptor = serialDescriptor<Product>()
// Get descriptor for collection types
val listDescriptor = serialDescriptor<List<Product>>()
val mapDescriptor = serialDescriptor<Map<String, Product>>()
// Runtime descriptor lookup
val typeDescriptor = serialDescriptor(typeOf<List<String>>())
// Create collection descriptors manually
val customListDesc = listSerialDescriptor(PrimitiveSerialDescriptor("kotlin.String", PrimitiveKind.STRING))
val customMapDesc = mapSerialDescriptor(
PrimitiveSerialDescriptor("kotlin.String", PrimitiveKind.STRING),
serialDescriptor<Product>()
)Additional properties available on descriptors for advanced use cases.
/**
* Extension property that returns the captured KClass for the descriptor.
* Useful for runtime type checking and reflection operations.
*/
val SerialDescriptor.capturedKClass: KClass<Any>?When implementing custom serializers, you typically need to provide appropriate descriptors:
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
object LocalDateSerializer : KSerializer<LocalDate> {
override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDate) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): LocalDate {
return LocalDate.parse(decoder.decodeString())
}
}Descriptors help format implementations understand data structure:
fun encodeStructure(descriptor: SerialDescriptor, encoder: Encoder) {
when (descriptor.kind) {
StructureKind.CLASS -> encodeObject(descriptor, encoder)
StructureKind.LIST -> encodeArray(descriptor, encoder)
StructureKind.MAP -> encodeMap(descriptor, encoder)
is PrimitiveKind -> encodePrimitive(descriptor, encoder)
else -> throw SerializationException("Unsupported kind: ${descriptor.kind}")
}
}Descriptor-related errors typically indicate structural mismatches:
Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-core-jvm