Kotlin multiplatform reflectionless serialization library core API and infrastructure
—
The descriptors system provides structural metadata and introspection capabilities for serializable types. It enables format implementations to understand the structure of serializable classes without relying on reflection.
The main interface for describing the structure and metadata of serializable types.
interface SerialDescriptor {
val serialName: String
val kind: SerialKind
val elementsCount: Int
val annotations: List<Annotation>
val isNullable: Boolean
fun getElementName(index: Int): String
fun getElementIndex(name: String): Int
fun getElementAnnotations(index: Int): List<Annotation>
fun getElementDescriptor(index: Int): SerialDescriptor
fun isElementOptional(index: Int): Boolean
}Properties:
serialName: Unique name identifying the serializable classkind: The kind of serializable structure (class, list, map, etc.)elementsCount: Number of elements/properties in the descriptorannotations: Annotations applied to the classisNullable: Whether the type can be nullMethods:
getElementName(index): Gets the name of the element at the given indexgetElementIndex(name): Gets the index of the element with the given namegetElementAnnotations(index): Gets annotations for the element at the given indexgetElementDescriptor(index): Gets the nested descriptor for the element at the given indexisElementOptional(index): Checks if the element at the given index is optionalSerial kinds categorize different types of serializable structures.
sealed class SerialKind {
object ENUM : SerialKind()
object CONTEXTUAL : SerialKind()
}Represents primitive types.
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()
}Represents structured types.
sealed class StructureKind : SerialKind() {
object CLASS : StructureKind()
object LIST : StructureKind()
object MAP : StructureKind()
object OBJECT : StructureKind()
}Represents polymorphic types.
@ExperimentalSerializationApi
sealed class PolymorphicKind : SerialKind() {
object SEALED : PolymorphicKind()
object OPEN : PolymorphicKind()
}Creates a descriptor for class-like structures.
fun buildClassSerialDescriptor(
serialName: String,
vararg typeParameters: SerialDescriptor,
builderAction: ClassSerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptorUsage:
val userDescriptor = buildClassSerialDescriptor("User") {
element<String>("name")
element<String>("email")
element<Int>("age", isOptional = true)
}Creates a generic descriptor with a specified kind.
fun buildSerialDescriptor(
serialName: String,
kind: SerialKind,
vararg typeParameters: SerialDescriptor,
builder: SerialDescriptorBuilder.() -> Unit = {}
): SerialDescriptor@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(): SerialDescriptorUsage:
val stringListDescriptor = listSerialDescriptor(String.serializer().descriptor)
val intStringMapDescriptor = mapSerialDescriptor(
Int.serializer().descriptor,
String.serializer().descriptor
)Base builder for creating descriptors.
abstract class SerialDescriptorBuilder {
abstract fun element(
elementName: String,
descriptor: SerialDescriptor,
annotations: List<Annotation> = emptyList(),
isOptional: Boolean = false
)
inline fun <reified T> element(
elementName: String,
serializer: KSerializer<T> = serializer(),
annotations: List<Annotation> = emptyList(),
isOptional: Boolean = false
)
}Specialized builder for class descriptors.
class ClassSerialDescriptorBuilder(serialName: String) : SerialDescriptorBuilder() {
override fun element(
elementName: String,
descriptor: SerialDescriptor,
annotations: List<Annotation>,
isOptional: Boolean
)
}Usage:
val descriptor = buildClassSerialDescriptor("Person") {
element<String>("firstName")
element<String>("lastName")
element<Int>("age", isOptional = true)
element("address", Address.serializer().descriptor, isOptional = true)
}val BOOLEAN_DESCRIPTOR: SerialDescriptor
val BYTE_DESCRIPTOR: SerialDescriptor
val CHAR_DESCRIPTOR: SerialDescriptor
val SHORT_DESCRIPTOR: SerialDescriptor
val INT_DESCRIPTOR: SerialDescriptor
val LONG_DESCRIPTOR: SerialDescriptor
val FLOAT_DESCRIPTOR: SerialDescriptor
val DOUBLE_DESCRIPTOR: SerialDescriptor
val STRING_DESCRIPTOR: SerialDescriptor
val UNIT_DESCRIPTOR: SerialDescriptorMarks descriptors that require serialization context.
@Target(AnnotationTarget.CLASS)
annotation class ContextAwareFor inline value classes and primitive wrappers.
fun SerialDescriptor.getInlinedSerializer(): KSerializer<*>?
fun buildInlineDescriptor(
inlineSerializerName: String,
inlineValueDescriptor: SerialDescriptor
): SerialDescriptorfun SerialDescriptor.hashCodeImpl(): Int
fun SerialDescriptor.equalsImpl(other: SerialDescriptor): Boolean// Creating a descriptor for a custom data structure
val customDescriptor = buildClassSerialDescriptor("CustomData") {
element<String>("id")
element<List<String>>("tags")
element<Map<String, Any>>("metadata", isOptional = true)
}
// Using the descriptor to understand structure
println("Serial name: ${customDescriptor.serialName}")
println("Kind: ${customDescriptor.kind}")
println("Elements count: ${customDescriptor.elementsCount}")
// Using extension properties
for ((index, elementDescriptor) in customDescriptor.elementDescriptors.withIndex()) {
val elementName = customDescriptor.getElementName(index)
val isOptional = customDescriptor.isElementOptional(index)
println("Element $index: $elementName (${elementDescriptor.serialName}) - Optional: $isOptional")
}
// Iterate over element names
for (elementName in customDescriptor.elementNames) {
val index = customDescriptor.getElementIndex(elementName)
println("Element '$elementName' is at index $index")
}// Descriptor for List<User>
val userListDescriptor = listSerialDescriptor(User.serializer().descriptor)
// Descriptor for Map<String, User>
val userMapDescriptor = mapSerialDescriptor(
String.serializer().descriptor,
User.serializer().descriptor
)
// Check descriptor properties
println("List kind: ${userListDescriptor.kind}") // StructureKind.LIST
println("Map kind: ${userMapDescriptor.kind}") // StructureKind.MAPInstall with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-serialization-core