kotlinx.serialization — мультиплатформенная библиотека от JetBrains для преобразования Kotlin-объектов в JSON, ProtoBuf, CBOR и другие форматы без использования рефлексии. В отличие от Gson и Moshi, она генерирует код сериализатора на этапе компиляции через аннотацию @Serializable, что даёт высокую производительность и безопасность типов. По данным GitHub Kotlin/kotlinx.serialization, библиотека поддерживает Kotlin/JVM, Kotlin/Native, Kotlin/JS и Kotlin/Wasm.
Главное
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.
Генерация кода в 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.
// 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 — самый популярный формат в kotlinx.serialization. Для сериализации объекта достаточно навесить на data class аннотацию @Serializable и вызвать Json.encodeToString(). Для десериализации — Json.decodeFromString() с указанием типа. Библиотека автоматически обрабатывает null-поля, списки, вложенные объекты и enums. Все поля класса по умолчанию обязательны, если не указано иное.
Настройка JSON-конфигурации выполняется через Json {} builder. В конструктор можно передать ignoreUnknownKeys = true для пропуска неизвестных полей при десериализации, prettyPrint = true для форматированного вывода, coerceInputValues = true для преобразования некорректных значений в значения по умолчанию. Также доступны настройки encodeDefaults (сериализовать поля со значениями по умолчанию) и classDiscriminator (имя поля для полиморфной сериализации).
// Пример сериализации и десериализации 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 — один из самых мощных кейсов kotlinx.serialization. Библиотека поддерживает полиморфную сериализацию для sealed class иерархий без дополнительной настройки: достаточно пометить sealed class и все его наследники аннотацией @Serializable. При сериализации добавляется поле "type" (настраивается через classDiscriminator), по которому при десериализации определяется конкретный тип.
// Полиморфная сериализация 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") в любое значение, ожидаемое сервером.
Библиотека предоставляет набор аннотаций для тонкой настройки сериализации. Основная — @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 удобна для полей, которые не нужно отправлять на сервер — например, временные вычисляемые значения или кеши.
По умолчанию все поля в kotlinx.serialization обязательны. Если поле может отсутствовать в JSON, нужно сделать его nullable (String?) или задать значение по умолчанию (val name: String = ""). Однако бывают ситуации, когда поле не nullable в Kotlin, но его может не быть в JSON из-за версионирования API. В этом случае @Required выбрасывает SerializationException при отсутствии поля, а значение по умолчанию — заполняет default без ошибки.
KSerializer — интерфейс, который реализуют все сериализаторы в kotlinx.serialization. Если стандартная генерация кода не подходит (например, для работы с Date, Bitmap, или специфическим бинарным форматом), можно написать свой сериализатор. Для этого нужно реализовать методы serialize() и deserialize(), а также предоставить descriptor — описание структуры для схемы формата.
Кастомные сериализаторы подключаются двумя способами: через аннотацию @Serializable(with = MySerializer::class) для привязки к конкретному классу или глобально через Json { serializersModule = ... } для привязки ко всем экземплярам типа. Второй способ предпочтительнее для встроенных типов (Date, UUID), чтобы не писать аннотацию на каждом поле.
// Кастомный сериализатор для 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) } }.
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.
| Формат | Модуль | Тип | Схема | Типичное применение |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | Текстовый | Опционально | REST API, хранение данных |
| ProtoBuf | kotlinx-serialization-protobuf | Бинарный | Обязательна (.proto) | Микросервисы, gRPC |
| CBOR | kotlinx-serialization-cbor | Бинарный | Опционально | IoT, мобильные устройства |
| HOCON | kotlinx-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.
Первая ошибка — игнорирование 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 использует compile-time генерацию кода через KSP, а Gson и Moshi — runtime-рефлексию. Это даёт преимущество в производительности (в 3-5 раз быстрее Gson) и типобезопасности. Gson сериализует любое поле без аннотации, что может привести к утечке данных. kotlinx.serialization требует явной аннотации @Serializable, что безопаснее. Moshi также поддерживает codegen, но только для JVM и Android.
Да, kotlinx.serialization — официальная мультиплатформенная библиотека JetBrains. Она работает на Kotlin/JVM (Android, Backend), Kotlin/Native (iOS), Kotlin/JS (Web, React) и Kotlin/Wasm. API едино для всех платформ: @Serializable + Json.encodeToString() работает одинаково везде. Для iOS не требуется дополнительных настроек — Kotlin/Native компилирует сериализованный код в нативный бинарник.
Nullable-поля (String?) десериализуются как null, если в JSON значение отсутствует или указано null. Для non-nullable полей (String) без значения по умолчанию отсутствие поля в JSON вызовет SerializationException. Если вы хотите, чтобы null-значения не попадали в JSON, настройте Json { encodeDefaults = false }. Это исключит из вывода все поля, равные default (включая null для nullable).
Используйте @SerialName("snake_case_name") на каждом поле, имя которого отличается от Kotlin-формата. Альтернативно, для Kotlin 2.0+ доступен Json { namingStrategy = JsonNamingStrategy.SnakeCase } — автоматическое преобразование camelCase ↔ snake_case. Эта настройка применяется ко всем полям сразу. Если нужна частичная кастомизация, комбинируйте @SerialName с глобальной стратегией.
Нет, Flow и корутины не сериализуемы напрямую — они представляют асинхронное выполнение, а не данные. Для передачи данных из Flow нужно собрать его в коллекцию через .toList() в корутине и сериализовать коллекцию. Аналогично, нельзя сериализовать Job, Deferred или Continuation. Сериализуйте только data class — модели данных без поведенческой логики.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также