JSON в Kotlin data классы: генерация
Создание Kotlin data classes из JSON, аннотации Gson/Moshi, nullable типы, использование в Android.
Введение
Kotlin — основной язык разработки под Android и популярный выбор для backend на JVM. Он строготипизирован, лаконичен и поддерживает data class — специальный вид классов для хранения данных с автоматически сгенерированными методамиequals, hashCode иtoString. При работе с REST API почти всегда приходится превращать JSON-ответы в Kotlin-классы. Делать это вручную долго и чревато опечатками в аннотациях. В статье разберём, как Kotlin работает с JSON через Gson, Moshi и kotlinx.serialization, как правильно описывать nullable поля, и как быстро получить готовые классы из примера ответа. Для разовой конвертации используйте нашконвертер JSON в Kotlin data классы.
Что такое data class
Data class объявляется ключевым словом data передclass. В скобках указываются свойства-конструктора, которые участвуют в equals, hashCodeи copy:
data class User(
val id: Long,
val name: String,
val email: String?
)Этот короткий код заменяет десятки строк Java: геттеры, конструктор, equals, hashCode, toString и copy. Для парсинга JSON достаточно добавить аннотации библиотеки сериализации.
Val против var
В data class принято использовать val — иммутабельные свойства. Это делает объекты потокобезопасными и упрощает рассуждение о состоянии. var оправдан только если библиотека сериализации требует setter (некоторые версии Jackson), либо если объект действительно мутирует в ходе бизнес-логики.
Библиотеки сериализации
Gson
Gson — самая старая и самая распространённая библиотека сериализации на JVM. Не требует аннотаций по умолчанию: имена полей в Kotlin совпадают с ключами JSON. Если имена различаются, используют @SerializedName:
import com.google.gson.annotations.SerializedName
data class User(
val id: Long,
@SerializedName("first_name") val firstName: String,
@SerializedName("is_active") val isActive: Boolean = true
)Gson не различает null и отсутствие поля, поэтому для опциональных значений используйте nullable-типы (String?) и значения по умолчанию.
Moshi
Moshi — современная альтернатива Gson от Square. Легче, быстрее и нативно понимает Kotlin. Аннотация @Json задаёт имя ключа:
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class User(
val id: Long,
@Json(name = "first_name") val firstName: String,
@Json(name = "is_active") val isActive: Boolean = true
)Аннотация @JsonClass(generateAdapter = true)включает кодогенерацию: на этапе компиляции создаётся TypeAdapter, что даёт прирост скорости и безопасность относительно рефлексии.
kotlinx.serialization
Официальная библиотека Kotlin, не требует рефлексии и работает в мультиплатформенных проектах (KMP). Аннотации@Serializable и @SerialName:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
val id: Long,
@SerialName("first_name") val firstName: String,
@SerialName("is_active") val isActive: Boolean = true
)Для мультиплатформенных проектов это лучший выбор: один и тот же код работает на Android, iOS, в backend и в JavaScript.
Типы данных: соответствие JSON и Kotlin
| JSON | Kotlin | Примечание |
|---|---|---|
| string | String | UTF-8 |
| number (целое) | Int, Long | Long для ID и timestamp |
| number (дробное) | Double | Float редко |
| boolean | Boolean | — |
| object | data class | Вложенный класс |
| array | List<T> | EmptyList по умолчанию |
| null | T? | Nullable-тип |
| — | JsonElement | Для произвольных данных |
Денежные суммы и точность
Для денег Double не подходит — теряется точность. Варианты: Long в копейках/центах, типBigDecimal из Java или специальный@JsonAdapter в Moshi. Генераторы по умолчанию ставят Double — проверяйте и меняйте на подходящий тип для критичных полей.
Nullable поля и значения по умолчанию
В Kotlin String и String? — разные типы. Без знака вопроса переменная не может бытьnull, и библиотека сериализации выбросит исключение, если в JSON пришёл null или поле отсутствует без значения по умолчанию.
data class Profile(
val login: String, // обязательно, не null
val bio: String? = null, // может отсутствовать или быть null
val age: Int? = null, // опциональное число
val tags: List<String> = emptyList() // пустой список по умолчанию
)Правило простое: обязательные поля — без ?, опциональные — с ? и значением по умолчанию. Списки чаще всего инициализируют emptyList(), чтобы не проверять на null в каждой точке использования.
Вложенные объекты и массивы
Вложенные объекты становятся отдельными data class. Если структура встречается только в одном месте — можно объявить её вложенной. Если в нескольких — вынесите в top-level.
data class Order(
val id: Long,
val customer: Customer,
val items: List<Item>,
val total: Double,
val createdAt: String // ISO-8601 timestamp, парсится отдельно
)
data class Customer(
val id: Long,
val name: String,
val email: String? = null
)
data class Item(
val sku: String,
val quantity: Int,
val price: Double
)Массивы разнотипных объектов
Если в массиве лежат объекты разной формы (например, GraphQL union-типы), применяют sealed class илиJsonElement из kotlinx.serialization. Sealed class удобен для дальнейшей обработки через when:
@Serializable
data class FeedItem(
val type: String,
val data: JsonElement
)
// Десериализация конкретного типа:
val post = Json.decodeFromJsonElement<Post>(item.data)Автоматическая генерация data классов
Ручное переписывание JSON в Kotlin занимает время и оставляет место для ошибок в аннотациях. Современные подходы к генерации — три уровня.
Онлайн-конвертеры
Самый быстрый путь — нашконвертер JSON в Kotlin data классы. Он умеет:
- определять типы по значениям (
Int,Long,Boolean,Double); - ставить
?на поля, которые иногда отсутствуют; - добавлять значения по умолчанию;
- генерировать аннотации Gson, Moshi или kotlinx.serialization на выбор;
- превращать ключи
snake_caseвcamelCaseс правильными@SerialName; - выносить вложенные объекты в именованные классы.
Плагины для IDE
В Android Studio и IntelliJ IDEA есть плагиныJSON To Kotlin Class (JsonToKotlinClass) иKotlin Data Class File from JSON. Они генерируют классы прямо в проекте, поддерживают шаблоны имён, фильтрацию полей и группировку вложенных объектов в отдельные файлы. Удобно, когда API ещё не стабильно и классы часто пересоздаются.
Генерация из OpenAPI
Если у вас есть OpenAPI-спецификация, используйтеopenapi-generator с флагом--generator-name kotlin илиkotlin-spring для сервера. Это даёт не только модели, но и клиентские интерфейсы — Retrofit или Ktor. На крупных проектах это окупается: меняется API — перегенерация приводит модели в соответствие за секунды.
Практические советы
Имена классов и пакетов
Генераторы часто дают технические имена: AutoGenerated,Response, Item. После генерации переименуйте в осмысленные: UserDto,OrderResponse, OrderItem. СуффиксDto или Response помогает отличать модели данных от доменных сущностей приложения.
DTO и domain models
Не используйте data class из API в бизнес-логике напрямую. Лучше разделить: DTO приходят из сети, mapper превращает их в domain models, с которыми работает UI. Это упрощает тестирование и изолирует изменения API от остального кода.
fun UserDto.toDomain(): User = User(
id = id,
displayName = "$firstName $lastName",
isVerified = isActive
)Тесты на реальных ответах
Сгенерированные классы обязательно прогоните через десериализацию нескольких реальных ответов API, включая пограничные случаи: пустые массивы, null-поля, отсутствие опциональных ключей. Если библиотека бросаетJsonDataException или MissingKotlinParameterException— добавьте ? или значение по умолчанию.
Десериализация с полиморфизмом
Когда в массиве лежат объекты разного типа, отличающиеся полемtype, применяют sealed class с полиморфной десериализацией. В kotlinx.serialization это решается через аннотацию @Serializable(with = ...) или@JsonClassDiscriminator:
@Serializable
sealed class Notification {
@Serializable
@SerialName("email")
data class Email(val to: String, val subject: String) : Notification()
@Serializable
@SerialName("push")
data class Push(val token: String, val title: String) : Notification()
}
// Десериализация:
val list = Json.decodeFromString<List<Notification>>(json)После этого можно безопасно обрабатывать список черезwhen и компилятор проверит, что все ветви обработаны. Это удобно для фидов, вебхуков и событий из очередей, где в одном потоке смешаны разные типы сообщений.
Заключение
Kotlin data class — это компактный и безопасный способ представлять данные из JSON. Gson, Moshi и kotlinx.serialization предлагают разные подходы к сериализации, но все они хорошо сочетаются с data class. Nullable-типы и значения по умолчанию помогают корректно обрабатывать отсутствующие поля, вложенные классы — структурировать сложные ответы. Для быстрой генерации классов из примера ответа используйте нашконвертер JSON в Kotlin; для командной работы на крупных проектах — плагины IDE или openapi-generator. Главное правило: после автогенерации переименовывайте классы в осмысленные, добавляйте nullable для опциональных полей и проверяйте на реальных ответах API.
Попробуйте эти инструменты
Похожие статьи
Client-side vs Server-side: где обрабатывать файлы
Сравнение обработки файлов в браузере vs на сервере: приватность, скорость, ограничения, безопасность.
Почему браузерные инструменты лучше облачных
Преимущества client-side обработки: приватность, нет загрузки, нет регистрации, скорость, бесплатно.
Конвертация файлов: лучшие практики
Правила конвертации: сохранение качества, выбор формата, batch обработка, автоматизация.
Оптимизация веб-производительности: изображения
Lazy loading, responsive images, современные форматы (WebP, AVIF), CDN, сжатие, влияние на SEO.