kotlinx.serialization: что это такое, аннотации и сериализация в JSON

Автор: IT Sectr Опубликовано: 2026-03-15 Время чтения: 12 мин

kotlinx.serialization — мультиплатформенная библиотека от JetBrains для преобразования Kotlin-объектов в JSON, ProtoBuf, CBOR и другие форматы без использования рефлексии. В отличие от Gson и Moshi, она генерирует код сериализатора на этапе компиляции через аннотацию @Serializable, что даёт высокую производительность и безопасность типов. По данным GitHub Kotlin/kotlinx.serialization, библиотека поддерживает Kotlin/JVM, Kotlin/Native, Kotlin/JS и Kotlin/Wasm.

Главное

  • kotlinx.serialization — compile-time сериализация: код генерируется на этапе компиляции, рефлексия не используется
  • @Serializable — основная аннотация, которая запускает генерацию сериализатора для класса
  • Json {} builder — конфигурация JSON через Json { ignoreUnknownKeys = true; prettyPrint = true }
  • Мультиплатформенность — библиотека работает на JVM, Native, JS и Wasm без изменения API
  • Кастомные сериализаторы — через интерфейс KSerializer для нестандартных форматов данных

Что такое kotlinx.serialization

kotlinx.serialization — это встроенная библиотека сериализации для Kotlin, разработанная JetBrains как часть официального Kotlin-экосистемы. Её главное отличие от сторонних решений (Gson, Moshi, Jackson) в том, что она не использует рефлексию во время исполнения. Вместо этого код сериализатора генерируется на этапе компиляции с помощью Kotlin Symbol Processing (KSP) или Kotlin Compiler Plugin. Это даёт прирост производительности до 3-5 раз по сравнению с Gson и гарантирует типобезопасность.

Библиотека официально поддерживает четыре формата: JSON (через модуль kotlinx-serialization-json), ProtoBuf (kotlinx-serialization-protobuf), CBOR (kotlinx-serialization-cbor) и HOCON (kotlinx-serialization-hocon). Форматы подключаются как отдельные зависимости в build.gradle.kts, что позволяет не тащить лишние библиотеки в проект. Для каждого формата существует свой набор конфигурационных параметров.

Мультиплатформенность — ключевая фича библиотеки. Один и тот же класс с @Serializable работает на всех целях: JVM (Android, Backend), Native (iOS), JS (Web, React) и Wasm (WebAssembly). Разработчику не нужно писать разные реализации сериализации для каждой платформы — код остаётся единым. Это особенно ценно в Kotlin Multiplatform Mobile (KMM) проектах, где общий код делится между Android и iOS.

Как работает compile-time генерация кода

Генерация кода в kotlinx.serialization происходит в три этапа. На первом этапе компилятор Kotlin обнаруживает аннотацию @Serializable на классе и передаёт его Kotlin Symbol Processing (KSP) плагину. На втором этапе KSP генерирует объект-сериализатор, реализующий интерфейс KSerializer. На третьем этапе сгенерированный код компилируется вместе с исходным кодом проекта. В результате ни один из этих этапов не выполняется во время работы приложения.

Сгенерированный сериализатор работает напрямую с полями класса через их геттеры и сеттеры, без рефлексии. Это означает, что поля с модификатором private также сериализуются, если они помечены @Serializable. Производительность такого подхода близка к ручной сериализации: для простых классов (5-10 полей) время сериализации составляет 10-50 микросекунд, для сложных графов объектов — до 200 микросекунд на 1000 объектов.

Для подключения библиотеки в проект Android или Kotlin/JVM нужно добавить плагин и зависимости в build.gradle.kts. Плагин org.jetbrains.kotlin.plugin.serialization версии, совпадающей с версией Kotlin, активирует генерацию кода. Библиотека kotlinx-serialization-json добавляется в раздел dependencies с версией, не зависящей от версии Kotlin.

kotlin
// build.gradle.kts — подключение kotlinx.serialization
plugins {
    val kotlinVersion = "2.1.0"
    kotlin("jvm") version kotlinVersion
    kotlin("plugin.serialization") version kotlinVersion
}

dependencies {
    // Основной модуль сериализации
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // Дополнительные форматы
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}

Базовое использование: сериализация в JSON

JSON — самый популярный формат в kotlinx.serialization. Для сериализации объекта достаточно навесить на data class аннотацию @Serializable и вызвать Json.encodeToString(). Для десериализации — Json.decodeFromString() с указанием типа. Библиотека автоматически обрабатывает null-поля, списки, вложенные объекты и enums. Все поля класса по умолчанию обязательны, если не указано иное.

Настройка JSON-конфигурации выполняется через Json {} builder. В конструктор можно передать ignoreUnknownKeys = true для пропуска неизвестных полей при десериализации, prettyPrint = true для форматированного вывода, coerceInputValues = true для преобразования некорректных значений в значения по умолчанию. Также доступны настройки encodeDefaults (сериализовать поля со значениями по умолчанию) и classDiscriminator (имя поля для полиморфной сериализации).

kotlin
// Пример сериализации и десериализации 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")
    )

    // Сериализация в JSON с prettyPrint
    val json = Json { prettyPrint = true }
    val jsonString = json.encodeToString(project)
    println(jsonString)
    /*
    {
        "name": "kotlinx.serialization",
        "stars": 7200,
        "isActive": true,
        "languages": ["Kotlin", "Java"]
    }
    */

    // Десериализация из JSON
    val decoded = json.decodeFromString<Project>(jsonString)
    println(decoded.name)  // kotlinx.serialization
}

Пример демонстрирует базовый цикл сериализации и десериализации. Data class Project с аннотацией @Serializable автоматически получает encodeToString и decodeFromString. Поле isActive имеет значение по умолчанию true — если в JSON это поле отсутствует, используется default. Если в JSON придут неизвестные поля без ignoreUnknownKeys = true, будет выброшено исключение SerializationException.

Полиморфная сериализация sealed class

Sealed class — один из самых мощных кейсов kotlinx.serialization. Библиотека поддерживает полиморфную сериализацию для sealed class иерархий без дополнительной настройки: достаточно пометить sealed class и все его наследники аннотацией @Serializable. При сериализации добавляется поле "type" (настраивается через classDiscriminator), по которому при десериализации определяется конкретный тип.

kotlin
// Полиморфная сериализация 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}")
    }
}

Полиморфная сериализация sealed class особенно полезна в API-клиентах, где сервер возвращает разные типы ответов. Без kotlinx.serialization пришлось бы писать ручной десериализатор с when по полю-дискриминатору. С библиотекой это делается одной аннотацией. classDiscriminator позволяет переименовать поле-маркер (по умолчанию "type") в любое значение, ожидаемое сервером.

Аннотации kotlinx.serialization: полный обзор

Библиотека предоставляет набор аннотаций для тонкой настройки сериализации. Основная — @Serializable для класса. Дополнительные: @SerialName для задания имени поля в JSON (если оно отличается от Kotlin-имени), @Transient для исключения поля из сериализации, @Required для поля, которое обязательно должно присутствовать в JSON, @EncodeDefault для принудительной сериализации поля со значением по умолчанию.

АннотацияНазначениеПример
@SerializableВключает генерацию сериализатора для класса@Serializable data class User
@SerialNameЗадаёт альтернативное имя поля в формате@SerialName("user_name") val name: String
@TransientИсключает поле из сериализации@Transient val cache: MutableMap
@RequiredПоле обязательно в JSON при десериализации@Required val id: String
@EncodeDefaultСериализует поле даже со значением по умолчанию@EncodeDefault val type: Type = Type.A
@SerializerПривязывает кастомный сериализатор к классу@Serializer(forClass = Date::class)

Аннотация @SerialName критична при работе с API, где имена полей в snake_case, а Kotlin-стиль — camelCase. Например, сервер присылает "user_id", а в коде Kotlin используется userId. @SerialName("user_id") решает эту проблему без дополнительных мапперов. @Transient удобна для полей, которые не нужно отправлять на сервер — например, временные вычисляемые значения или кеши.

@Required как альтернатива nullable-полям

По умолчанию все поля в kotlinx.serialization обязательны. Если поле может отсутствовать в JSON, нужно сделать его nullable (String?) или задать значение по умолчанию (val name: String = ""). Однако бывают ситуации, когда поле не nullable в Kotlin, но его может не быть в JSON из-за версионирования API. В этом случае @Required выбрасывает SerializationException при отсутствии поля, а значение по умолчанию — заполняет default без ошибки.

Кастомные сериализаторы: KSerializer и ручное управление

KSerializer — интерфейс, который реализуют все сериализаторы в kotlinx.serialization. Если стандартная генерация кода не подходит (например, для работы с Date, Bitmap, или специфическим бинарным форматом), можно написать свой сериализатор. Для этого нужно реализовать методы serialize() и deserialize(), а также предоставить descriptor — описание структуры для схемы формата.

Кастомные сериализаторы подключаются двумя способами: через аннотацию @Serializable(with = MySerializer::class) для привязки к конкретному классу или глобально через Json { serializersModule = ... } для привязки ко всем экземплярам типа. Второй способ предпочтительнее для встроенных типов (Date, UUID), чтобы не писать аннотацию на каждом поле.

kotlin
// Кастомный сериализатор для 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())
    }
}

// Использование кастомного сериализатора
@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))
}

В примере DateSerializer преобразует java.util.Date в строку ISO 8601. Без кастомного сериализатора kotlinx.serialization не умеет работать с Date — это тип, не входящий в стандартную библиотеку Kotlin. @Serializable(with = DateSerializer::class) на конкретном поле подключает сериализатор только для этого поля. Для глобальной регистрации всех Date используйте Json { serializersModule = SerializersModule { contextual(DateSerializer) } }.

Форматы сериализации: JSON, ProtoBuf, CBOR, HOCON

kotlinx.serialization не ограничивается JSON. Библиотека поддерживает четыре встроенных формата, каждый со своим модулем и конфигурацией. JSON (kotlinx-serialization-json) — универсальный, человекочитаемый, подходит для REST API. ProtoBuf (kotlinx-serialization-protobuf) — бинарный, компактный, с обязательной схемой, для высоконагруженных микросервисов. CBOR (kotlinx-serialization-cbor) — бинарный аналог JSON, удобный для IoT и мобильных устройств с ограниченным трафиком. HOCON (kotlinx-serialization-hocon) — конфигурационный формат, совместимый с TypeSafe Config.

ФорматМодульТипСхемаТипичное применение
JSONkotlinx-serialization-jsonТекстовыйОпциональноREST API, хранение данных
ProtoBufkotlinx-serialization-protobufБинарныйОбязательна (.proto)Микросервисы, gRPC
CBORkotlinx-serialization-cborБинарныйОпциональноIoT, мобильные устройства
HOCONkotlinx-serialization-hoconТекстовыйОпциональноКонфигурационные файлы

ProtoBuf требует определения схемы в .proto-файлах, но kotlinx-serialization-protobuf генерирует Kotlin-классы напрямую из @Serializable без .proto. Это упрощает разработку: достаточно аннотировать data class и использовать ProtoBuf.encodeToByteArray(). CBOR особенно актуален для фреймворка Android, когда нужно передавать компактные бинарные данные через NFC или BLE. Размер CBOR-сообщения в среднем на 20-30% меньше JSON при том же наборе данных.

Выбор формата для проекта

Для REST API в мобильном приложении оптимален JSON — он отлаживается без дополнительных инструментов, читается в логах и совместим с любым бэкендом. Если приложение передаёт большие объёмы данных между микросервисами (сотни мегабайт) — ProtoBuf даст выигрыш в скорости до 5 раз за счёт бинарной кодировки. Для хранения настроек в файлах используйте HOCON или JSON. Для устройств с жёсткими лимитами трафика (IoT-датчики) — CBOR.

Типичные ошибки при работе с kotlinx.serialization

Первая ошибка — игнорирование unknown keys при десериализации. Если сервер добавил новое поле, а у вас ignoreUnknownKeys = false, приложение упадёт с SerializationException. По умолчанию этот флаг выключен. Решение: всегда задавайте Json { ignoreUnknownKeys = true } для production-кода, чтобы быть устойчивым к изменениям API.

Вторая ошибка — сериализация internal или private полей в data class. В Kotlin data class все поля в primary constructor по умолчанию сериализуются. Если поле содержит чувствительные данные (пароль, токен), его нужно пометить @Transient или вынести из primary constructor. @Transient исключает поле из JSON полностью, но в constructor оно может вызвать ошибку — лучше задавать такое поле в теле класса с @Transient.

Третья ошибка — полиморфная сериализация без sealed class. Если использовать open class вместо sealed, kotlinx.serialization требует явной регистрации всех наследников в serializersModule. В отличие от sealed class, где компилятор знает всех наследников, open class допускает произвольное расширение — библиотека не может автоматически определить все подтипы. Регистрация выполняется через Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }.

Ошибка версионирования библиотеки

Версия kotlinx.serialization должна быть совместима с версией Kotlin. JetBrains публикует таблицу совместимости: kotlinx-serialization 1.6.x совместима с Kotlin 1.9.x, 1.7.x — с Kotlin 2.0.x и 2.1.x. Несовпадение версий вызывает cryptic-ошибки компиляции "Symbol 'serializer' is missing". Всегда проверяйте актуальную версию на mavenCentral или в GitHub-репозитории проекта.

Часто задаваемые вопросы

Чем kotlinx.serialization отличается от Gson и Moshi?

kotlinx.serialization использует compile-time генерацию кода через KSP, а Gson и Moshi — runtime-рефлексию. Это даёт преимущество в производительности (в 3-5 раз быстрее Gson) и типобезопасности. Gson сериализует любое поле без аннотации, что может привести к утечке данных. kotlinx.serialization требует явной аннотации @Serializable, что безопаснее. Moshi также поддерживает codegen, но только для JVM и Android.

Поддерживает ли kotlinx.serialization Kotlin Multiplatform?

Да, kotlinx.serialization — официальная мультиплатформенная библиотека JetBrains. Она работает на Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) и Kotlin/Wasm. API едино для всех платформ: @Serializable + Json.encodeToString() работает одинаково везде. Для iOS не требуется дополнительных настроек — Kotlin/Native компилирует сериализованный код в нативный бинарник.

Как обрабатывать null-поля в JSON?

Nullable-поля (String?) десериализуются как null, если в JSON значение отсутствует или указано null. Для non-nullable полей (String) без значения по умолчанию отсутствие поля в JSON вызовет SerializationException. Если вы хотите, чтобы null-значения не попадали в JSON, настройте Json { encodeDefaults = false }. Это исключит из вывода все поля, равные default (включая null для nullable).

Что делать, если сервер присылает snake_case поля?

Используйте @SerialName("snake_case_name") на каждом поле, имя которого отличается от Kotlin-формата. Альтернативно, для Kotlin 2.0+ доступен Json { namingStrategy = JsonNamingStrategy.SnakeCase } — автоматическое преобразование camelCase ↔ snake_case. Эта настройка применяется ко всем полям сразу. Если нужна частичная кастомизация, комбинируйте @SerialName с глобальной стратегией.

Можно ли сериализовать Kotlin Flow или coroutine?

Нет, Flow и корутины не сериализуемы напрямую — они представляют асинхронное выполнение, а не данные. Для передачи данных из Flow нужно собрать его в коллекцию через .toList() в корутине и сериализовать коллекцию. Аналогично, нельзя сериализовать Job, Deferred или Continuation. Сериализуйте только data class — модели данных без поведенческой логики.

Итоги

  • kotlinx.serialization — compile-time сериализация через @Serializable, без рефлексии, с производительностью до 5 раз выше Gson
  • @Serializable, @SerialName, @Transient — ключевые аннотации для настройки сериализации полей и классов
  • Json {} builder конфигурирует JSON: ignoreUnknownKeys, prettyPrint, coerceInputValues, encodeDefaults
  • Sealed class и полиморфная сериализация — бесшовная поддержка иерархий типов без дополнительного кода
  • KSerializer — интерфейс для кастомных сериализаторов нестандартных типов (Date, Bitmap, UUID)
  • Четыре формата: JSON, ProtoBuf, CBOR, HOCON — подключаются модулями, API единый для всех
  • Мультиплатформенность — единый код для JVM, Native, JS и Wasm; критично для KMM и общих модулей

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также