kotlinx.serialization: co to jest, adnotacje i serializacja do JSON

Autor: IT Sectr Opublikowano: 2026-03-15 Czas czytania: 12 min

kotlinx.serialization — wieloplatformowa biblioteka od JetBrains do konwersji obiektów Kotlin na JSON, ProtoBuf, CBOR i inne formaty bez użycia refleksji. W przeciwieństwie do Gson i Moshi, generuje kod serializatora na etapie kompilacji poprzez adnotację @Serializable, co zapewnia wysoką wydajność i bezpieczeństwo typów. Według GitHub Kotlin/kotlinx.serialization, biblioteka wspiera Kotlin/JVM, Kotlin/Native, Kotlin/JS i Kotlin/Wasm.

Najważniejsze

  • kotlinx.serialization — serializacja w czasie kompilacji: kod generowany na etapie kompilacji, refleksja nie jest używana
  • @Serializable — główna adnotacja uruchamiająca generację serializatora dla klasy
  • Json {} builder — konfiguracja JSON przez Json { ignoreUnknownKeys = true; prettyPrint = true }
  • Wieloplatformowość — biblioteka działa na JVM, Native, JS i Wasm bez zmian w API
  • Niestandardowe serializatory — przez interfejs KSerializer dla nietypowych formatów danych

Co to jest kotlinx.serialization

kotlinx.serialization — to wbudowana biblioteka serializacji dla Kotlin, opracowana przez JetBrains jako część oficjalnego ekosystemu Kotlin. Jej główna różnica od rozwiązań firm trzecich (Gson, Moshi, Jackson) polega na tym, że nie używa refleksji w czasie wykonania. Zamiast tego kod serializatora jest generowany na etapie kompilacji za pomocą Kotlin Symbol Processing (KSP) lub wtyczki kompilatora Kotlin. Zapewnia to wzrost wydajności do 3-5 razy w porównaniu z Gson oraz gwarantuje bezpieczeństwo typów.

Biblioteka oficjalnie obsługuje cztery formaty: JSON (poprzez moduł kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) i HOCON (kotlinx-serialization-hocon). Format są podłączane jako osobne zależności w build.gradle.kts, co pozwala nie ciągnąć zbędnych bibliotek do projektu. Dla każdego formatu istnieje własny zestaw parametrów konfiguracyjnych.

Wieloplatformowość — kluczowa cecha biblioteki. Ta sama klasa z @Serializable działa na wszystkich platformach docelowych: JVM (Android, Backend), Native (iOS), JS (Web, React) i Wasm (WebAssembly). Deweloper nie musi pisać różnych implementacji serializacji dla każdej platformy — kod pozostaje jednolity. Jest to szczególnie cenne w projektach Kotlin Multiplatform Mobile (KMM), gdzie wspólny kod jest dzielony między Android i iOS.

Jak działa generacja kodu w czasie kompilacji

Generacja kodu w kotlinx.serialization odbywa się w trzech etapach. W pierwszym etapie kompilator Kotlin wykrywa adnotację @Serializable na klasie i przekazuje ją do wtyczki Kotlin Symbol Processing (KSP). W drugim etapie KSP generuje obiekt-serializator implementujący interfejs KSerializer. W trzecim etapie wygenerowany kod jest kompilowany razem z kodem źródłowym projektu. W rezultacie żaden z tych etapów nie jest wykonywany podczas działania aplikacji.

Wygenerowany serializator działa bezpośrednio z polami klasy poprzez ich gettery i settery, bez refleksji. Oznacza to, że pola z modyfikatorem private również są serializowane, jeśli są oznaczone @Serializable. Wydajność takiego podejścia jest bliska ręcznej serializacji: dla prostych klas (5-10 pól) czas serializacji wynosi 10-50 mikrosekund, dla złożonych grafów obiektów — do 200 mikrosekund na 1000 obiektów.

Aby podłączyć bibliotekę w projekcie Android lub Kotlin/JVM, należy dodać wtyczkę i zależności w build.gradle.kts. Wtyczka org.jetbrains.kotlin.plugin.serialization w wersji zgodnej z wersją Kotlin aktywuje generację kodu. Biblioteka kotlinx-serialization-json jest dodawana w sekcji dependencies z wersją niezależną od wersji Kotlin.

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

dependencies {
    // Główny moduł serializacji
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

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

Podstawowe użycie: serializacja do JSON

JSON — najpopularniejszy format w kotlinx.serialization. Do serializacji obiektu wystarczy umieścić na data class adnotację @Serializable i wywołać Json.encodeToString(). Do deserializacji — Json.decodeFromString() z określeniem typu. Biblioteka automatycznie obsługuje pola null, listy, zagnieżdżone obiekty i enums. Wszystkie pola klasy są domyślnie wymagane, chyba że określono inaczej.

Konfiguracja JSON jest wykonywana poprzez Json {} builder. W konstruktorze można przekazać ignoreUnknownKeys = true do pomijania nieznanych pól podczas deserializacji, prettyPrint = true do sformatowanego wyjścia, coerceInputValues = true do konwersji nieprawidłowych wartości na wartości domyślne. Dostępne są również ustawienia encodeDefaults (serializacja pól z wartościami domyślnymi) i classDiscriminator (nazwa pola dla polimorficznej serializacji).

kotlin
// Przykład serializacji i deserializacji 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")
    )

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

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

Przykład demonstruje podstawowy cykl serializacji i deserializacji. Data class Project z adnotacją @Serializable automatycznie otrzymuje encodeToString i decodeFromString. Pole isActive ma domyślną wartość true — jeśli w JSON to pole nie występuje, używana jest wartość domyślna. Jeśli w JSON pojawią się nieznane pola bez ignoreUnknownKeys = true, zostanie wyrzucony wyjątek SerializationException.

Polimorficzna serializacja sealed class

Sealed class — jeden z najpotężniejszych przypadków użycia kotlinx.serialization. Biblioteka obsługuje polimorficzną serializację dla hierarchii sealed class bez dodatkowej konfiguracji: wystarczy oznaczyć sealed class i wszystkie jej dziedzice adnotacją @Serializable. Podczas serializacji dodawane jest pole „type” (konfigurowane przez classDiscriminator), na podstawie którego podczas deserializacji określany jest konkretny typ.

kotlin
// Polimorficzna serializacja 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}")
    }
}

Polimorficzna serializacja sealed class jest szczególnie przydatna w klientach API, gdzie serwer zwraca różne typy odpowiedzi. Bez kotlinx.serialization należałoby napisać ręczny deserializator z when według pola-dyskryminatora. Z biblioteką odbywa się to za pomocą jednej adnotacji. classDiscriminator pozwala zmienić nazwę pola-markeru (domyślnie „type”) na dowolną wartość oczekiwaną przez serwer.

Adnotacje kotlinx.serialization: pełny przegląd

Biblioteka udostępnia zestaw adnotacji do precyzyjnego konfigurowania serializacji. Główna to @Serializable dla klasy. Dodatkowe: @SerialName do określania nazwy pola w JSON (jeśli różni się od nazwy Kotlin), @Transient do wykluczenia pola z serializacji, @Required dla pola, które musi być obecne w JSON, @EncodeDefault do wymuszonej serializacji pola z wartością domyślną.

AdnotacjaPrzeznaczeniePrzykład
@SerializableWłącza generację serializatora dla klasy@Serializable data class User
@SerialNameOkreśla alternatywną nazwę pola w formacie@SerialName(„user_name”) val name: String
@TransientWyklucza pole z serializacji@Transient val cache: MutableMap
@RequiredPole wymagane w JSON podczas deserializacji@Required val id: String
@EncodeDefaultSerializuje pole nawet z wartością domyślną@EncodeDefault val type: Type = Type.A
@SerializerPrzypisuje niestandardowy serializator do klasy@Serializer(forClass = Date::class)

Adnotacja @SerialName jest krytyczna przy pracy z API, gdzie nazwy pól są w snake_case, a styl Kotlin to camelCase. Na przykład serwer przysyła „user_id”, a w kodzie Kotlin używane jest userId. @SerialName(„user_id”) rozwiązuje ten problem bez dodatkowych mapperów. @Transient jest przydatna dla pól, które nie muszą być wysyłane na serwer — na przykład tymczasowe wartości obliczeniowe lub pamięć podręczna.

@Required jako alternatywa dla pól nullable

Domyślnie wszystkie pola w kotlinx.serialization są wymagane. Jeśli pole może być nieobecne w JSON, należy uczynić je nullable (String?) lub ustawić wartość domyślną (val name: String = „”). Istnieją jednak sytuacje, gdy pole nie jest nullable w Kotlin, ale może go nie być w JSON z powodu wersjonowania API. W tym przypadku @Required wyrzuca SerializationException przy braku pola, a wartość domyślna wypełnia default bez błędu.

Niestandardowe serializatory: KSerializer i ręczne zarządzanie

KSerializer — interfejs implementowany przez wszystkie serializatory w kotlinx.serialization. Jeśli standardowa generacja kodu nie odpowiada (na przykład do pracy z Date, Bitmap lub specyficznym formatem binarnym), można napisać własny serializator. W tym celu należy zaimplementować metody serialize() i deserialize() oraz dostarczyć descriptor — opis struktury dla schematu formatu.

Niestandardowe serializatory są podłączane na dwa sposoby: poprzez adnotację @Serializable(with = MySerializer::class) do przypisania do konkretnej klasy lub globalnie przez Json { serializersModule = ... } do przypisania do wszystkich instancji typu. Drugi sposób jest preferowany dla wbudowanych typów (Date, UUID), aby nie pisać adnotacji na każdym polu.

kotlin
// Niestandardowy serializator dla 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())
    }
}

// Użycie niestandardowego serializatora
@Serializable
data class Event(
    val title: String,
    @Serializable(with = DateSerializer::class)
    val date: Date
)

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

W przykładzie DateSerializer konwertuje java.util.Date na łańcuch ISO 8601. Bez niestandardowego serializatora kotlinx.serialization nie potrafi pracować z Date — to typ, który nie wchodzi w skład standardowej biblioteki Kotlin. @Serializable(with = DateSerializer::class) na konkretnym polu podłącza serializator tylko dla tego pola. Do globalnej rejestracji wszystkich Date użyj Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.

Formaty serializacji: JSON, ProtoBuf, CBOR, HOCON

kotlinx.serialization nie ogranicza się do JSON. Biblioteka obsługuje cztery wbudowane formaty, każdy z własnym modułem i konfiguracją. JSON (kotlinx-serialization-json) — uniwersalny, czytelny dla człowieka, odpowiedni dla REST API. ProtoBuf (kotlinx-serialization-protobuf) — binarny, kompaktowy, z obowiązkowym schematem, dla mocno obciążonych mikrousług. CBOR (kotlinx-serialization-cbor) — binarny odpowiednik JSON, wygodny dla IoT i urządzeń mobilnych z ograniczonym transferem. HOCON (kotlinx-serialization-hocon) — format konfiguracyjny, zgodny z TypeSafe Config.

FormatModułTypSchematTypowe zastosowanie
JSONkotlinx-serialization-jsonTekstowyOpcjonalnieREST API, przechowywanie danych
ProtoBufkotlinx-serialization-protobufBinarnyWymagany (.proto)Mikrousługi, gRPC
CBORkotlinx-serialization-cborBinarnyOpcjonalnieIoT, urządzenia mobilne
HOCONkotlinx-serialization-hoconTekstowyOpcjonalniePliki konfiguracyjne

ProtoBuf wymaga określenia schematu w plikach .proto, ale kotlinx-serialization-protobuf generuje klasy Kotlin bezpośrednio z @Serializable bez .proto. Upraszcza to rozwój: wystarczy adnotować data class i użyć ProtoBuf.encodeToByteArray(). CBOR jest szczególnie istotny dla frameworka Android, gdy trzeba przesyłać kompaktowe dane binarne przez NFC lub BLE. Rozmiar komunikatu CBOR jest średnio o 20-30% mniejszy niż JSON przy tym samym zestawie danych.

Wybór formatu dla projektu

Dla REST API w aplikacji mobilnej optymalny jest JSON — jest debugowany bez dodatkowych narzędzi, czytelny w logach i zgodny z każdym backendem. Jeśli aplikacja przesyła duże ilości danych między mikrousługami (setki megabajtów) — ProtoBuf zapewni wzrost prędkości do 5 razy dzięki kodowaniu binarnemu. Do przechowywania ustawień w plikach użyj HOCON lub JSON. Dla urządzeń z ostrą limitacją transferu (czujniki IoT) — CBOR.

Typowe błędy przy pracy z kotlinx.serialization

Pierwszy błąd — ignorowanie nieznanych kluczy podczas deserializacji. Jeśli serwer dodał nowe pole, a masz ignoreUnknownKeys = false, aplikacja padnie z SerializationException. Domyślnie ta flaga jest wyłączona. Rozwiązanie: zawsze ustawiaj Json { ignoreUnknownKeys = true } dla kodu produkcyjnego, aby być odpornym na zmiany API.

Drugi błąd — serializacja pól internal lub private w data class. W Kotlin data class wszystkie pola w primary constructor są domyślnie serializowane. Jeśli pole zawiera wrażliwe dane (hasło, token), należy je oznaczyć @Transient lub przenieść z primary constructor. @Transient wyklucza pole z JSON całkowicie, ale w constructor może spowodować błąd — lepiej umieścić takie pole w ciele klasy z @Transient.

Trzeci błąd — polimorficzna serializacja bez sealed class. Jeśli używasz open class zamiast sealed, kotlinx.serialization wymaga jawnej rejestracji wszystkich dziedziców w serializersModule. W przeciwieństwie do sealed class, gdzie kompilator zna wszystkich dziedziców, open class dopuszcza dowolne rozszerzanie — biblioteka nie może automatycznie określić wszystkich podtypów. Rejestracja odbywa się przez Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.

Błąd wersjonowania biblioteki

Wersja kotlinx.serialization musi być zgodna z wersją Kotlin. JetBrains publikuje tabelę zgodności: kotlinx-serialization 1.6.x jest zgodna z Kotlin 1.9.x, 1.7.x — z Kotlin 2.0.x i 2.1.x. Niezgodność wersji powoduje tajemnicze błędy kompilacji „Symbol ‘serializer’ is missing”. Zawsze sprawdzaj aktualną wersję na mavenCentral lub w repozytorium GitHub projektu.

Często zadawane pytania

Czym różni się kotlinx.serialization od Gson i Moshi?

kotlinx.serialization używa generacji kodu w czasie kompilacji przez KSP, a Gson i Moshi używają refleksji w czasie wykonania. Zapewnia to przewagę w wydajności (3-5 razy szybciej niż Gson) i bezpieczeństwie typów. Gson serializuje dowolne pole bez adnotacji, co może prowadzić do wycieku danych. kotlinx.serialization wymaga jawnej adnotacji @Serializable, co jest bezpieczniejsze. Moshi również obsługuje codegen, ale tylko dla JVM i Android.

Czy kotlinx.serialization obsługuje Kotlin Multiplatform?

Tak, kotlinx.serialization to oficjalna wieloplatformowa biblioteka JetBrains. Działa na Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) i Kotlin/Wasm. API jest jednolite dla wszystkich platform: @Serializable + Json.encodeToString() działa wszędzie tak samo. Dla iOS nie są wymagane dodatkowe ustawienia — Kotlin/Native kompiluje zserializowany kod do natywnego pliku binarnego.

Jak obsługiwać pola null w JSON?

Pola nullable (String?) są deserializowane jako null, jeśli w JSON wartość nie występuje lub jest określona jako null. Dla pól non-nullable (String) bez wartości domyślnej brak pola w JSON spowoduje SerializationException. Jeśli chcesz, aby wartości null nie trafiały do JSON, skonfiguruj Json { encodeDefaults = false }. Wykluczy to z wyjścia wszystkie pola równe default (w tym null dla nullable).

Co zrobić, gdy serwer przysyła pola w snake_case?

Użyj @SerialName(„snake_case_name”) na każdym polu, którego nazwa różni się od formatu Kotlin. Alternatywnie, dla Kotlin 2.0+ dostępny jest Json { namingStrategy = JsonNamingStrategy.SnakeCase } — automatyczna konwersja camelCase ↔ snake_case. To ustawienie stosuje się do wszystkich pól jednocześnie. Jeśli potrzebna jest częściowa personalizacja, łącz @SerialName z globalną strategią.

Czy można serializować Kotlin Flow lub coroutine?

Nie, Flow i korutyny nie są bezpośrednio serializowalne — reprezentują one asynchroniczne wykonanie, a nie dane. Aby przesłać dane z Flow, należy zebrać je do kolekcji poprzez .toList() w korutynie i zserializować kolekcję. Analogicznie, nie można serializować Job, Deferred ani Continuation. Serializuj tylko data class — modele danych bez logiki behawioralnej.

Podsumowanie

  • kotlinx.serialization — serializacja w czasie kompilacji przez @Serializable, bez refleksji, z wydajnością do 5 razy wyższą niż Gson
  • @Serializable, @SerialName, @Transient — kluczowe adnotacje do konfiguracji serializacji pól i klas
  • Json {} builder konfiguruje JSON: ignoreUnknownKeys, prettyPrint, coerceInputValues, encodeDefaults
  • Sealed class i polimorficzna serializacja — bezproblemowa obsługa hierarchii typów bez dodatkowego kodu
  • KSerializer — interfejs dla niestandardowych serializatorów nietypowych typów (Date, Bitmap, UUID)
  • Cztery formaty: JSON, ProtoBuf, CBOR, HOCON — podłączane modułami, API jednolite dla wszystkich
  • Wieloplatformowość — jednolity kod dla JVM, Native, JS i Wasm; kluczowe dla KMM i wspólnych modułów

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również