kotlinx.serialization: ce este, adnotări și serializare în JSON

Autor: IT Sectr Publicat: 2026-03-15 Timp de citire: 12 min

kotlinx.serialization — bibliotecă multi-platformă de la JetBrains pentru conversia obiectelor Kotlin în JSON, ProtoBuf, CBOR și alte formate fără utilizarea reflecției. Spre deosebire de Gson și Moshi, generează codul serializatorului în etapa de compilare prin adnotarea @Serializable, ceea ce oferă performanță ridicată și siguranță a tipurilor. Conform GitHub Kotlin/kotlinx.serialization, biblioteca suportă Kotlin/JVM, Kotlin/Native, Kotlin/JS și Kotlin/Wasm.

Principalele

  • kotlinx.serialization — serializare la compilare: codul se generează în etapa de compilare, reflecția nu este utilizată
  • @Serializable — adnotarea principală care pornește generarea serializatorului pentru clasă
  • Json {} builder — configurarea JSON prin Json { ignoreUnknownKeys = true; prettyPrint = true }
  • Multi-platformitate — biblioteca funcționează pe JVM, Native, JS și Wasm fără modificarea API
  • Serializatoare personalizate — prin interfața KSerializer pentru formate de date nestandard

Ce este kotlinx.serialization

kotlinx.serialization — este o bibliotecă de serializare integrată pentru Kotlin, dezvoltată de JetBrains ca parte a ecosistemului oficial Kotlin. Principala sa diferență față de soluțiile terțe (Gson, Moshi, Jackson) este că nu utilizează reflecția în timpul execuției. În schimb, codul serializatorului este generat în etapa de compilare cu ajutorul Kotlin Symbol Processing (KSP) sau al pluginului de compilator Kotlin. Aceasta oferă o creștere a performanței de până la 3-5 ori în comparație cu Gson și garantează siguranța tipurilor.

Biblioteca suportĄ oficial patru formate: JSON (prin modulul kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) și HOCON (kotlinx-serialization-hocon). Formatele se conectează ca dependențe separate în build.gradle.kts, ceea ce permite să nu se aducă biblioteci inutile în proiect. Pentru fiecare format există propriul set de parametri de configurare.

Multi-platformitatea — caracteristica cheie a bibliotecii. Aceeași clasă cu @Serializable funcționează pe toate platformele țintă: JVM (Android, Backend), Native (iOS), JS (Web, React) și Wasm (WebAssembly). Dezvoltatorul nu trebuie să scrie implementări diferite de serializare pentru fiecare platformă — codul rămâne unitar. Acest lucru este deosebit de valoros în proiectele Kotlin Multiplatform Mobile (KMM), unde codul comun este partajat între Android și iOS.

Cum funcționează generarea codului la compilare

Generarea codului în kotlinx.serialization are loc în trei etape. În prima etapă, compilatorul Kotlin detectează adnotarea @Serializable pe clasă și o transmite pluginului Kotlin Symbol Processing (KSP). În a doua etapă, KSP generează un obiect serializator care implementează interfața KSerializer. În a treia etapă, codul generat este compilat împreună cu codul sursă al proiectului. Ca rezultat, niciuna dintre aceste etape nu se execută în timpul funcționării aplicației.

Serializatorul generat lucrează direct cu câmpurile clasei prin gettere și settere, fără reflecție. Aceasta înseamnă că câmpurile cu modificatorul private sunt de asemenea serializate, dacă sunt marcate cu @Serializable. Performanța acestei abordări este apropiată de serializarea manuală: pentru clase simple (5-10 câmpuri), timpul de serializare este de 10-50 microsecunde, pentru grafuri complexe de obiecte — până la 200 de microsecunde pentru 1000 de obiecte.

Pentru conectarea bibliotecii într-un proiect Android sau Kotlin/JVM, trebuie să adăugați pluginul și dependențele în build.gradle.kts. Pluginul org.jetbrains.kotlin.plugin.serialization într-o versiune care corespunde versiunii Kotlin activează generarea codului. Biblioteca kotlinx-serialization-json se adaugă în secțiunea dependencies cu o versiune independentă de versiunea Kotlin.

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

dependencies {
    // Modulul principal de serializare
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

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

Utilizarea de bază: serializarea în JSON

JSON — cel mai popular format în kotlinx.serialization. Pentru serializarea unui obiect, este suficient să plasați adnotarea @Serializable pe data class și să apelați Json.encodeToString(). Pentru deserializare — Json.decodeFromString() cu specificarea tipului. Biblioteca gestionează automat câmpurile null, listele, obiectele imbricate și enum-urile. Toate câmpurile clasei sunt în mod implicit obligatorii, dacă nu se specifică altfel.

Configurarea JSON se realizează prin Json {} builder. În constructor pot fi transmise ignoreUnknownKeys = true pentru ignorarea câmpurilor necunoscute la deserializare, prettyPrint = true pentru ieșire formatată, coerceInputValues = true pentru conversia valorilor incorecte în valori implicite. De asemenea, sunt disponibile setările encodeDefaults (serializarea câmpurilor cu valori implicite) și classDiscriminator (numele câmpului pentru serializarea polimorfă).

kotlin
// Exemplu de serializare și deserializare JSON
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")
    )

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

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

Exemplul demonstrează ciclul de bază de serializare și deserializare. Data class Project cu adnotarea @Serializable primește automat encodeToString și decodeFromString. Câmpul isActive are valoarea implicită true — dacă acest câmp lipsește în JSON, se utilizează valoarea implicită. Dacă în JSON apar câmpuri necunoscute fără ignoreUnknownKeys = true, se va arunca excepția SerializationException.

Serializarea polimorfă sealed class

Sealed class — unul dintre cele mai puternice cazuri de utilizare ale kotlinx.serialization. Biblioteca suportă serializarea polimorfă pentru ierarhiile sealed class fără configurare suplimentară: este suficient să marcați sealed class și toți moștenitorii săi cu @Serializable. La serializare se adaugă câmpul „type” (configurabil prin classDiscriminator), pe baza căruia la deserializare se determină tipul concret.

kotlin
// Serializarea polimorfă 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}")
    }
}

Serializarea polimorfă sealed class este deosebit de utilă în clienții API, unde serverul returnează diferite tipuri de răspunsuri. Fără kotlinx.serialization ar trebui să scrieți un deserializator manual cu when după câmpul discriminator. Cu biblioteca, acest lucru se face cu o singură adnotare. classDiscriminator permite redenumirea câmpului marcator (implicit „type”) în orice valoare așteptată de server.

Adnotări kotlinx.serialization: prezentare completă

Biblioteca oferă un set de adnotări pentru configurarea fină a serializării. Principala este @Serializable pentru clasă. Adnotări suplimentare: @SerialName pentru specificarea numelui câmpului în JSON (dacă diferă de numele Kotlin), @Transient pentru excluderea câmpului din serializare, @Required pentru câmpul care trebuie să fie prezent în JSON, @EncodeDefault pentru serializarea forțată a unui câmp cu valoare implicită.

AdnotareDestinațieExemplu
@SerializableActivează generarea serializatorului pentru clasă@Serializable data class User
@SerialNameSpecifică un nume alternativ al câmpului în format@SerialName(„user_name”) val name: String
@TransientExclude câmpul din serializare@Transient val cache: MutableMap
@RequiredCâmpul este obligatoriu în JSON la deserializare@Required val id: String
@EncodeDefaultSerializează câmpul chiar și cu valoare implicită@EncodeDefault val type: Type = Type.A
@SerializerConectează un serializator personalizat la clasă@Serializer(forClass = Date::class)

Adnotarea @SerialName este critică atunci când lucrați cu API-uri unde numele câmpurilor sunt în snake_case, iar stilul Kotlin este camelCase. De exemplu, serverul trimite „user_id”, iar în codul Kotlin se utilizează userId. @SerialName(„user_id”) rezolvă această problemă fără mapatoare suplimentare. @Transient este utilă pentru câmpurile care nu trebuie trimise pe server — de exemplu, valori de calcul temporare sau cache.

@Required ca alternativă pentru câmpurile nullable

În mod implicit, toate câmpurile în kotlinx.serialization sunt obligatorii. Dacă un câmp poate lipsi în JSON, trebuie să-l faceți nullable (String?) sau să setați o valoare implicită (val name: String = „”). Există însă situații când câmpul nu este nullable în Kotlin, dar poate lipsi în JSON din cauza versionării API. În acest caz, @Required aruncă SerializationException la lipsa câmpului, iar valoarea implicită completează default fără eroare.

Serializatoare personalizate: KSerializer și gestionare manuală

KSerializer — interfața pe care o implementează toate serializatoarele în kotlinx.serialization. Dacă generarea standard de cod nu este potrivită (de exemplu, pentru lucrul cu Date, Bitmap sau un format binar specific), puteți scrie propriul serializator. Pentru aceasta, trebuie să implementați metodele serialize() și deserialize() și să furnizați descriptorul — descrierea structurii pentru schema formatului.

Serializatoarele personalizate se conectează în două moduri: prin adnotarea @Serializable(with = MySerializer::class) pentru conectarea la o clasă specifică sau global prin Json { serializersModule = ... } pentru conectarea la toate instanțele tipului. Al doilea mod este preferabil pentru tipurile integrate (Date, UUID) pentru a nu scrie adnotarea pe fiecare câmp.

kotlin
// Serializator personalizat pentru 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())
    }
}

// Utilizarea serializatorului personalizat
@Serializable
data class Event(
    val title: String,
    @Serializable(with = DateSerializer::class)
    val date: Date
)

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

În exemplu, DateSerializer convertește java.util.Date în șir ISO 8601. Fără un serializator personalizat, kotlinx.serialization nu poate lucra cu Date — acesta este un tip care nu face parte din biblioteca standard Kotlin. @Serializable(with = DateSerializer::class) pe un câmp specific conectează serializatorul doar pentru acest câmp. Pentru înregistrarea globală a tuturor Date, utilizați Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.

Formate de serializare: JSON, ProtoBuf, CBOR, HOCON

kotlinx.serialization nu se limitează doar la JSON. Biblioteca suportă patru formate integrate, fiecare cu propriul modul și configurare. JSON (kotlinx-serialization-json) — universal, lizibil pentru om, potrivit pentru REST API. ProtoBuf (kotlinx-serialization-protobuf) — binar, compact, cu schemă obligatorie, pentru microservicii încărcate. CBOR (kotlinx-serialization-cbor) — echivalentul binar al JSON, convenabil pentru IoT și dispozitive mobile cu trafic limitat. HOCON (kotlinx-serialization-hocon) — format de configurare, compatibil cu TypeSafe Config.

FormatModulTipSchematUtilizare tipică
JSONkotlinx-serialization-jsonTextOpționalREST API, stocare date
ProtoBufkotlinx-serialization-protobufBinarObligatorie (.proto)Microservicii, gRPC
CBORkotlinx-serialization-cborBinarOpționalIoT, dispozitive mobile
HOCONkotlinx-serialization-hoconTextOpționalFișiere de configurare

ProtoBuf necesită definirea schemei în fișiere .proto, dar kotlinx-serialization-protobuf generează clase Kotlin direct din @Serializable fără .proto. Acest lucru simplifică dezvoltarea: este suficient să adnotați data class și să utilizați ProtoBuf.encodeToByteArray(). CBOR este deosebit de relevant pentru framework-ul Android când trebuie să transmiteți date binare compacte prin NFC sau BLE. Dimensiunea mesajului CBOR este în medie cu 20-30% mai mică decât JSON pentru același set de date.

Alegerea formatului pentru proiect

Pentru REST API într-o aplicație mobilă, JSON este optim — se depanează fără instrumente suplimentare, este lizibil în jurnale și compatibil cu orice backend. Dacă aplicația transmite volume mari de date între microservicii (sute de megaocteți) — ProtoBuf va oferi o creștere a vitezei de până la 5 ori datorită codificării binare. Pentru stocarea setărilor în fișiere, utilizați HOCON sau JSON. Pentru dispozitive cu limitări stricte de trafic (senzori IoT) — CBOR.

Erori tipice la lucrul cu kotlinx.serialization

Prima eroare — ignorarea cheilor necunoscute la deserializare. Dacă serverul a adăugat un câmp nou și aveți ignoreUnknownKeys = false, aplicația va cădea cu SerializationException. În mod implicit, acest steag este dezactivat. Soluția: setați întotdeauna Json { ignoreUnknownKeys = true } pentru codul de producție pentru a fi rezistent la schimbările API.

A doua eroare — serializarea câmpurilor internal sau private în data class. În Kotlin data class, toate câmpurile din constructorul principal sunt serializate implicit. Dacă un câmp conține date sensibile (parolă, token), trebuie să-l marcați cu @Transient sau să-l scoateți din constructorul principal. @Transient exclude complet câmpul din JSON, dar în constructor poate provoca o eroare — este mai bine să definiți un astfel de câmp în corpul clasei cu @Transient.

A treia eroare — serializarea polimorfă fără sealed class. Dacă utilizați open class în loc de sealed, kotlinx.serialization necesită înregistrarea explicită a tuturor moștenitorilor în serializersModule. Spre deosebire de sealed class, unde compilatorul cunoaște toți moștenitorii, open class permite extinderea arbitrară — biblioteca nu poate determina automat toate subtipurile. Înregistrarea se face prin Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.

Eroare de versionare a bibliotecii

Versiunea kotlinx.serialization trebuie să fie compatibilă cu versiunea Kotlin. JetBrains publică un tabel de compatibilitate: kotlinx-serialization 1.6.x este compatibilă cu Kotlin 1.9.x, 1.7.x — cu Kotlin 2.0.x și 2.1.x. Neconcordanța versiunilor provoacă erori de compilare criptice precum „Symbol ‘serializer’ is missing”. Verificați întotdeauna versiunea curentă pe mavenCentral sau în repository-ul GitHub al proiectului.

Întrebări frecvente

Cu ce se deosebește kotlinx.serialization de Gson și Moshi?

kotlinx.serialization utilizează generarea codului la compilare prin KSP, iar Gson și Moshi utilizează reflecția în timpul execuției. Aceasta oferă un avantaj în performanță (de 3-5 ori mai rapid decât Gson) și siguranța tipurilor. Gson serializează orice câmp fără adnotare, ceea ce poate duce la scurgeri de date. kotlinx.serialization necesită adnotarea explicită @Serializable, ceea ce este mai sigur. Moshi suportă de asemenea codegen, dar doar pentru JVM și Android.

Suportă kotlinx.serialization Kotlin Multiplatform?

Da, kotlinx.serialization este biblioteca oficială multi-platformă JetBrains. Funcționează pe Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) și Kotlin/Wasm. API-ul este unitar pentru toate platformele: @Serializable + Json.encodeToString() funcționează la fel peste tot. Pentru iOS nu sunt necesare setări suplimentare — Kotlin/Native compilează codul serializat în binar nativ.

Cum să gestionăm câmpurile null în JSON?

Câmpurile nullable (String?) se deserializează ca null dacă valoarea în JSON lipsește sau este null. Pentru câmpurile non-nullable (String) fără valoare implicită, lipsa câmpului în JSON va provoca SerializationException. Dacă doriți ca valorile null să nu ajungă în JSON, configurați Json { encodeDefaults = false }. Aceasta va exclude din ieșire toate câmpurile egale cu default (inclusiv null pentru nullable).

Ce să faceți dacă serverul trimite câmpuri în snake_case?

Utilizați @SerialName(„snake_case_name”) pe fiecare câmp al cărui nume diferă de formatul Kotlin. Alternativ, pentru Kotlin 2.0+ este disponibil Json { namingStrategy = JsonNamingStrategy.SnakeCase } — conversia automată camelCase ↔ snake_case. Această setare se aplică tuturor câmpurilor simultan. Dacă este necesară o personalizare parțială, combinați @SerialName cu strategia globală.

Se poate serializa Kotlin Flow sau coroutine?

Nu, Flow și corutinele nu sunt direct serializabile — ele reprezintă execuția asincronă, nu date. Pentru a transmite date din Flow, trebuie să le colectați într-o colecție prin .toList() într-o corutină și să serializați colecția. În mod similar, nu puteți serializa Job, Deferred sau Continuation. Serializați doar data class — modele de date fără logică comportamentală.

Rezumat

  • kotlinx.serialization — serializare la compilare prin @Serializable, fără reflecție, cu performanță de până la 5 ori mai mare decât Gson
  • @Serializable, @SerialName, @Transient — adnotări cheie pentru configurarea serializării câmpurilor și claselor
  • Json {} builder configurează JSON: ignoreUnknownKeys, prettyPrint, coerceInputValues, encodeDefaults
  • Sealed class și serializare polimorfă — suport fără probleme pentru ierarhii de tipuri fără cod suplimentar
  • KSerializer — interfață pentru serializatoare personalizate de tipuri nestandard (Date, Bitmap, UUID)
  • Patru formate: JSON, ProtoBuf, CBOR, HOCON — se conectează prin module, API unitar pentru toate
  • Multi-platformitate — cod unitar pentru JVM, Native, JS și Wasm; critic pentru KMM și module comune

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și