kotlinx.serialization — een multiplatformbibliotheek van JetBrains voor het converteren van Kotlin-objecten naar JSON, ProtoBuf, CBOR en andere formaten zonder reflectie. In tegenstelling tot Gson en Moshi genereert het de serialisatiecode tijdens compilatie via de @Serializable-annotatie, wat hoge prestaties en typeveiligheid biedt. Volgens GitHub Kotlin/kotlinx.serialization ondersteunt de bibliotheek Kotlin/JVM, Kotlin/Native, Kotlin/JS en Kotlin/Wasm.
Belangrijkste
kotlinx.serialization — is een ingebouwde serialisatiebibliotheek voor Kotlin, ontwikkeld door JetBrains als onderdeel van het officiële Kotlin-ecosysteem. Het belangrijkste verschil met externe oplossingen (Gson, Moshi, Jackson) is dat het geen reflectie gebruikt tijdens runtime. In plaats daarvan wordt de serialisatiecode gegenereerd tijdens compilatie met behulp van Kotlin Symbol Processing (KSP) of de Kotlin Compiler Plugin. Dit levert een prestatieverbetering van 3-5 keer in vergelijking met Gson en garandeert typeveiligheid.
De bibliotheek ondersteunt officieel vier formaten: JSON (via de module kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) en HOCON (kotlinx-serialization-hocon). Formaten worden als aparte afhankelijkheden toegevoegd in build.gradle.kts, waardoor onnodige bibliotheken niet in het project worden getrokken. Voor elk formaat is er een eigen set configuratieparameters.
Multiplatform — de belangrijkste functie van de bibliotheek. Dezelfde klasse met @Serializable werkt op alle doelplatforms: JVM (Android, Backend), Native (iOS), JS (Web, React) en Wasm (WebAssembly). De ontwikkelaar hoeft geen verschillende serialisatie-implementaties voor elk platform te schrijven — de code blijft uniform. Dit is vooral waardevol in Kotlin Multiplatform Mobile (KMM)-projecten, waar gedeelde code wordt gebruikt tussen Android en iOS.
Codegeneratie in kotlinx.serialization verloopt in drie fasen. In de eerste fase detecteert de Kotlin-compiler de @Serializable-annotatie op een klasse en geeft deze door aan de Kotlin Symbol Processing (KSP)-plugin. In de tweede fase genereert KSP een serialisator-object dat de KSerializer-interface implementeert. In de derde fase wordt de gegenereerde code samen met de broncode van het project gecompileerd. Als resultaat wordt geen van deze fasen uitgevoerd tijdens de uitvoering van de applicatie.
De gegenereerde serialisator werkt rechtstreeks met de velden van de klasse via hun getters en setters, zonder reflectie. Dit betekent dat velden met de modifier private ook worden geserialiseerd als ze zijn gemarkeerd met @Serializable. De prestaties van deze benadering liggen dicht bij handmatige serialisatie: voor eenvoudige klassen (5-10 velden) is de serialisatietijd 10-50 microseconden, voor complexe objectgrafen — tot 200 microseconden per 1000 objecten.
Om de bibliotheek in een Android- of Kotlin/JVM-project aan te sluiten, moet u de plugin en afhankelijkheden toevoegen in build.gradle.kts. De plugin org.jetbrains.kotlin.plugin.serialization in een versie die overeenkomt met de Kotlin-versie activeert de codegeneratie. De bibliotheek kotlinx-serialization-json wordt toegevoegd in de sectie dependencies met een versie die onafhankelijk is van de Kotlin-versie.
// build.gradle.kts — aansluiten van kotlinx.serialization
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// Hoofdmodule serialisatie
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// Aanvullende formaten
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSON — het populairste formaat in kotlinx.serialization. Om een object te serialiseren volstaat het om de @Serializable-annotatie op een data class te plaatsen en Json.encodeToString() aan te roepen. Voor deserialisatie — Json.decodeFromString() met type-opgave. De bibliotheek verwerkt automatisch null-velden, lijsten, geneste objecten en enums. Alle velden van de klasse zijn standaard verplicht, tenzij anders vermeld.
JSON-configuratie wordt uitgevoerd via de Json {} builder. In de constructor kunnen ignoreUnknownKeys = true worden doorgegeven voor het negeren van onbekende velden tijdens deserialisatie, prettyPrint = true voor geformatteerde uitvoer, coerceInputValues = true voor het converteren van onjuiste waarden naar standaardwaarden. Ook zijn de instellingen encodeDefaults (serialisatie van velden met standaardwaarden) en classDiscriminator (veldnaam voor polymorfe serialisatie) beschikbaar.
// Voorbeeld van JSON-serialisatie en deserialisatie
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")
)
// Serialisatie naar JSON met prettyPrint
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// Deserialisatie uit JSON
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
Het voorbeeld toont de basisfiets van serialisatie en deserialisatie. Data class Project met de @Serializable-annotatie krijgt automatisch encodeToString en decodeFromString. Het veld isActive heeft de standaardwaarde true — als dit veld ontbreekt in JSON, wordt de standaardwaarde gebruikt. Als er onbekende velden in JSON verschijnen zonder ignoreUnknownKeys = true, wordt een SerializationException gegenereerd.
Sealed class — een van de krachtigste use cases van kotlinx.serialization. De bibliotheek ondersteunt polymorfe serialisatie voor sealed class-hiërarchieën zonder extra configuratie: markeer de sealed class en al zijn subklassen met @Serializable. Tijdens serialisatie wordt het veld „type” (configureerbaar via classDiscriminator) toegevoegd, op basis waarvan tijdens deserialisatie het concrete type wordt bepaald.
// Polymorfe serialisatie van 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}")
}
}
Polymorfe serialisatie van sealed class is vooral nuttig in API-cliënten, waar de server verschillende antwoordtypen retourneert. Zonder kotlinx.serialization zou u een handmatige deserialisator moeten schrijven met when op basis van het discriminatorveld. Met de bibliotheek gebeurt dit met één annotatie. classDiscriminator maakt het mogelijk om de naam van het markeringsveld (standaard „type”) te wijzigen naar elke waarde die de server verwacht.
De bibliotheek biedt een set annotaties voor fijnafstemming van serialisatie. De belangrijkste is @Serializable voor de klasse. Aanvullende annotaties: @SerialName voor het specificeren van de veldnaam in JSON (als deze afwijkt van de Kotlin-naam), @Transient voor het uitsluiten van een veld van serialisatie, @Required voor een veld dat in JSON aanwezig moet zijn, @EncodeDefault voor geforceerde serialisatie van een veld met een standaardwaarde.
| Annotatie | Doel | Voorbeeld |
|---|---|---|
| @Serializable | Activeert serialisatiegeneratie voor de klasse | @Serializable data class User |
| @SerialName | Specificeert een alternatieve veldnaam in het formaat | @SerialName(„user_name”) val name: String |
| @Transient | Sluit veld uit van serialisatie | @Transient val cache: MutableMap |
| @Required | Veld is verplicht in JSON tijdens deserialisatie | @Required val id: String |
| @EncodeDefault | Serialiseert veld zelfs met standaardwaarde | @EncodeDefault val type: Type = Type.A |
| @Serializer | Koppelt een aangepaste serialisator aan de klasse | @Serializer(forClass = Date::class) |
De annotatie @SerialName is cruciaal bij het werken met API's waar veldnamen in snake_case zijn en de Kotlin-stijl camelCase is. De server stuurt bijvoorbeeld „user_id” en in de Kotlin-code wordt userId gebruikt. @SerialName(„user_id”) lost dit probleem op zonder extra mappers. @Transient is handig voor velden die niet naar de server hoeven te worden gestuurd — bijvoorbeeld tijdelijke berekende waarden of cache.
Standaard zijn alle velden in kotlinx.serialization verplicht. Als een veld in JSON kan ontbreken, moet u het nullable maken (String?) of een standaardwaarde instellen (val name: String = „”). Er zijn echter situaties waarin een veld niet nullable is in Kotlin, maar kan ontbreken in JSON vanwege API-versiebeheer. In dit geval gooit @Required een SerializationException bij afwezigheid van het veld, terwijl de standaardwaarde default zonder fout invult.
KSerializer — de interface die alle serialisatoren in kotlinx.serialization implementeren. Als de standaard codegeneratie niet geschikt is (bijvoorbeeld voor het werken met Date, Bitmap of een specifiek binair formaat), kunt u uw eigen serialisator schrijven. Hiervoor moet u de methoden serialize() en deserialize() implementeren en een descriptor — een structuurbeschrijving voor het formaatschema — leveren.
Aangepaste serialisatoren worden op twee manieren aangesloten: via de annotatie @Serializable(with = MySerializer::class) voor koppeling aan een specifieke klasse of globaal via Json { serializersModule = ... } voor koppeling aan alle instanties van het type. De tweede manier heeft de voorkeur voor ingebouwde typen (Date, UUID) om niet op elk veld een annotatie te hoeven schrijven.
// Aangepaste serialisator voor 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())
}
}
// Gebruik van aangepaste serialisator
@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))
}
In het voorbeeld converteert DateSerializer java.util.Date naar een ISO 8601-tekenreeks. Zonder een aangepaste serialisator kan kotlinx.serialization niet met Date werken — dit is een type dat geen deel uitmaakt van de standaard Kotlin-bibliotheek. @Serializable(with = DateSerializer::class) op een specifiek veld koppelt de serialisator alleen voor dat veld. Voor globale registratie van alle Date gebruikt u Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.
kotlinx.serialization beperkt zich niet tot JSON. De bibliotheek ondersteunt vier ingebouwde formaten, elk met zijn eigen module en configuratie. JSON (kotlinx-serialization-json) — universeel, menselijk leesbaar, geschikt voor REST API. ProtoBuf (kotlinx-serialization-protobuf) — binair, compact, met verplicht schema, voor zwaarbelaste microservices. CBOR (kotlinx-serialization-cbor) — de binaire tegenhanger van JSON, handig voor IoT en mobiele apparaten met beperkte bandbreedte. HOCON (kotlinx-serialization-hocon) — configuratieformaat, compatibel met TypeSafe Config.
| Formaat | Module | Type | Schema | Typisch gebruik |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Tekstueel | Optioneel | REST API, gegevensopslag |
| ProtoBuf | kotlinx-serialization-protobuf | Binair | Verplicht (.proto) | Microservices, gRPC |
| CBOR | kotlinx-serialization-cbor | Binair | Optioneel | IoT, mobiele apparaten |
| HOCON | kotlinx-serialization-hocon | Tekstueel | Optioneel | Configuratiebestanden |
ProtoBuf vereist de definitie van een schema in .proto-bestanden, maar kotlinx-serialization-protobuf genereert Kotlin-klassen rechtstreeks uit @Serializable zonder .proto. Dit vereenvoudigt de ontwikkeling: annoteer een data class en gebruik ProtoBuf.encodeToByteArray(). CBOR is vooral relevant voor het Android-framework wanneer u compacte binaire gegevens via NFC of BLE moet verzenden. De grootte van een CBOR-bericht is gemiddeld 20-30% kleiner dan JSON voor dezelfde gegevensset.
Voor REST API in een mobiele app is JSON optimaal — het wordt gedebugd zonder extra tools, is leesbaar in logs en compatibel met elke backend. Als de app grote hoeveelheden gegevens tussen microservices verzendt (honderden megabytes) — biedt ProtoBuf een snelheidswinst van tot 5 keer dankzij binaire codering. Voor het opslaan van instellingen in bestanden gebruikt u HOCON of JSON. Voor apparaten met strikte bandbreedtebeperkingen (IoT-sensoren) — CBOR.
Eerste fout — het negeren van onbekende sleutels tijdens deserialisatie. Als de server een nieuw veld heeft toegevoegd en u ignoreUnknownKeys = false heeft, crasht de app met een SerializationException. Standaard is deze vlag uitgeschakeld. Oplossing: stel altijd Json { ignoreUnknownKeys = true } in voor productiecode om bestand te zijn tegen API-wijzigingen.
Tweede fout — serialisatie van internal of private velden in een data class. In een Kotlin data class worden alle velden in de primary constructor standaard geserialiseerd. Als een veld gevoelige gegevens bevat (wachtwoord, token), moet u het markeren met @Transient of uit de primary constructor halen. @Transient sluit het veld volledig uit van JSON, maar in de constructor kan het een fout veroorzaken — het is beter om zo'n veld in de klassebody met @Transient te definiëren.
Derde fout — polymorfe serialisatie zonder sealed class. Als u open class gebruikt in plaats van sealed, vereist kotlinx.serialization expliciete registratie van alle subklassen in serializersModule. In tegenstelling tot sealed class, waar de compiler alle subklassen kent, staat open class willekeurige uitbreiding toe — de bibliotheek kan niet automatisch alle subtypen bepalen. Registratie gebeurt via Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.
De versie van kotlinx.serialization moet compatibel zijn met de Kotlin-versie. JetBrains publiceert een compatibiliteitstabel: kotlinx-serialization 1.6.x is compatibel met Kotlin 1.9.x, 1.7.x — met Kotlin 2.0.x en 2.1.x. Versie-incompatibiliteit veroorzaakt cryptische compilatiefouten zoals „Symbol ‘serializer’ is missing”. Controleer altijd de huidige versie op mavenCentral of in de GitHub-repository van het project.
Veelgestelde vragen
kotlinx.serialization gebruikt compile-time codegeneratie via KSP, terwijl Gson en Moshi runtime-reflectie gebruiken. Dit biedt een prestatievoordeel (3-5 keer sneller dan Gson) en typeveiligheid. Gson serialiseert elk veld zonder annotatie, wat kan leiden tot gegevenslekken. kotlinx.serialization vereist expliciete @Serializable-annotatie, wat veiliger is. Moshi ondersteunt ook codegen, maar alleen voor JVM en Android.
Ja, kotlinx.serialization is de officiële multiplatformbibliotheek van JetBrains. Het werkt op Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) en Kotlin/Wasm. De API is uniform voor alle platforms: @Serializable + Json.encodeToString() werkt overal hetzelfde. Voor iOS zijn geen extra instellingen nodig — Kotlin/Native compileert de geserialiseerde code naar een native binair bestand.
Nullable-velden (String?) worden gedeserialiseerd als null als de waarde in JSON ontbreekt of als null is opgegeven. Voor non-nullable velden (String) zonder standaardwaarde veroorzaakt het ontbreken van het veld in JSON een SerializationException. Als u wilt dat null-waarden niet in JSON terechtkomen, configureer dan Json { encodeDefaults = false }. Dit sluit alle velden die gelijk zijn aan default (inclusief null voor nullable) uit van de uitvoer.
Gebruik @SerialName(„snake_case_name”) op elk veld waarvan de naam afwijkt van het Kotlin-formaat. Alternatief is voor Kotlin 2.0+ Json { namingStrategy = JsonNamingStrategy.SnakeCase } beschikbaar — automatische conversie camelCase ↔ snake_case. Deze instelling wordt op alle velden tegelijk toegepast. Als gedeeltelijke aanpassing nodig is, combineer dan @SerialName met de globale strategie.
Nee, Flow en coroutines zijn niet direct serialiseerbaar — ze vertegenwoordigen asynchrone uitvoering, geen gegevens. Om gegevens uit Flow door te geven, moet u ze verzamelen in een collectie via .toList() in een coroutine en de collectie serialiseren. Evenzo kunnen Job, Deferred of Continuation niet worden geserialiseerd. Serialiseer alleen data class — gegevensmodellen zonder gedragslogica.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook