Leer y escribir JSON en Kotlin con Jackson

En este tutorial, repasaremos cómo serializar y deserializar objetos y arreglos JSON en objetos y listas de Kotlin y viceversa, usando Jackson.

En este artículo, veremos cómo leer y escribir archivos JSON en Kotlin, específicamente, usando la biblioteca Jackson.

Dependencia Jackson

Para utilizar Jackson, querremos agregar su dependencia jackson-module-kotlin a nuestro proyecto. Si está utilizando Maven, simplemente puede agregar:

1
2
3
4
5
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
    <version>2.12.1</version>
</dependency>

O, si está usando Gradle, puede agregar:

1
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.12.1'

Con la dependencia en su lugar, definamos un objeto JSON que querremos leer:

1
2
3
4
5
6
{
   "id":101,
   "username":"admin",
   "password":"Admin123",
   "fullName":"Best Admin"
}

Leer un objeto JSON en un objeto Kotlin

Echemos un vistazo a cómo podemos deserializar un objeto JSON en un objeto Kotlin. Ya que queremos convertir el contenido JSON en un objeto Kotlin, definamos una clase de datos Usuario:

1
2
3
4
5
6
data class User (
    val id: Int,
    val username: String,
    val password: String,
    val fullName: String
)

Luego, querremos instanciar el mapeador de objetos, lo cual se puede hacer fácilmente con:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

// Registering the Kotlin module with the ObjectMpper instance
val mapper = jacksonObjectMapper()

// JSON String
val jsonString = """{
    "id":101,
    "username":"admin",
    "password":"Admin123",
    "fullName":"Best Admin"
    }"""

Una vez hecho esto, podemos pasar contenido JSON a nuestros métodos ObjectMapper, como readValue(), como es habitual para Jackson:

1
2
3
4
// Read data from a JSON string
val userFromJson = mapper.readValue<User>(jsonString)
// Or
val userFromJsonWithType: User = mapper.readValue(jsonString)

Si imprimimos el valor de userFromJson veremos algo como esto:

1
User(id=101, username=admin, password=Admin123, fullName=Best Admin)

La función readValue() se puede usar con o sin el parámetro Class, como viste un poco antes con las dos variables (userFromJson y userFromJsonWithType).

Nota: El uso de la función sin el parámetro Class materializará el tipo y creará automáticamente una TypeReference para Jackson.

Escribir un objeto JSON desde un objeto Kotlin

Ahora, echemos un vistazo a cómo podemos serializar un objeto Kotlin en un objeto JSON.

Usaremos la misma clase de datos (Usuario) y jugaremos un poco con las características de Jackson:

1
2
3
val user = User(102, "test", "pass12", "Test User")
val userJson = mapper.writeValueAsString(user)
println(userJson)

Aquí, hemos instanciado una nueva instancia de la clase Usuario, con algunos valores.

La función writeValueAsString() es nuestro jugador clave aquí y serializa cualquier objeto y sus campos como una cadena.

Imprimamos la variable de cadena userFromJson y veamos cómo se ve:

1
2
3
4
5
6
{
   "id":102,
   "username":"test",
   "password":"pass12",
   "fullName":"Test User"
}

Escribir la lista de Kotlin en una matriz JSON {#escribir una lista de Kotlin en una matriz JSON}

A menudo, estamos tratando con listas y matrices, en lugar de objetos singulares. Echemos un vistazo a cómo podemos * serializar una lista de Kotlin en una matriz JSON *.

Usando la misma clase de datos, crearemos una lista de ‘Usuarios’ y los serializaremos en una matriz JSON:

1
2
3
4
5
6
val userList = mutableListOf<User>()
userList.add(User(102, "jsmith", "[correo electrónico protegido]", "John Smith"))
userList.add(User(103, "janed", "Pass1", "Jane Doe"))

val jsonArray = mapper.writeValueAsString(userList)
println(jsonArray)

Esto resulta en:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
   {
      "id":102,
      "username":"jsmith",
      "password":"[correo electrónico protegido]",
      "fullName":"John Smith"
   },
   {
      "id":103,
      "username":"janed",
      "password":"Pass1",
      "fullName":"Jane Doe"
   }
]

Lectura de matriz JSON en lista Kotlin

Ahora, vayamos al revés y deserialicemos una matriz JSON en una lista de Kotlin:

1
2
val userListFromJson: List<User> = mapper.readValue(jsonArray)
println(userListFromJson)

Y esto da como resultado:

1
[User(id=102, username=test, password=pass12, fullName=Test User), User(id=103, username=user, password=Pass1, fullName=Demo User)]

Manejo de relaciones bidireccionales JSON

Otra cosa común para encontrar son las relaciones bidireccionales. En muchos casos, es posible que desee tener una relación de uno a muchos o de muchos a uno entre algunos objetos.

Para esto, vamos a crear una clase de datos ‘Autor’ que puede contener uno o más objetos de tipo ‘Libro’ y el ‘Libro’ puede tener un ‘Autor’:

1
2
3
4
5
6
7
8
9
data class Author (
    val name: String,
    val books: MutableList<Book>
)

data class Book (
    val title: String,
    val author: Author
)

El ‘Autor’ contiene una lista, llamada ’libros’, de tipo ‘Libro’. Al mismo tiempo, el ‘Libro’ contiene una instancia de ‘Autor’.

Hagamos algunas instancias e intentemos serializarlas:

1
2
3
4
5
val author = Author("JK Rowling", mutableListOf())
val bookOne = Book("Harry Potter 1", author)
val bookTwo = Book("Harry Potter 2", author)
author.books.add(bookOne)
author.books.add(bookTwo)

Si tuviéramos que hacer lo mismo que antes:

1
val authors = mapper.writeValueAsString(author)

Nos encontraríamos rápidamente con un problema:

1
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: Book["author"]->Author["books"]->...

El ‘Autor’ contiene el ‘Libro’, que contiene el ‘Autor’, que contiene el ‘Libro’, que contiene el ‘Autor’, y así sucesivamente, hasta que se genera un ‘StackOverflowError’.

Afortunadamente, esto se puede arreglar fácilmente usando las anotaciones @JsonManagedReference y @JsonBackReference. Le informamos a Jackson sobre esta relación bidireccional, que podría durar para siempre:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
data class Author (
    val name: String,
    @JsonManagedReference
    val books: MutableList<Book>
);

data class Book (
    val title: String,
    @JsonBackReference
    val author: Author
);

@JsonManagedReference especifica el atributo que se serializará normalmente y @JsonBackReference especificará el atributo omitido de la serialización. Efectivamente, eliminaremos la referencia del autor del Libro cuando cada uno sea serializado.

Ahora, podemos serializar a los autores:

1
2
val authors = mapper.writeValueAsString(author)
println(authors)

Si ejecutamos este código, producirá:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
   "name":"JK Rowling",
   "books":[
      {
         "title":"Harry Potter 1"
      },
      {
         "title":"Harry Potter 2"
      }
   ]
}

Conclusión

En este tutorial, hemos repasado cómo leer y escribir archivos JSON en Kotlin con Jackson.

Hemos repasado las dependencias necesarias, cómo serializar y deserializar objetos JSON y Kotlin, así como matrices y listas.

Finalmente, hemos cubierto cómo manejar las relaciones bidireccionales al serializar y deserializar.