Jackson JSON в Kotlin: аннотации и тонкая настройка сериализации

Библиотека Jackson стала стандартом для работы с JSON в Java-экосистеме, а благодаря отличной совместимости с Kotlin она широко применяется и в Android-разработке. В этой статье мы разберём, как с помощью аннотаций Jackson json kotlin управлять сериализацией и десериализацией, настраивать ObjectMapper и избегать подводных камней при работе с data-классами.


Быстрый старт с Jackson в Kotlin-проекте

Чтобы подключить Jackson, добавьте в build.gradle.kts зависимость. Для работы с Kotlin-классами необходимо также подключить модуль поддержки Kotlin, который использует рефлексию времени исполнения.

dependencies {
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.1")
}

После синхронизации Gradle вы можете сразу создать экземпляр ObjectMapper с поддержкой Kotlin-классов:

val mapper = jacksonObjectMapper() // расширение из модуля Kotlin

Без этого модуля Jackson не сможет корректно инстанцировать Kotlin-объекты, так как у них нет конструкторов по умолчанию в привычном для Java смысле. Модуль решает эту проблему автоматически.


Data-классы и базовая сериализация

Jackson умеет работать с обычными data-классами без каких-либо аннотаций. Например:

data class User(
    val id: Int,
    val name: String,
    val email: String
)

val mapper = jacksonObjectMapper()
val json = mapper.writeValueAsString(User(1, "Анна", "anna@example.com"))
// {"id":1,"name":"Анна","email":"anna@example.com"}

Обратное преобразование — десериализация — также тривиально:

val user = mapper.readValue<User>("""{"id":2,"name":"Иван","email":"ivan@example.com"}""")

Однако в реальных проектах имена полей в JSON часто не совпадают с названиями свойств класса. Именно здесь вступают в игру аннотации.


Аннотация @JsonProperty: переименование полей

Когда сервер отдаёт ключ "user_name", а в классе привычнее видеть userName, аннотация @JsonProperty связывает их.

data class User(
    @JsonProperty("user_id") val id: Int,
    @JsonProperty("user_name") val name: String,
    @JsonProperty("user_email") val email: String
)

Теперь Jackson будет ожидать в JSON ключи "user_id", "user_name" и "user_email", а в Kotlin-коде вы работаете с привычными именами полей. Это особенно полезно при интеграции с API, которые следуют стилю snake_case.


@JsonIgnore: исключение полей из сериализации

Некоторые свойства класса не должны попадать в JSON — например, пароль или внутренний флаг. Аннотация @JsonIgnore полностью исключает поле как из вывода, так и из чтения.

data class Account(
    val login: String,
    @JsonIgnore val password: String,
    val active: Boolean
)

Пароль не будет сериализован в JSON и не будет прочитан из JSON, если он там присутствует. Для более тонкой настройки можно использовать @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) или READ_ONLY.


@JsonInclude: управление null и пустыми значениями

По умолчанию Jackson включает поля со значением null в результирующий JSON. Аннотация @JsonInclude позволяет управлять этим поведением на уровне класса или отдельного поля.

@JsonInclude(JsonInclude.Include.NON_NULL)
data class Profile(
    val name: String?,
    val age: Int?,
    val city: String?
)

Теперь, если поле age равно null, оно вообще не появится в JSON. Это уменьшает объём передаваемых данных и делает ответы чище.


@JsonFormat: форматирование дат и чисел

Даты и временные метки требуют единого формата. Аннотация @JsonFormat задаёт шаблон для сериализации и десериализации.

data class Event(
    val title: String,
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm")
    val date: LocalDateTime
)

В JSON поле date будет записано как строка вроде "05-02-2025 14:30". Без аннотации Jackson сериализует LocalDateTime в массив чисел, что неудобно для чтения человеком.


Собственные сериализаторы и десериализаторы

Если аннотаций недостаточно, Jackson позволяет написать собственный обработчик. Это делается через наследование JsonSerializer или JsonDeserializer и регистрацию в модуле Kotlin.

class RatingSerializer : JsonSerializer<Int>() {
    override fun serialize(value: Int, gen: JsonGenerator, serializers: SerializerProvider) {
        gen.writeString("★".repeat(value.coerceIn(0..5)))
    }
}

data class Movie(
    val title: String,
    @JsonSerialize(using = RatingSerializer::class) val rating: Int
)

Теперь число рейтинга будет представлено звёздочками. При чтении же может потребоваться обратное преобразование, но для такого специфического поля лучше использовать и десериализатор.


Обзор ключевых аннотаций Jackson

Аннотация Назначение Пример
@JsonProperty Задаёт имя поля в JSON @JsonProperty("full_name") val name: String
@JsonIgnore Исключает поле из обработки @JsonIgnore val password: String
@JsonInclude Управляет включением null/пустых значений @JsonInclude(NON_NULL) class User(...)
@JsonFormat Задаёт формат даты, чисел и т.д. @JsonFormat(pattern = "dd.MM.yyyy") val birthDate: LocalDate
@JsonSerialize Указывает кастомный сериализатор @JsonSerialize(using = MySerializer::class)
@JsonDeserialize Указывает кастомный десериализатор @JsonDeserialize(using = MyDeserializer::class)

Практические рекомендации

При работе с jackson json kotlin соблюдайте несколько правил, которые сэкономят время. Во-первых, всегда используйте jacksonObjectMapper() вместо обычного ObjectMapper() — это включает поддержку Kotlin и корректную работу с data-классами. Во-вторых, тестируйте сериализацию и десериализацию на разных входных данных, особенно если сервер может прислать неполный JSON: добавьте значения по умолчанию в data-класс. В-третьих, при обфускации приложения настраивайте правила ProGuard/R8, чтобы аннотации Jackson сохранились — иначе ваш парсинг сломается на релизной сборке.


Коротко об изученном

Вы освоили аннотации Jackson для Kotlin и теперь можете гибко управлять сериализацией JSON. Библиотека jackson json kotlin даёт огромные возможности: от простого переименования полей до кастомных сериализаторов и тонкого контроля формата дат. Используйте эти знания в своих проектах — и работа с JSON станет предсказуемой и удобной.