Jackson в Kotlin: как исправить ошибку "No Creators, like default construct, exist"

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему ваш код на Kotlin внезапно "ломает" Jackson?

Вы когда-нибудь писали на Kotlin красивый, лаконичный data class, добавляли в него пару полей, запускали десериализацию JSON через Jackson — и вдруг получали в лицо длинную, пугающую ошибку? Знакомо? Если да, то вы не одиноки. Ошибка "No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator" — один из самых популярных запросов на Stack Overflow (тысячи просмотров!) среди разработчиков, переходящих с Java на Kotlin.

В этой статье мы не просто переведём текст ошибки. Мы разберём её "под микроскопом": почему она возникает, как её воспроизвести, и, самое главное, — как от неё избавиться раз и навсегда. Вы получите не только теорию, но и готовые, проверенные решения с кодом на Kotlin. Поехали!

Что вообще значит эта ошибка? Простыми словами

Давайте представим, что Jackson — это такой "строитель", которому нужно собрать объект вашего класса из кирпичиков JSON. Чтобы собрать дом, строителю нужен понятный план (конструктор) или хотя бы набор готовых стен (поля).

Ошибка "No Creators, like default construct, exist" буквально говорит: "Уважаемый Jackson, я не могу создать объект этого класса, потому что у него нет конструктора по умолчанию (без параметров), и я не знаю, как его собрать из переданных JSON-полей!"

В Java всё просто: если вы не написали ни одного конструктора, компилятор любезно создаёт пустой конструктор по умолчанию. Jackson его видит и радостно использует. В Kotlin же, если вы определяете data class с параметрами в конструкторе, конструктор без параметров не создаётся автоматически. А Jackson, по умолчанию, ищет именно его.

Воспроизводим проблему: код, который упадёт с ошибкой

Давайте напишем простой пример, который гарантированно вызовет эту ошибку. Представим, что у нас есть класс пользователя:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

// ВНИМАНИЕ: Этот код вызовет ошибку!
data class User(val name: String, val age: Int)

fun main() {
    val mapper: ObjectMapper = jacksonObjectMapper()
    val json = """{"name": "Алексей", "age": 30}"""

    // Пытаемся десериализовать JSON в объект User
    val user: User = mapper.readValue(json)
    println(user)
}

Запустите этот код, и вы получите заветное исключение:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Cannot construct instance of `User` (no Creators, like default construct, exist): 
cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"name": "Алексей", "age": 30}"; line: 1, column: 2]

Вот она, наша "звезда" Stack Overflow! Почему это произошло? Потому что data class User(val name: String, val age: Int) не имеет конструктора без параметров. Все его поля объявлены в первичном конструкторе, и они обязательны (val). Jackson не может создать User, не зная, какие значения передать в name и age, и не имея подсказок в виде сеттеров.

3 способа исправить ошибку (от простого к правильному)

Хорошая новость: решений несколько. Выбор зависит от того, насколько вы хотите контролировать процесс и какой код считаете "чистым".

Способ 1: Самый простой (но не для Kotlin-пуристов)

Добавить конструктор без параметров вручную. В Kotlin это делается через указание значений по умолчанию для всех полей.

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

// Даём значения по умолчанию — и Jackson счастлив!
data class User(
    val name: String = "",
    val age: Int = 0
)

fun main() {
    val mapper: ObjectMapper = jacksonObjectMapper()
    val json = """{"name": "Алексей", "age": 30}"""
    val user: User = mapper.readValue(json)
    println(user) // User(name=Алексей, age=30)
}

Плюсы: Просто, работает "из коробки".
Минусы: Вы размываете бизнес-логику. Поле age не может быть "просто 0" в реальном приложении. Это костыль, а не решение.

Способ 2: "Правильный" — используем Jackson Kotlin Module

Это самый рекомендуемый способ для современных Kotlin-проектов. Jackson предоставляет специальный модуль, который "учит" его работать с Kotlin-классами: он понимает data class, первичные конструкторы, nullability и, что самое важное, может создавать объекты, используя конструктор с параметрами.

Всё, что вам нужно — добавить зависимость в build.gradle.kts:

implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")

А затем просто использовать jacksonObjectMapper() (как мы уже сделали в примере выше). Этот метод автоматически регистрирует Kotlin-модуль. Если вы создаёте ObjectMapper руками, не забудьте добавить модуль:

val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())

Теперь наш исходный код (без значений по умолчанию) будет работать идеально:

import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

// Никаких значений по умолчанию!
data class User(val name: String, val age: Int)

fun main() {
    val mapper = jacksonObjectMapper() // Магия!
    val json = """{"name": "Алексей", "age": 30}"""
    val user: User = mapper.readValue(json)
    println(user) // User(name=Алексей, age=30)
}

Плюсы: Чистый Kotlin-код, никаких костылей, полная поддержка фич языка (null safety, неизменяемые поля).
Минусы: Нужно добавить зависимость (но она есть почти во всех современных проектах).

Способ 3: Использовать аннотации Jackson (для сложных случаев)

Иногда вам нужно полностью контролировать процесс десериализации. Например, если JSON приходит с сервера в странном формате, или вам нужно вызвать какой-то специфический конструктор. Тогда на помощь приходят аннотации @JsonCreator и @JsonProperty.

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

data class User @JsonCreator constructor(
    @JsonProperty("user_name") val name: String,
    @JsonProperty("user_age") val age: Int
)

fun main                

Блоги

Рекомендаций по книгам