kotlinx.serialization: JSON und data class ohne Handarbeit verbinden
Eine API antwortet mit JSON, deine App will eine data class. Dazwischen steht in vielen Projekten ein handgeschriebener Parser. Mit kotlinx.serialization sind es eine Annotation und eine Zeile – und ein Schalter, ohne den deine App beim nächsten API-Update abstürzt.
Jede App, die ich baue, redet irgendwann mit einem Server, und der antwortet mit JSON. Früher stand dafür bei mir eine Klasse voller getString("name")-Aufrufe – Zeile für Zeile von Hand, und bei jedem neuen Feld musste ich sie anfassen. kotlinx.serialization nimmt einem das ab: Du beschreibst die Form einmal als data class, den Rest erzeugt der Compiler.
Einrichten
Im Modul-build.gradle.kts:
plugins {
kotlin("plugin.serialization") version "2.1.0"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}Die data class beschreibt die Antwort
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Ort(
val name: String,
val land: String = "DE", // fehlt das Feld, greift der Standard
@SerialName("lat") val breite: Double, // anderer Name im JSON als im Code
@SerialName("lon") val laenge: Double,
val einwohner: Int? = null // darf auch null sein
)
val json = Json { ignoreUnknownKeys = true }
val antwort = """{"name":"Regensburg","lat":49.0134,"lon":12.1016,"zeitzone":"Europe/Berlin"}"""
val ort: Ort = json.decodeFromString(antwort)
println(ort.name) // Regensburg
println(ort.land) // DE (stand nicht im JSON – Standardwert)
println(ort.einwohner) // null
val zurueck: String = json.encodeToString(ort)Kein Parser, keine Reflexion zur Laufzeit: Der Compiler erzeugt beim Bauen einen Serializer für jede @Serializable-Klasse. Deshalb bekommst du Fehler bereits beim Kompilieren und nicht erst, wenn der Nutzer die Seite öffnet.
Der Schalter, ohne den es knallt
ignoreUnknownKeys = true ist keine Kosmetik. Ohne ihn wirft decodeFromString eine Ausnahme, sobald das JSON ein Feld enthält, das deine Klasse nicht kennt. Und genau das passiert früher oder später: Der Serverbetreiber ergänzt ein Feld, seine App-Version ändert sich nicht – und deine stürzt ab. Im Beispiel oben ist "zeitzone" so ein Feld.
Zwei weitere Einstellungen, die ich fast immer setze:
val json = Json {
ignoreUnknownKeys = true // unbekannte Felder überspringen statt abstürzen
coerceInputValues = true // null in einem nicht-nullbaren Feld → Standardwert
prettyPrint = false // beim Senden: kompakt, spart Bytes
}coerceInputValues ist der zweite Rettungsring: Manche APIs schicken null, wo laut Dokumentation ein Wert stehen sollte. Ohne den Schalter fliegt eine Ausnahme, mit ihm greift der Standardwert deiner data class.
Was du dir merken solltest
Ein Feld ohne Standardwert ist Pflicht. Fehlt es im JSON, gibt es eine MissingFieldException – das ist gewollt, denn ein Pflichtfeld, das schweigend null wird, ist die schlimmere Variante. Wenn ein Feld optional ist, gib ihm einen Standardwert oder mach es nullbar.
Standardwerte werden beim Senden nicht mitgeschrieben. encodeToString lässt Felder weg, die noch auf ihrem Standard stehen. Brauchst du sie in der ausgehenden Nachricht, setz encodeDefaults = true.
Für Retrofit gibt es einen passenden Converter (Json.asConverterFactory), sodass du decodeFromString gar nicht selbst aufrufen musst – die Schnittstelle liefert direkt deine data class.
Für die Android-Apps, die ich baue, ist das inzwischen der Standardweg: eine Datei mit @Serializable-Klassen, ein Json-Objekt mit den drei Schaltern oben, fertig. Wenn du gerade eine App planst und über solche Grundentscheidungen sprechen willst – meld dich einfach über bymw.de, ich schaue mir das gern mit dir an.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
callbackFlow – Listener-APIs in einen Flow verwandeln
Android steckt voller Callback-APIs mit `register`/`unregister`. Mit `callbackFlow` machst du daraus einen ganz normalen Flow – inklusive automatischem Abmelden, wenn niemand mehr zuhört.
AnimatedVisibility in Jetpack Compose – ein- und ausblenden, das nicht springt
Ein Element einfach mit if wegzulassen, poppt hart weg. AnimatedVisibility blendet es weich ein und aus – mit sinnvollen Voreinstellungen und, wenn du willst, deiner eigenen Kombination aus Faden und Bewegung. Ich zeige dir beides.
snapshotFlow in Jetpack Compose – aus Compose-Zustand einen Flow machen
Manchmal willst du auf einen Compose-Zustand reagieren wie auf einen Datenstrom – entprellen, kombinieren, sammeln. Genau das macht snapshotFlow. Ich zeige dir das an der häufigsten Aufgabe: mehr laden, wenn der Nutzer ans Listenende scrollt.