Introducción: ¿por qué tu código en Kotlin de repente "rompe" a Jackson?
\n\n¿Alguna vez has escrito en Kotlin una hermosa y concisa data class, le has añadido un par de campos, has ejecutado la deserialización JSON con Jackson y de repente te has encontrado con un error largo y aterrador? ¿Te suena? Si es así, no estás solo. El error "No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator" es una de las consultas más populares en Stack Overflow (¡miles de visitas!) entre los desarrolladores que migran de Java a Kotlin.
En este artículo no solo traduciremos el texto del error. Lo analizaremos "bajo el microscopio": por qué ocurre, cómo reproducirlo y, lo más importante, cómo deshacerse de él de una vez por todas. Obtendrás no solo teoría, sino también soluciones listas y probadas con código en Kotlin. ¡Allá vamos!
\n\n¿Qué significa realmente este error? En palabras sencillas
\n\nImaginemos que Jackson es como un "constructor" que necesita ensamblar un objeto de tu clase a partir de los ladrillos del JSON. Para construir una casa, el constructor necesita un plano claro (constructor) o al menos un conjunto de paredes prefabricadas (campos).
\n\nEl error "No Creators, like default construct, exist" literalmente dice: "Estimado Jackson, no puedo crear un objeto de esta clase porque no tiene un constructor por defecto (sin parámetros) y no sé cómo ensamblarlo a partir de los campos JSON proporcionados"
\n\nEn Java es simple: si no escribes ningún constructor, el compilador crea amablemente un constructor vacío por defecto. Jackson lo ve y lo usa con alegría. En Kotlin, en cambio, si defines una data class con parámetros en el constructor, el constructor sin parámetros no se crea automáticamente. Y Jackson, por defecto, busca precisamente ese.
Reproduciendo el problema: código que fallará con el error
\n\nEscribamos un ejemplo simple que garantice provocar este error. Imaginemos que tenemos una clase de usuario:
\n\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.fasterxml.jackson.module.kotlin.readValue\nimport com.fasterxml.jackson.module.kotlin.jacksonObjectMapper\n\n// ATENCIÓN: ¡Este código provocará un error!\ndata class User(val name: String, val age: Int)\n\nfun main() {\n val mapper: ObjectMapper = jacksonObjectMapper()\n val json = """{"name": "Alejandro", "age": 30}"""\n\n // Intentamos deserializar JSON en un objeto User\n val user: User = mapper.readValue(json)\n println(user)\n}\n\n\nEjecuta este código y obtendrás la famosa excepción:
\n\ncom.fasterxml.jackson.databind.exc.InvalidDefinitionException: \nCannot construct instance of `User` (no Creators, like default construct, exist): \ncannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (String)"{"name": "Alejandro", "age": 30}"; line: 1, column: 2]\n\n\n¡Ahí está, nuestra "estrella" de Stack Overflow! ¿Por qué ocurrió esto? Porque data class User(val name: String, val age: Int) no tiene un constructor sin parámetros. Todos sus campos están declarados en el constructor primario y son obligatorios (val). Jackson no puede crear un User sin saber qué valores pasar a name y age, y sin tener pistas en forma de setters.
3 formas de corregir el error (de la más simple a la más correcta)
\n\nLa buena noticia: hay varias soluciones. La elección depende de cuánto quieras controlar el proceso y qué código consideres "limpio".
\n\nForma 1: La más simple (pero no para los puristas de Kotlin)
\n\nAñadir un constructor sin parámetros manualmente. En Kotlin esto se hace especificando valores por defecto para todos los campos.
\n\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.fasterxml.jackson.module.kotlin.readValue\nimport com.fasterxml.jackson.module.kotlin.jacksonObjectMapper\n\n// Sí