kotlinx.serialization: модели и JSON

Как превратить Kotlin-классы в JSON и обратно одинаково на Android и iOS, без рефлексии.

kotlinx.serialization — мультиплатформенная библиотека сериализации, генерирующая код сериализаторов на этапе компиляции по аннотации @Serializable, без рефлексии в рантайме.

Почему не Gson/Moshi

Привычные на Android Gson и Moshi опираются на рефлексию JVM — её нет на Kotlin/Native. Поэтому в KMP используют kotlinx.serialization: компиляторный плагин генерирует сериализатор для каждого @Serializable-класса прямо в код. Это работает на всех целях и быстрее рефлексии.

Объявление модели

// commonMain
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName

@Serializable
data class OrderDto(
    val id: Long,
    @SerialName("total_amount") val totalAmount: Double,
    val items: List<ItemDto>
)

@Serializable
data class ItemDto(val name: String, val qty: Int)

@SerialName сопоставляет имя поля Kotlin с именем в JSON (snake_case в API против camelCase в коде).

Парсинг и сборка JSON

// commonMain
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString

val json = Json { ignoreUnknownKeys = true }
val order: OrderDto = json.decodeFromString(rawJson)
val back: String = json.encodeToString(order)

Флаг ignoreUnknownKeys важен на проде: API добавит новое поле — клиент не упадёт.

Подключение плагина

plugins {
    kotlin("multiplatform")
    kotlin("plugin.serialization") version "2.0.0"
}

Как работает под капотом

Плагин сериализации на этапе компиляции для каждого @Serializable-класса генерирует объект-сериализатор: код, который читает/пишет поля по порядку, зная их типы. Никакого анализа классов в рантайме — всё известно заранее. Поэтому одна и та же модель сериализуется идентично на Android и iOS, и нет проблемы «на iOS рефлексия не работает». Это же объясняет требование @Serializable: без аннотации плагину нечего генерировать, и компиляция выдаст ошибку при попытке сериализовать такой тип.

Частые ошибки

Забыть @Serializable на вложенной модели (ItemDto) — компилятор пожалуется, что для типа нет сериализатора. Аннотировать нужно все участвующие классы. Вторая ошибка — не выставить ignoreUnknownKeys и ловить краши при эволюции API. Третья — путать DTO (модели API) с доменными моделями; держите их раздельно и маппите, иначе формат бэкенда протечёт во всё приложение.

Итоги

  • kotlinx.serialization работает без рефлексии — пригодна для Native.
  • Сериализатор генерируется на компиляции по @Serializable.
  • @SerialName мостит имена JSON и Kotlin; ignoreUnknownKeys спасает при росте API.
  • Все участвующие классы должны быть аннотированы.
Проверьте себя
1. Почему в KMP не используют Gson для сериализации?
AGson слишком новый
BGson опирается на рефлексию JVM, которой нет в Kotlin/Native
CGson не умеет JSON
DGson платный
2. Когда генерируется сериализатор в kotlinx.serialization?
AВ рантайме через рефлексию
BНа этапе компиляции по аннотации @Serializable
CПри первом запросе к сети
DНикогда, он встроен
3. Зачем нужен флаг ignoreUnknownKeys?
AУскоряет парсинг
BПозволяет клиенту не падать, когда API добавляет новые поля
CШифрует JSON
DВключает рефлексию