Avatar ·

No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) в Kotlin с Jackson и Retrofit

Всем привет! Я новичок в Kotlin и Android-разработке. Пишу приложение, которое получает данные с сервера через Retrofit и Jackson. У меня есть дата-класс для ответа от API: ```kotlin data class UserResponse( val id: Int, val name: String, val email: String ) ``` И интерфейс Retrofit: ```kotlin interface ApiService { @GET("users/{id}") suspend fun getUser(@Path("id") userId: Int): UserResponse } ``` В `MainActivity` я делаю запрос: ```kotlin lifecycleScope.launch { val response = apiService.getUser(1) textView.text = response.name } ``` Но приложение падает с ошибкой: ``` com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.app.UserResponse` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (BufferedInputStream); line: 1, column: 2] ``` Я использую следующие зависимости в `build.gradle.kts`: ```kotlin implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-jackson:2.9.0") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2") ``` Пожалуйста, объясните, в чём проблема и как её исправить. Я уже гуглил, но не до конца понимаю, что значит "no Creators, like default construct, exist". <<>>

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

Ошибка возникает из-за того, что Jackson не может создать экземпляр вашего дата-класса UserResponse. В отличие от Java, Kotlin генерирует дата-классы с конструктором, принимающим все поля, но без конструктора без параметров (default constructor). Jackson по умолчанию пытается использовать конструктор без параметров, а затем заполнять поля через сеттеры или рефлексию, но в Kotlin сеттеры не генерируются автоматически для свойств, объявленных в конструкторе.

Решение: подключить jackson-module-kotlin

Вы уже добавили зависимость jackson-module-kotlin, но её нужно зарегистрировать в ObjectMapper, который использует Retrofit. Вот правильная настройка:

// Создаём ObjectMapper с модулем Kotlin
val objectMapper = ObjectMapper().registerModule(KotlinModule.Builder().build())

// Создаём Retrofit с этим ObjectMapper
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(JacksonConverterFactory.create(objectMapper))
    .build()

val apiService = retrofit.create(ApiService::class.java)

Модуль KotlinModule добавляет поддержку:

  • Конструкторов с параметрами (использует рефлексию для определения порядка аргументов)
  • Null-безопасности (учитывает nullable/non-null типы)
  • Значений по умолчанию в конструкторе

Альтернативный подход: использовать @JsonCreator

Если по каким-то причинам вы не хотите использовать модуль Kotlin, можно аннотировать конструктор вручную:

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

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

Проверка зависимостей

Убедитесь, что у вас нет конфликта версий. Рекомендуемые версии (для Jackson 2.15.x):

implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
implementation("com.squareup.retrofit2:converter-jackson:2.9.0")

Если ошибка остаётся

Попробуйте добавить конструктор без параметров в дата-класс со значениями по умолчанию:

data class UserResponse(
    val id: Int = 0,
    val name: String = "",
    val email: String = ""
)

Но это плохая практика — лучше решить проблему через модуль Kotlin.

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

// Модель
data class UserResponse(
    val id: Int,
    val name: String,
    val email: String
)

// Retrofit клиент
val objectMapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(JacksonConverterFactory.create(objectMapper))
    .build()

val apiService = retrofit.create(ApiService::class.java)

// Использование
lifecycleScope.launch {
    try {
        val user = apiService.getUser(1)
        println(user.name) // Работает!
    } catch (e: Exception) {
        Log.e("TAG", "Ошибка: ${e.message}")
    }
}

После регистрации модуля Kotlin Jackson сможет корректно десериализовать JSON в дата-классы Kotlin без дополнительных аннотаций.

<<>>

Альтернативное решение: использовать kotlinx.serialization вместо Jackson

Если вы не привязаны жёстко к Jackson, рекомендую рассмотреть kotlinx.serialization — это официальная библиотека сериализации от JetBrains, которая лучше интегрируется с Kotlin и не требует дополнительных модулей.

Для этого:

  1. Добавьте плагин в build.gradle.kts (проект): id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0"
  2. Добавьте зависимости: implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
  3. Аннотируйте дата-класс: @Serializable data class UserResponse(...)
  4. Используйте конвертер Retrofit: implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")

Пример настройки:

@Serializable
data class UserResponse(
    val id: Int,
    val name: String,
    val email: String
)

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
    .build()

Преимущества: не нужно регистрировать модули, работает из коробки с дата-классами, поддерживает sealed-классы и полиморфизм. Недостаток — меньше гибкости, чем у Jackson, для сложных случаев (например, кастомные десериализаторы).