kotlinx.serialization — egy többplatformos könyvtár a JetBrainstól Kotlin-objektumok JSON, ProtoBuf, CBOR és más formátumokba való átalakításához reflexió nélkül. A Gsonnal és Moshival ellentétben a szerializátor kódot a fordítás során hozza létre a @Serializable annotáción keresztül, ami magas teljesítményt és típusbiztonságot nyújt. A GitHub Kotlin/kotlinx.serialization szerint a könyvtár támogatja a Kotlin/JVM, Kotlin/Native, Kotlin/JS és Kotlin/Wasm platformokat.
Főbb pontok
kotlinx.serialization — egy beépített szerializációs könyvtár Kotlinhoz, amelyet a JetBrains fejlesztett a hivatalos Kotlin-ökoszisztéma részeként. Fő különbsége a harmadik féltől származó megoldásoktól (Gson, Moshi, Jackson) az, hogy nem használ reflexiót futásidőben. Ehelyett a szerializátor kódja a fordítás során jön létre a Kotlin Symbol Processing (KSP) vagy a Kotlin Compiler Plugin segítségével. Ez akár 3-5-szörös teljesítménynövekedést biztosít a Gsonhoz képest, és garantálja a típusbiztonságot.
A könyvtár hivatalosan négy formátumot támogat: JSON (a kotlinx-serialization-json modulon keresztül), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) és HOCON (kotlinx-serialization-hocon). A formátumok külön függőségekként kerülnek hozzáadásra a build.gradle.kts fájlban, ami lehetővé teszi, hogy ne húzzon be felesleges könyvtárakat a projektbe. Minden formátumhoz saját konfigurációs paraméterkészlet tartozik.
Többplatformosság — a könyvtár kulcsfontosságú jellemzője. Ugyanaz az osztály @Serializable annotációval minden célplatformon működik: JVM (Android, Backend), Native (iOS), JS (Web, React) és Wasm (WebAssembly). A fejlesztőnek nem kell különböző szerializációs implementációkat írnia minden platformhoz — a kód egységes marad. Ez különösen értékes a Kotlin Multiplatform Mobile (KMM) projektekben, ahol a megosztott kód Android és iOS között oszlik meg.
Kódgenerálás a kotlinx.serializationban három szakaszban történik. Az első szakaszban a Kotlin-fordító észleli a @Serializable annotációt egy osztályon, és továbbítja a Kotlin Symbol Processing (KSP) bővítménynek. A második szakaszban a KSP létrehoz egy szerializátor objektumot, amely megvalósítja a KSerializer interfészt. A harmadik szakaszban a létrehozott kód a projekt forráskódjával együtt fordul le. Ennek eredményeként ezen szakaszok egyike sem fut le az alkalmazás futása során.
A létrehozott szerializátor közvetlenül az osztály mezőivel dolgozik azok getterén és setterén keresztül, reflexió nélkül. Ez azt jelenti, hogy a private módosítóval rendelkező mezők is szerializálódnak, ha @Serializable-val vannak jelölve. Ennek a megközelítésnek a teljesítménye közel áll a kézi szerializációhoz: egyszerű osztályok (5-10 mező) esetén a szerializációs idő 10-50 mikroszekundum, összetett objektumgráfok esetén — akár 200 mikroszekundum 1000 objektumonként.
A könyvtár csatlakoztatásához egy Android vagy Kotlin/JVM projektben hozzá kell adni a bővítményt és a függőségeket a build.gradle.kts fájlban. A Kotlin verziójának megfelelő org.jetbrains.kotlin.plugin.serialization bővítmény aktiválja a kódgenerálást. A kotlinx-serialization-json könyvtár a dependencies szakaszban kerül hozzáadásra egy Kotlin verziójától független verzióval.
// build.gradle.kts — a kotlinx.serialization csatlakoztatása
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// Fő szerializációs modul
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// További formátumok
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSON — a legnépszerűbb formátum a kotlinx.serializationban. Egy objektum szerializálásához elegendő a @Serializable annotációt elhelyezni egy data class-on, és meghívni a Json.encodeToString() metódust. A deszerializációhoz — Json.decodeFromString() a típus megadásával. A könyvtár automatikusan kezeli a null mezőket, listákat, beágyazott objektumokat és enumokat. Az osztály összes mezője alapértelmezés szerint kötelező, hacsak másként nincs megadva.
A JSON-konfiguráció a Json {} builder segítségével történik. A konstruktorban átadható az ignoreUnknownKeys = true az ismeretlen mezők figyelmen kívül hagyásához deszerializációkor, a prettyPrint = true a formázott kimenethez, a coerceInputValues = true a helytelen értékek alapértelmezett értékekre való konvertálásához. Szintén elérhetők az encodeDefaults (alapértelmezett értékű mezők szerializálása) és classDiscriminator (a mező neve polimorf szerializációhoz) beállítások.
// Példa JSON szerializációra és deszerializációra
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")
)
// Szerializáció JSON-ba prettyPrint segítségével
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// Deszerializáció JSON-ból
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
A példa a szerializáció és deszerializáció alapvető ciklusát mutatja be. A @Serializable annotációval ellátott Project data class automatikusan megkapja az encodeToString és decodeFromString metódusokat. Az isActive mező alapértelmezett értéke true — ha ez a mező hiányzik a JSON-ból, az alapértelmezett érték kerül használatra. Ha ismeretlen mezők érkeznek a JSON-ban ignoreUnknownKeys = true nélkül, SerializationException kivétel dobódik.
Sealed class — a kotlinx.serialization egyik legerősebb használati esete. A könyvtár támogatja a polimorf szerializációt sealed class hierarchiákhoz külön konfiguráció nélkül: elegendő a sealed class-t és annak összes leszármazottját @Serializable-val jelölni. A szerializáció során a „type” mező (classDiscriminator segítségével konfigurálható) hozzáadásra kerül, amely alapján a deszerializáció során a konkrét típus meghatározásra kerül.
// Sealed class polimorf szerializációja
@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}")
}
}
A sealed class polimorf szerializációja különösen hasznos az API kliensekben, ahol a szerver különböző típusú válaszokat ad vissza. Kotlinx.serialization nélkül kézi deszerializátort kellene írni when segítségével a diszkriminátor mező alapján. A könyvtárral ez egyetlen annotációval történik. A classDiscriminator lehetővé teszi a marker mező nevének (alapértelmezés szerint „type”) megváltoztatását bármilyen, a szerver által várt értékre.
A könyvtár egy sor annotációt kínál a szerializáció finomhangolásához. A fő annotáció a @Serializable az osztály számára. További annotációk: @SerialName a mező JSON-beli nevének megadásához (ha eltér a Kotlin-névtől), @Transient a mező szerializációból való kizárásához, @Required a JSON-ban kötelezően jelenlévő mezőhöz, @EncodeDefault az alapértelmezett értékű mező kényszerített szerializálásához.
| Annotáció | Cél | Példa |
|---|---|---|
| @Serializable | Engedélyezi a szerializátor generálását az osztály számára | @Serializable data class User |
| @SerialName | Megadja a mező alternatív nevét a formátumban | @SerialName(„user_name”) val name: String |
| @Transient | Kizárja a mezőt a szerializációból | @Transient val cache: MutableMap |
| @Required | A mező kötelező a JSON-ban deszerializációkor | @Required val id: String |
| @EncodeDefault | Szerializálja a mezőt még alapértelmezett értékkel is | @EncodeDefault val type: Type = Type.A |
| @Serializer | Egyedi szerializátort kapcsol az osztályhoz | @Serializer(forClass = Date::class) |
A @SerialName annotáció kritikus fontosságú olyan API-kkal való munka során, ahol a mezőnevek snake_case, a Kotlin stílus pedig camelCase. Például a szerver „user_id”-t küld, a Kotlin-kódban pedig userId szerepel. A @SerialName(„user_id”) megoldja ezt a problémát további mapperek nélkül. A @Transient olyan mezőkhöz hasznos, amelyeket nem kell elküldeni a szerverre — például ideiglenes számított értékek vagy gyorsítótár.
Alapértelmezés szerint a kotlinx.serializationban minden mező kötelező. Ha egy mező hiányozhat a JSON-ból, nullázhatóvá kell tenni (String?) vagy alapértelmezett értéket kell beállítani (val name: String = „”). Vannak azonban olyan helyzetek, amikor a mező nem nullázható Kotlinban, de hiányozhat a JSON-ból az API verziókezelése miatt. Ebben az esetben a @Required SerializationException-t dob a mező hiányakor, míg az alapértelmezett érték hiba nélkül tölti ki a default-ot.
KSerializer — interfész, amelyet a kotlinx.serialization összes szerializátora megvalósít. Ha a szabványos kódgenerálás nem megfelelő (például Date, Bitmap vagy egy adott bináris formátum kezeléséhez), megírhatja saját szerializátorát. Ehhez meg kell valósítania a serialize() és deserialize() metódusokat, valamint meg kell adnia egy deskriptort — a struktúra leírását a formátum sémájához.
Az egyedi szerializátorok kétféleképpen csatlakoztathatók: a @Serializable(with = MySerializer::class) annotáción keresztül egy adott osztályhoz kapcsolva, vagy globálisan a Json { serializersModule = ... } segítségével a típus összes példányához kapcsolva. A második mód előnyösebb a beépített típusok (Date, UUID) esetében, hogy ne kelljen minden mezőn annotációt írni.
// Egyedi szerializátor java.util.Date-hez
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())
}
}
// Egyedi szerializátor használata
@Serializable
data class Event(
val title: String,
@Serializable(with = DateSerializer::class)
val date: Date
)
fun main() {
val json = Json { prettyPrint = true }
val event = Event("Kiadás", Date())
println(json.encodeToString(event))
}
A példában a DateSerializer a java.util.Date-t ISO 8601 stringgé alakítja. Egyedi szerializátor nélkül a kotlinx.serialization nem tud a Date-tel dolgozni — ez egy olyan típus, amely nem része a szabványos Kotlin könyvtárnak. A @Serializable(with = DateSerializer::class) egy adott mezőn csak arra a mezőre kapcsolja a szerializátort. Az összes Date globális regisztrációjához használja a Json { serializersModule = SerializersModule { contextual(DateSerializer) } } kifejezést.
kotlinx.serialization nem korlátozódik a JSON-ra. A könyvtár négy beépített formátumot támogat, mindegyik saját modullal és konfigurációval. JSON (kotlinx-serialization-json) — univerzális, ember által olvasható, REST API-hoz alkalmas. ProtoBuf (kotlinx-serialization-protobuf) — bináris, kompakt, kötelező sémával, nagy terhelésű mikroszolgáltatásokhoz. CBOR (kotlinx-serialization-cbor) — a JSON bináris megfelelője, korlátozott forgalmú IoT és mobileszközökhöz. HOCON (kotlinx-serialization-hocon) — konfigurációs formátum, kompatibilis a TypeSafe Config-gal.
| Formátum | Modul | Típus | Schema | Tipikus használat |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Szöveges | Opcionális | REST API, adattárolás |
| ProtoBuf | kotlinx-serialization-protobuf | Bináris | Kötelező (.proto) | Mikroszolgáltatások, gRPC |
| CBOR | kotlinx-serialization-cbor | Bináris | Opcionális | IoT, mobileszközök |
| HOCON | kotlinx-serialization-hocon | Szöveges | Opcionális | Konfigurációs fájlok |
ProtoBuf a séma .proto fájlokban történő meghatározását igényli, de a kotlinx-serialization-protobuf közvetlenül a @Serializable-ból generál Kotlin osztályokat .proto nélkül. Ez leegyszerűsíti a fejlesztést: elegendő egy data class-t annotálni és használni a ProtoBuf.encodeToByteArray() metódust. A CBOR különösen releváns az Android keretrendszer számára, amikor kompakt bináris adatokat kell továbbítani NFC-n vagy BLE-n keresztül. A CBOR üzenet mérete átlagosan 20-30%-kal kisebb, mint a JSON azonos adatkészlet esetén.
REST API-hoz mobilalkalmazásban a JSON az optimális — további eszközök nélkül debugolható, olvasható a naplókban és kompatibilis bármilyen backenddel. Ha az alkalmazás nagy mennyiségű adatot továbbít mikroszolgáltatások között (több száz megabájt) — a ProtoBuf akár 5-szörös sebességnövekedést biztosít a bináris kódolásnak köszönhetően. Beállítások fájlokban történő tárolásához használja a HOCON-t vagy JSON-t. Szigorú forgalmi korlátozásokkal rendelkező eszközökhöz (IoT érzékelők) — CBOR.
Első hiba — ismeretlen kulcsok figyelmen kívül hagyása deszerializációkor. Ha a szerver új mezőt adott hozzá, és az ignoreUnknownKeys = false, az alkalmazás SerializationException-nel összeomlik. Alapértelmezés szerint ez a jelző ki van kapcsolva. Megoldás: mindig állítsa be a Json { ignoreUnknownKeys = true } értéket éles kódban, hogy ellenálló legyen az API-változásokkal szemben.
Második hiba — internal vagy private mezők szerializálása data class-ban. Kotlin data class-ban az elsődleges konstruktor összes mezője alapértelmezés szerint szerializálódik. Ha egy mező érzékeny adatokat tartalmaz (jelszó, token), @Transient jelöléssel kell ellátni vagy ki kell venni az elsődleges konstruktorból. A @Transient teljesen kizárja a mezőt a JSON-ból, de a konstruktorban hibát okozhat — jobb az ilyen mezőt az osztály törzsében @Transient jelöléssel definiálni.
Harmadik hiba — polimorf szerializáció sealed class nélkül. Ha open class-t használ sealed helyett, a kotlinx.serialization az összes leszármazott explicit regisztrációját igényli a serializersModule-ban. Ellentétben a sealed class-sal, ahol a fordító ismeri az összes leszármazottat, az open class tetszőleges bővítést tesz lehetővé — a könyvtár nem tudja automatikusan meghatározni az összes altípust. A regisztráció a Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } } segítségével történik.
A kotlinx.serialization verziójának kompatibilisnek kell lennie a Kotlin verziójával. A JetBrains kompatibilitási táblázatot tesz közzé: a kotlinx-serialization 1.6.x kompatibilis a Kotlin 1.9.x-szel, 1.7.x — a Kotlin 2.0.x-szel és 2.1.x-szel. A verziók eltérése rejtélyes fordítási hibákat okoz, mint a „Symbol ‘serializer’ is missing”. Mindig ellenőrizze az aktuális verziót a mavenCentral-on vagy a projekt GitHub repozitóriumában.
Gyakran ismételt kérdések
kotlinx.serialization fordítási idejű kódgenerálást használ a KSP-n keresztül, míg a Gson és Moshi futásidejű reflexiót használ. Ez előnyt jelent a teljesítményben (3-5-ször gyorsabb, mint a Gson) és a típusbiztonságban. A Gson annotáció nélkül szerializál bármilyen mezőt, ami adatszivárgáshoz vezethet. A kotlinx.serialization explicit @Serializable annotációt igényel, ami biztonságosabb. A Moshi szintén támogatja a codegen-t, de csak JVM és Android esetén.
Igen, a kotlinx.serialization a JetBrains hivatalos többplatformos könyvtára. Működik Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) és Kotlin/Wasm platformokon. Az API egységes minden platformon: a @Serializable + Json.encodeToString() mindenhol ugyanúgy működik. Az iOS-hez nincs szükség további beállításokra — a Kotlin/Native natív binárissá fordítja a szerializált kódot.
A nullázható mezők (String?) nullként deszerializálódnak, ha az érték hiányzik a JSON-ból vagy nullként van megadva. A nem nullázható mezők (String) alapértelmezett érték nélküli hiánya a JSON-ban SerializationException-t okoz. Ha azt szeretné, hogy a null értékek ne kerüljenek a JSON-ba, állítsa be a Json { encodeDefaults = false } értéket. Ez kizárja a kimenetből az összes default értékkel egyenlő mezőt (beleértve a null-t a nullázhatóknál).
Használja a @SerialName(„snake_case_name”) annotációt minden olyan mezőn, amelynek neve eltér a Kotlin formátumtól. Alternatívaként Kotlin 2.0+ esetén elérhető a Json { namingStrategy = JsonNamingStrategy.SnakeCase } — automatikus camelCase ↔ snake_case konverzió. Ez a beállítás az összes mezőre egyszerre vonatkozik. Ha részleges testreszabásra van szükség, kombinálja a @SerialName-t a globális stratégiával.
Nem, a Flow és a korutinok nem szerializálhatók közvetlenül — aszinkron végrehajtást képviselnek, nem adatokat. A Flow-ból történő adatátvitelhez össze kell gyűjteni azokat egy gyűjteménybe a .toList() segítségével egy korutinban, és szerializálni a gyűjteményt. Hasonlóképpen, a Job, Deferred vagy Continuation sem szerializálható. Csak data class-okat — viselkedési logika nélküli adatmodelleket — szerializáljon.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is