Avatar ·

Jackson/Kotlin: No Creators, like default construct, exist — ошибка десериализации

Всем привет! Я новичок в Kotlin и пытаюсь использовать Retrofit + Jackson для парсинга JSON в Android. У меня есть простой data класс: ```kotlin data class User( val id: Int, val name: String, val email: String ) ``` И ретрофит интерфейс: ```kotlin interface ApiService { @GET("users/{id}") suspend fun getUser(@Path("id") id: Int): User } ``` Но при попытке выполнить запрос я получаю исключение: ``` com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.app.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (BufferedInputStream); line: 1, column: 2] ``` Я использую Jackson через модуль `jackson-module-kotlin`. В чем проблема? Я же определил data class со всеми полями через val, почему Jackson не может создать объект? Пробовал добавлять @JsonProperty — не помогло. Подскажите, как правильно настроить десериализацию для Kotlin data классов? Спасибо! Теги: android, kotlin, serialization, jackson, retrofit2 <<>>

Причина ошибки

Проблема в том, что Jackson по умолчанию требует для десериализации либо пустой конструктор (default constructor), либо конструктор с аннотациями @JsonCreator и @JsonProperty. Kotlin data классы генерируют конструктор с параметрами, но Jackson не знает, как сопоставить параметры JSON с полями конструктора, если явно не указать это.

Вы используете jackson-module-kotlin, но, скорее всего, не подключили его к ObjectMapper. Вот правильное решение.

Шаг 1: Подключите jackson-module-kotlin в build.gradle

dependencies {
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
    implementation("com.squareup.retrofit2:converter-jackson:2.9.0")
}

Шаг 2: Настройте Retrofit с Jackson конвертером и модулем Kotlin

Создайте кастомный ObjectMapper с зарегистрированным Kotlin модулем и передайте его в Retrofit:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory

val mapper = jacksonObjectMapper() // Этот метод уже включает Kotlin модуль!

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build()

Важно: Используйте jacksonObjectMapper() из jackson-module-kotlin, а не стандартный ObjectMapper(). Первый автоматически регистрирует KotlinModule, который умеет работать с data классами.

Шаг 3: Альтернатива — используйте @JsonCreator (если не хотите менять ObjectMapper)

Если вы не можете настроить ObjectMapper глобально, можно аннотировать конструктор data класса:

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

data class User @JsonCreator constructor(
    @JsonProperty("id") val id: Int,
    @JsonProperty("name") val name: String,
    @JsonProperty("email") val email: String
)

Но это громоздко, поэтому первый способ предпочтительнее.

Почему это работает?

  • KotlinModule анализирует data классы и автоматически определяет, какие параметры конструктора соответствуют полям JSON.
  • Он также поддерживает значения по умолчанию (val name: String = "Unknown"), если поле отсутствует в JSON.
  • Без этого модуля Jackson видит только пустой конструктор (которого нет) и падает с ошибкой.

Полный пример рабочего кода

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path

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

interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
}

fun main() {
    val mapper = jacksonObjectMapper()
    val retrofit = Retrofit.Builder()
        .baseUrl("https://jsonplaceholder.typicode.com/")
        .addConverterFactory(JacksonConverterFactory.create(mapper))
        .build()
        .create(ApiService::class.java)

    // Теперь вызов retrofit.getUser(1) будет работать без ошибок
}

Проверьте, что в вашем проекте используется jacksonObjectMapper(), а не ObjectMapper(). Это самая частая причина ошибки.

<<>>

Альтернативное решение: используйте Gson или Kotlinx Serialization

Если вы не хотите возиться с Jackson, рассмотрите другие библиотеки сериализации, которые лучше интегрированы с Kotlin.

Вариант 1: Gson

Gson по умолчанию работает с Kotlin data классами без дополнительных настроек:

implementation("com.squareup.retrofit2:converter-gson:2.9.0")

// Retrofit
val retrofit = Retrofit.Builder()
    .baseUrl("...")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

Вариант 2: Kotlinx Serialization (рекомендуется для Kotlin Multiplatform)

Это нативная библиотека Kotlin:

// build.gradle.kts
plugins {
    kotlin("plugin.serialization") version "1.9.0"
}
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
    implementation("com.jakewharton.retrofit2:retrofit2-kotlinx-serialization-converter:1.0.0")
}

// Класс
@Serializable
data class User(val id: Int, val name: String, val email: String)

// Retrofit
val retrofit = Retrofit.Builder()
    .baseUrl("...")
    .addConverterFactory(kotlinx.serialization.json.Json.asConverterFactory("application/json".toMediaType()))
    .build()

Оба варианта избавят вас от ошибки "No Creators" и не требуют настройки ObjectMapper. Выбирайте то, что удобнее для вашего проекта.