kotlinx.serialization: Was es ist, Annotationen und JSON-Serialisierung

Autor: IT Sectr Veröffentlicht: 2026-03-15 Lesezeit: 12 Min.

kotlinx.serialization — eine plattformübergreifende Bibliothek von JetBrains zum Konvertieren von Kotlin-Objekten in JSON, ProtoBuf, CBOR und andere Formate ohne Reflektion. Im Gegensatz zu Gson und Moshi generiert sie Serialisierercode zur Compile-Zeit durch die @Serializable-Annotation, was hohe Leistung und Typsicherheit bietet. Laut GitHub Kotlin/kotlinx.serialization unterstützt die Bibliothek Kotlin/JVM, Kotlin/Native, Kotlin/JS und Kotlin/Wasm.

Wichtige Punkte

  • kotlinx.serialization — Compilezeit-Serialisierung: Code wird zur Compile-Zeit generiert, keine Reflektion
  • @Serializable — die Hauptannotation, die die Serialisierergenerierung für eine Klasse auslöst
  • Json {} builder — JSON-Konfiguration über Json { ignoreUnknownKeys = true; prettyPrint = true }
  • Plattformübergreifend — die Bibliothek läuft auf JVM, Native, JS und Wasm ohne API-Änderungen
  • Benutzerdefinierte Serialisierer — über das KSerializer-Interface für nicht standardisierte Datenformate

Was ist kotlinx.serialization

kotlinx.serialization ist eine integrierte Serialisierungsbibliothek für Kotlin, die von JetBrains als Teil des offiziellen Kotlin-Ökosystems entwickelt wurde. Der Hauptunterschied zu Drittanbieterlösungen (Gson, Moshi, Jackson) besteht darin, dass sie zur Laufzeit keine Reflektion verwendet. Stattdessen wird der Serialisierercode zur Compile-Zeit mit Kotlin Symbol Processing (KSP) oder dem Kotlin-Compiler-Plugin generiert. Dies bietet eine Leistungssteigerung von bis zu 3–5 Mal im Vergleich zu Gson und garantiert Typsicherheit.

Die Bibliothek unterstützt offiziell vier Formate: JSON (über das Modul kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) und HOCON (kotlinx-serialization-hocon). Die Formate werden als separate Abhängigkeiten in build.gradle.kts hinzugefügt, sodass keine unnötigen Bibliotheken ins Projekt gelangen. Jedes Format hat seinen eigenen Satz von Konfigurationsparametern.

Plattformübergreifend ist ein Hauptmerkmal der Bibliothek. Dieselbe Klasse mit @Serializable funktioniert auf allen Zielen: JVM (Android, Backend), Native (iOS), JS (Web, React) und Wasm (WebAssembly). Entwickler müssen keine unterschiedlichen Serialisierungsimplementierungen für jede Plattform schreiben — der Code bleibt einheitlich. Dies ist besonders wertvoll in Kotlin Multiplatform Mobile (KMM)-Projekten, wo gemeinsamer Code zwischen Android und iOS verwendet wird.

Wie die Compilezeit-Codegenerierung funktioniert

Die Codegenerierung in kotlinx.serialization erfolgt in drei Schritten. Im ersten Schritt erkennt der Kotlin-Compiler die @Serializable-Annotation auf einer Klasse und übergibt sie an das Kotlin Symbol Processing (KSP)-Plugin. Im zweiten Schritt generiert KSP ein Serialisiererobjekt, das das KSerializer-Interface implementiert. Im dritten Schritt wird der generierte Code zusammen mit dem Quellcode des Projekts kompiliert. Dadurch wird keiner dieser Schritte zur Laufzeit der Anwendung ausgeführt.

Der generierte Serialisierer arbeitet direkt mit den Feldern der Klasse über deren Getter und Setter, ohne Reflektion. Dies bedeutet, dass Felder mit dem private-Modifikator ebenfalls serialisiert werden, wenn sie mit @Serializable markiert sind. Die Leistung dieses Ansatzes kommt der manuellen Serialisierung nahe: für einfache Klassen (5–10 Felder) beträgt die Serialisierungszeit 10–50 Mikrosekunden; für komplexe Objektgraphen bis zu 200 Mikrosekunden pro 1000 Objekte.

Um die Bibliothek zu einem Android- oder Kotlin/JVM-Projekt hinzuzufügen, müssen Sie das Plugin und die Abhängigkeiten in build.gradle.kts hinzufügen. Das Plugin org.jetbrains.kotlin.plugin.serialization in der zur Kotlin-Version passenden Version aktiviert die Codegenerierung. Die Bibliothek kotlinx-serialization-json wird im Abschnitt dependencies mit einer von der Kotlin-Version unabhängigen Version hinzugefügt.

kotlin
// build.gradle.kts — kotlinx.serialization hinzufügen
plugins {
    val kotlinVersion = "2.1.0"
    kotlin("jvm") version kotlinVersion
    kotlin("plugin.serialization") version kotlinVersion
}

dependencies {
    // Hauptserialisierungsmodul
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // Zusätzliche Formate
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}

Grundlegende Verwendung: JSON-Serialisierung

JSON ist das beliebteste Format in kotlinx.serialization. Um ein Objekt zu serialisieren, versehen Sie die data class mit der @Serializable-Annotation und rufen Json.encodeToString() auf. Für die Deserialisierung rufen Sie Json.decodeFromString() mit Angabe des Typs auf. Die Bibliothek behandelt automatisch null-Felder, Listen, verschachtelte Objekte und Enums. Standardmäßig sind alle Felder der Klasse erforderlich, sofern nichts anderes angegeben ist.

Die JSON-Konfiguration erfolgt über den Json {} builder. Sie können ignoreUnknownKeys = true übergeben, um unbekannte Felder bei der Deserialisierung zu überspringen, prettyPrint = true für formatierte Ausgabe, coerceInputValues = true zur Konvertierung ungültiger Werte in Standardwerte. Verfügbar sind auch encodeDefaults (Serialisierung von Feldern mit Standardwerten) und classDiscriminator (Feldname für polymorphe Serialisierung).

kotlin
// Beispiel für JSON-Serialisierung und -Deserialisierung
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

@Serializable
data class Project(
    val name: String,
    val stars: Int,
    val isActive: Boolean = true,
    val languages: List<String> = emptyList()
)

fun main() {
    val project = Project(
        name = "kotlinx.serialization",
        stars = 7200,
        languages = listOf("Kotlin", "Java")
    )

    // JSON-Serialisierung mit prettyPrint
    val json = Json { prettyPrint = true }
    val jsonString = json.encodeToString(project)
    println(jsonString)
    /*
    {
        "name": "kotlinx.serialization",
        "stars": 7200,
        "isActive": true,
        "languages": ["Kotlin", "Java"]
    }
    */

    // Deserialisierung von JSON
    val decoded = json.decodeFromString<Project>(jsonString)
    println(decoded.name)  // kotlinx.serialization
}

Das Beispiel zeigt den grundlegenden Zyklus der Serialisierung und Deserialisierung. Eine data class Project mit der @Serializable-Annotation erhält automatisch encodeToString und decodeFromString. Das Feld isActive hat den Standardwert true — wenn dieses Feld in JSON fehlt, wird der Standardwert verwendet. Wenn ohne ignoreUnknownKeys = true unbekannte Felder in JSON ankommen, wird eine SerializationException ausgelöst.

Polymorphe Serialisierung von sealed class

Sealed class ist einer der leistungsstärksten Anwendungsfälle von kotlinx.serialization. Die Bibliothek unterstützt polymorphe Serialisierung für sealed class-Hierarchien ohne zusätzliche Konfiguration: versehen Sie einfach die sealed class und alle ihre Unterklassen mit @Serializable. Bei der Serialisierung wird ein Feld „type“ hinzugefügt (konfigurierbar über classDiscriminator), das bei der Deserialisierung den konkreten Typ bestimmt.

kotlin
// Polymorphe Serialisierung von sealed class
@Serializable
sealed class Response

@Serializable
data class Success(val data: String) : Response()

@Serializable
data class Error(val code: Int, val message: String) : Response()

fun main() {
    val json = Json { classDiscriminator = "result_type" }

    val responses: List<Response> = listOf(
        Success(data = "Data loaded"),
        Error(code = 404, message = "Not found")
    )

    val jsonString = json.encodeToString(responses)
    println(jsonString)
    /*
    [
        {"result_type":"Success","data":"Data loaded"},
        {"result_type":"Error","code":404,"message":"Not found"}
    ]
    */

    val decoded = json.decodeFromString<List<Response>>(jsonString)
    when (val first = decoded[0]) {
        is Success -> println("Success: ${first.data}")
        is Error -> println("Error: ${first.code}")
    }
}

Die polymorphe Serialisierung von sealed class ist besonders nützlich in API-Clients, wo der Server verschiedene Antworttypen zurückgibt. Ohne kotlinx.serialization müssten Sie einen manuellen Deserialisierer mit einer when-Anweisung auf das Diskriminatorfeld schreiben. Mit der Bibliothek wird dies mit einer einzigen Annotation erledigt. classDiscriminator ermöglicht die Umbenennung des Markierungsfelds (Standard „type“) in einen beliebigen, vom Server erwarteten Wert.

kotlinx.serialization-Annotationen: vollständige Übersicht

Die Bibliothek bietet eine Reihe von Annotationen zur Feinabstimmung der Serialisierung. Die wichtigste ist @Serializable für eine Klasse. Zusätzliche: @SerialName zum Festlegen eines Feldnamens in JSON (falls abweichend vom Kotlin-Namen), @Transient zum Ausschließen eines Felds aus der Serialisierung, @Required für ein Feld, das in JSON vorhanden sein muss, @EncodeDefault zur erzwungenen Serialisierung eines Felds auch mit seinem Standardwert.

AnnotationZweckBeispiel
@SerializableAktiviert die Serialisierergenerierung für eine Klasse@Serializable data class User
@SerialNameLegt einen alternativen Feldnamen im Format fest@SerialName(„user_name“) val name: String
@TransientSchließt ein Feld von der Serialisierung aus@Transient val cache: MutableMap
@RequiredFeld ist in JSON bei Deserialisierung erforderlich@Required val id: String
@EncodeDefaultSerialisiert das Feld auch mit Standardwert@EncodeDefault val type: Type = Type.A
@SerializerBindet einen benutzerdefinierten Serialisierer an eine Klasse@Serializer(forClass = Date::class)

Die @SerialName-Annotation ist entscheidend bei der Arbeit mit APIs, wo Feldnamen in snake_case vorliegen, der Kotlin-Stil jedoch camelCase ist. Beispielsweise sendet der Server „user_id“, während der Kotlin-Code userId verwendet. @SerialName(„user_id“) löst dieses Problem ohne zusätzliche Mapper. @Transient eignet sich für Felder, die nicht an den Server gesendet werden sollen — wie temporäre berechnete Werte oder Caches.

@Required als Alternative zu nullable-Feldern

Standardmäßig sind alle Felder in kotlinx.serialization erforderlich. Wenn ein Feld in JSON fehlen kann, müssen Sie es nullable machen (String?) oder einen Standardwert setzen (val name: String = „“). Es gibt jedoch Situationen, in denen ein Feld in Kotlin non-nullable ist, aber aufgrund der API-Versionierung in JSON fehlen kann. In diesem Fall wirft @Required eine SerializationException, wenn das Feld fehlt, während ein Standardwert es fehlerlos füllt.

Benutzerdefinierte Serialisierer: KSerializer und manuelle Steuerung

KSerializer ist das Interface, das alle Serialisierer in kotlinx.serialization implementieren. Wenn die Standard-Codegenerierung nicht geeignet ist (z.B. für die Arbeit mit Date, Bitmap oder einem spezifischen Binärformat), können Sie Ihren eigenen Serialisierer schreiben. Dazu implementieren Sie die Methoden serialize() und deserialize() und stellen einen Deskriptor bereit — eine Strukturbeschreibung für das Formatschema.

Benutzerdefinierte Serialisierer werden auf zwei Arten angebunden: über die @Serializable(with = MySerializer::class)-Annotation zur Bindung an eine bestimmte Klasse oder global über Json { serializersModule = ... } zur Bindung an alle Instanzen eines Typs. Die zweite Methode ist für eingebaute Typen (Date, UUID) vorzuziehen, um nicht auf jedem Feld eine Annotation schreiben zu müssen.

kotlin
// Benutzerdefinierter Serialisierer für java.util.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object DateSerializer : KSerializer<Date> {
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Date) {
        encoder.encodeString(dateFormat.format(value))
    }

    override fun deserialize(decoder: Decoder): Date {
        return dateFormat.parse(decoder.decodeString())
    }
}

// Verwendung eines benutzerdefinierten Serialisierers
@Serializable
data class Event(
    val title: String,
    @Serializable(with = DateSerializer::class)
    val date: Date
)

fun main() {
    val json = Json { prettyPrint = true }
    val event = Event("Release", Date())
    println(json.encodeToString(event))
}

Im Beispiel konvertiert DateSerializer java.util.Date in einen ISO-8601-String. Ohne einen benutzerdefinierten Serialisierer kann kotlinx.serialization nicht mit Date arbeiten — es ist ein Typ, der nicht in der Standardbibliothek von Kotlin enthalten ist. @Serializable(with = DateSerializer::class) auf einem bestimmten Feld bindet den Serialisierer nur für dieses Feld. Für die globale Registrierung aller Dates verwenden Sie Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.

Serialisierungsformate: JSON, ProtoBuf, CBOR, HOCON

kotlinx.serialization beschränkt sich nicht auf JSON. Die Bibliothek unterstützt vier integrierte Formate, jedes mit eigenem Modul und Konfiguration. JSON (kotlinx-serialization-json) ist universell, menschenlesbar, geeignet für REST-APIs. ProtoBuf (kotlinx-serialization-protobuf) ist binär, kompakt, mit erforderlichem Schema, für hochbelastete Microservices. CBOR (kotlinx-serialization-cbor) ist ein binäres JSON-Äquivalent, geeignet für IoT und mobile Geräte mit begrenzter Bandbreite. HOCON (kotlinx-serialization-hocon) ist ein mit TypeSafe Config kompatibles Konfigurationsformat.

FormatModulTypSchemaTypische Verwendung
JSONkotlinx-serialization-jsonTextOptionalREST-API, Datenspeicherung
ProtoBufkotlinx-serialization-protobufBinärErforderlich (.proto)Microservices, gRPC
CBORkotlinx-serialization-cborBinärOptionalIoT, mobile Geräte
HOCONkotlinx-serialization-hoconTextOptionalKonfigurationsdateien

ProtoBuf erfordert die Definition eines Schemas in .proto-Dateien, aber kotlinx-serialization-protobuf generiert Kotlin-Klassen direkt aus @Serializable ohne .proto. Dies vereinfacht die Entwicklung: annotieren Sie einfach die data class und verwenden ProtoBuf.encodeToByteArray(). CBOR ist besonders relevant für Android, wenn kompakte Binärdaten über NFC oder BLE übertragen werden müssen. CBOR-Nachrichten sind bei gleichem Datensatz im Durchschnitt 20–30% kleiner als JSON.

Formatwahl für das Projekt

Für REST-APIs in einer mobilen App ist JSON die beste Wahl — es kann ohne zusätzliche Tools debuggt werden, ist in Logs lesbar und mit jedem Backend kompatibel. Wenn Ihre App große Datenmengen zwischen Microservices überträgt (hunderte Megabyte), bietet ProtoBuf durch die binäre Kodierung einen Geschwindigkeitsvorteil von bis zu 5x. Zum Speichern von Einstellungen in Dateien verwenden Sie HOCON oder JSON. Für Geräte mit strengen Traffic-Limits (IoT-Sensoren) verwenden Sie CBOR.

Häufige Fehler bei der Arbeit mit kotlinx.serialization

Der erste Fehler ist das Ignorieren unbekannter Schlüssel bei der Deserialisierung. Wenn der Server ein neues Feld hinzufügt und Sie ignoreUnknownKeys = false haben, stürzt die Anwendung mit einer SerializationException ab. Diese Flag ist standardmäßig deaktiviert. Lösung: Setzen Sie für Produktionscode immer Json { ignoreUnknownKeys = true }, um gegen API-Änderungen resilient zu sein.

Der zweite Fehler ist die Serialisierung interner oder privater Felder in einer data class. In einer Kotlin-data class werden alle Felder des primären Konstruktors standardmäßig serialisiert. Wenn ein Feld sensible Daten enthält (Passwort, Token), muss es mit @Transient markiert oder aus dem primären Konstruktor entfernt werden. @Transient schließt das Feld vollständig aus JSON aus, kann aber innerhalb des Konstruktors einen Fehler verursachen — besser ist es, solche Felder im Klassenrumpf mit @Transient zu definieren.

Der dritte Fehler ist die polymorphe Serialisierung ohne sealed class. Wenn Sie eine open class anstelle von sealed verwenden, benötigt kotlinx.serialization die explizite Registrierung aller Unterklassen in serializersModule. Im Gegensatz zu sealed class, wo der Compiler alle Unterklassen kennt, erlaubt open class beliebige Erweiterungen — die Bibliothek kann nicht automatisch alle Subtypen ermitteln. Die Registrierung erfolgt über Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.

Fehler bei der Bibliotheksversionierung

Die Version von kotlinx.serialization muss mit der Kotlin-Version kompatibel sein. JetBrains veröffentlicht eine Kompatibilitätstabelle: kotlinx-serialization 1.6.x ist kompatibel mit Kotlin 1.9.x, 1.7.x mit Kotlin 2.0.x und 2.1.x. Versionsinkompatibilitäten verursachen kryptische Compiler-Fehler wie „Symbol ‘serializer’ is missing“. Überprüfen Sie immer die aktuelle Version auf Maven Central oder im GitHub-Repository des Projekts.

Häufig gestellte Fragen

Wie unterscheidet sich kotlinx.serialization von Gson und Moshi?

kotlinx.serialization verwendet Compilezeit-Codegenerierung über KSP, während Gson und Moshi Laufzeit-Reflektion verwenden. Dies bietet einen Leistungsvorteil (3–5x schneller als Gson) und Typsicherheit. Gson serialisiert jedes Feld ohne Annotation, was zu Datenlecks führen kann. kotlinx.serialization erfordert die explizite @Serializable-Annotation, was sicherer ist. Moshi unterstützt ebenfalls Codegen, aber nur für JVM und Android.

Unterstützt kotlinx.serialization Kotlin Multiplatform?

Ja, kotlinx.serialization ist eine offizielle plattformübergreifende Bibliothek von JetBrains. Sie läuft auf Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) und Kotlin/Wasm. Die API ist auf allen Plattformen einheitlich: @Serializable + Json.encodeToString() funktioniert überall gleich. Für iOS ist keine zusätzliche Einrichtung erforderlich — Kotlin/Native kompiliert den serialisierten Code in ein nativer Binärdatei.

Wie werden null-Felder in JSON behandelt?

Nullable-Felder (String?) werden als null deserialisiert, wenn der Wert in JSON fehlt oder null ist. Bei nicht-nullbaren Feldern (String) ohne Standardwert führt das Fehlen des Felds in JSON zu einer SerializationException. Wenn Sie möchten, dass null-Werte nicht in JSON erscheinen, konfigurieren Sie Json { encodeDefaults = false }. Dies schließt alle Felder aus, die ihrem Standard entsprechen (einschließlich null für nullable-Typen).

Was tun, wenn der Server snake_case-Felder sendet?

Verwenden Sie @SerialName(„snake_case_name“) auf jedem Feld, dessen Name vom Kotlin-Format abweicht. Alternativ ist für Kotlin 2.0+ Json { namingStrategy = JsonNamingStrategy.SnakeCase } für die automatische camelCase ↔ snake_case-Konvertierung verfügbar. Diese Einstellung wird auf alle Felder gleichzeitig angewendet. Wenn eine teilweise Anpassung erforderlich ist, kombinieren Sie @SerialName mit der globalen Strategie.

Kann ich Kotlin Flow oder Coroutinen serialisieren?

Nein, Flow und Coroutinen sind nicht direkt serialisierbar — sie repräsentieren asynchrone Ausführung, keine Daten. Um Daten aus einem Flow zu übertragen, sammeln Sie sie über .toList() in einer Coroutine in einer Sammlung und serialisieren die Sammlung. Ebenso können Sie Job, Deferred oder Continuation nicht serialisieren. Serialisieren Sie nur data classes — Datenmodelle ohne Verhaltenslogik.

Zusammenfassung

  • kotlinx.serialization — Compilezeit-Serialisierung über @Serializable, ohne Reflektion, bis zu 5x schneller als Gson
  • @Serializable, @SerialName, @Transient — Schlüsselannotationen zur Konfiguration der Feld- und Klassenserialisierung
  • Json {} builder konfiguriert JSON: ignoreUnknownKeys, prettyPrint, coerceInputValues, encodeDefaults
  • Sealed class und polymorphe Serialisierung — nahtlose Unterstützung von Typhierarchien ohne zusätzlichen Code
  • KSerializer — Interface für benutzerdefinierte Serialisierer nicht standardisierter Typen (Date, Bitmap, UUID)
  • Vier Formate: JSON, ProtoBuf, CBOR, HOCON — als Module hinzufügbar, einheitliche API für alle
  • Plattformübergreifend — einheitliche Codebasis für JVM, Native, JS und Wasm; kritisch für KMM und gemeinsame Module

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch