1. Introducción a Jackson
El intercambio de datos entre una aplicación Java y el mundo exterior muy a menudo puede plantearse así: «¿Cómo convertir un objeto Java en JSON — y viceversa?» Por supuesto, puedes escribir tu propio analizador con String.split y expresiones regulares (e incluso disfrutar del sufrimiento), pero en proyectos reales nadie lo hace.
En Java hay varias bibliotecas populares para trabajar con JSON. La principal de ellas es Jackson. Es tan popular que forma parte de Spring Boot y se usa en multitud de otros frameworks y bibliotecas.
¿Qué es Jackson?
Jackson es una biblioteca potente y flexible para la serialización (convertir un objeto Java a JSON) y la deserialización (la operación inversa). Consta de varios módulos, pero para el 90 % de los casos te harán falta dos:
- jackson-core — el núcleo, un analizador de bajo nivel.
- jackson-databind — un módulo de alto nivel que sabe convertir objetos Java a JSON y viceversa.
Lo único que necesitas saber para empezar: si ves la clase ObjectMapper, es Jackson.
Jackson se considera el estándar de facto para trabajar con JSON en Java porque combina sencillez y potencia. Para serializar o deserializar datos bastan literalmente un par de líneas de código, lo que hace que la biblioteca sea cómoda incluso para principiantes. Al mismo tiempo, no se limita a lo básico: gracias a las anotaciones y a un gran número de ajustes, puedes controlar de forma flexible cómo se convierten los datos en objetos y viceversa, ya se trate de colecciones, entidades anidadas o fechas en distintos formatos.
También es importante que Jackson funciona muy rápido y de forma eficiente, algo crucial para proyectos reales con grandes volúmenes de datos. Los desarrolladores de la biblioteca mantienen el soporte para nuevas versiones de Java y los cambios actuales en el estándar JSON — Jackson sigue siendo una elección fiable tanto para aplicaciones sencillas como para grandes sistemas corporativos.
2. Lectura de JSON (deserialización)
Vamos a intentar leer una cadena JSON y convertirla en un objeto Java. Para ello necesitaremos:
- Una clase de datos (por ejemplo, Person)
- La clase ObjectMapper de Jackson
Añadir Jackson
Si usas Maven, añade en pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
Si usas Gradle, de forma análoga:
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
Ejemplo de clase
public class Person {
public String name;
public int age;
}
Nota: Para simplificar, los campos son públicos. Más adelante hablaremos de trabajar con campos privados y getters/setters.
Ejemplo de JSON
{
"name": "Alice",
"age": 30
}
Deserialización: convertir JSON en un objeto
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"name\": \"Alice\", \"age\": 30}";
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json, Person.class);
System.out.println(person.name); // Alice
System.out.println(person.age); // 30
}
}
Aquí Jackson analiza la cadena JSON, encuentra los campos que coinciden por nombre con los campos o getters de la clase y rellena el objeto con los valores correspondientes mediante readValue.
Deserialización de una lista de objetos
Supongamos que tenemos un array:
[
{ "name": "Bob", "age": 22 },
{ "name": "Eve", "age": 27 }
]
Lo deserializamos a una lista:
import com.fasterxml.jackson.core.type.TypeReference;
// ...
String json = "[{\"name\": \"Bob\", \"age\": 22}, {\"name\": \"Eve\", \"age\": 27}]";
ObjectMapper mapper = new ObjectMapper();
List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});
for (Person p : people) {
System.out.println(p.name + " (" + p.age + ")");
}
Quizá te preguntes: ¿por qué no podemos simplemente escribir mapper.readValue(json, List.class)? Recuerda que los genéricos en Java se borran en tiempo de compilación. Por eso necesitamos TypeReference, para que Jackson entienda que dentro de la lista debe haber objetos de tipo Person.
3. Escritura de JSON (serialización)
Ahora hagamos la operación inversa: convertir un objeto Java en una cadena JSON.
Ejemplo
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
String json = mapper.writeValueAsString(person);
System.out.println(json);
// {"name":"Charlie","age":40}
Serialización de una lista de objetos
ObjectMapper mapper = new ObjectMapper();
List<Person> people = new ArrayList<>();
people.add(new Person("Anna", 25));
people.add(new Person("Dmitry", 31));
String json = mapper.writeValueAsString(people);
System.out.println(json);
// [{"name":"Anna","age":25},{"name":"Dmitry","age":31}]
Escritura en un archivo
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
mapper.writeValue(new File("person.json"), person);
// El archivo person.json ahora contiene el objeto JSON
JSON bonito (pretty)
Por defecto Jackson lo escribe todo en una sola línea. Pero también existe una variante más «orientada a las personas» — pretty printing. Significa que la cadena JSON se muestra en un formato legible: con sangrías, saltos de línea y un formato cuidado.
A diferencia del JSON «normal», que suele escribirse en una sola línea por compacidad, la variante «bonita» es para las personas — para poder revisar fácilmente la estructura de los datos en logs, archivos o en la pantalla.
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
String prettyJson = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(person);
System.out.println(prettyJson);
/*
{
"name" : "Charlie",
"age" : 40
}
*/
4. Anotaciones de Jackson
Jackson admite numerosas anotaciones que permiten controlar el proceso de serialización y deserialización. Estas son las más útiles:
@JsonProperty
Permite indicar el nombre del campo en el JSON si difiere del nombre del campo en la clase.
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
@JsonProperty("full_name")
public String name;
public int age;
}
{"full_name": "Olga", "age": 28}
Jackson entenderá que el campo full_name del JSON debe asignarse al campo name del objeto.
@JsonIgnore
Si no quieres serializar o deserializar algún campo:
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Person {
public String name;
@JsonIgnore
public int age;
}
En el JSON no aparecerá el campo age, aunque exista en el objeto.
@JsonInclude
Controla qué campos se incluirán en el JSON. Por ejemplo, serializar solo los campos no vacíos:
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
public String name;
public Integer age;
}
Si age == null, en el JSON no habrá clave "age".
@JsonFormat
Permite establecer el formato para serializar fechas y horas.
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class Event {
public String title;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
public Date date;
}
Event event = new Event();
event.title = "Hackathon";
event.date = new Date();
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(event);
// {"title":"Hackathon","date":"2024-06-07 15:23:00"}
Ejemplo: todo junto
import com.fasterxml.jackson.annotation.*;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
@JsonProperty("full_name")
private String name;
private int age;
@JsonIgnore
private String password;
// ¡Los getters y setters son obligatorios para los campos privados!
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
5. Práctica: serialización y deserialización teniendo en cuenta las anotaciones
Ampliemos tu aplicación de ejemplo: ahora tenemos una clase User con campos privados, fecha de registro y una contraseña que no debe aparecer en el JSON.
import com.fasterxml.jackson.annotation.*;
import java.util.Date;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
@JsonProperty("login")
private String username;
private int age;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date registered;
@JsonIgnore
private String password;
// ¡Los getters y setters son obligatorios!
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Date getRegistered() { return registered; }
public void setRegistered(Date registered) { this.registered = registered; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
Serialización
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.SimpleDateFormat;
ObjectMapper mapper = new ObjectMapper();
User user = new User();
user.setUsername("superuser");
user.setAge(42);
user.setRegistered(new SimpleDateFormat("yyyy-MM-dd").parse("2024-06-07"));
user.setPassword("qwerty123"); // ¡No aparecerá en el JSON!
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(json);
/*
{
"login" : "superuser",
"age" : 42,
"registered" : "2024-06-07"
}
*/
Deserialización
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String json = "{ \"login\": \"superuser\", \"age\": 42, \"registered\": \"2024-06-07\" }";
User user = mapper.readValue(json, User.class);
System.out.println(user.getUsername()); // superuser
System.out.println(user.getAge()); // 42
System.out.println(user.getRegistered());// Fri Jun 07 00:00:00 ...
System.out.println(user.getPassword()); // null (¡y eso está bien!)
6. Errores típicos al trabajar con Jackson
Error n.º 1: falta el constructor sin parámetros.
Jackson no podrá crear una instancia de la clase si no tiene un constructor sin parámetros. Esto sucede a menudo en clases donde solo se ha declarado explícitamente un constructor con argumentos.
Error n.º 2: campos privados sin getters/setters.
Si haces que todos los campos sean privados pero olvidas añadir los getters y setters, Jackson no podrá rellenarlos durante la deserialización (por defecto).
Error n.º 3: no coinciden los nombres de los campos.
Cuando el nombre del campo en JSON difiere del nombre del campo/getter en Java, Jackson no encontrará correspondencia. Utiliza @JsonProperty.
Error n.º 4: formato de fecha incorrecto.
Si el formato de la fecha en el JSON no coincide con el formato esperado en Java, Jackson lanzará un error de análisis. Utiliza @JsonFormat para configurarlo.
Error n.º 5: intentar serializar campos con la anotación @JsonIgnore.
Esos campos no aparecerán en el JSON — no es un bug, es una característica.
Error n.º 6: serializar/deserializar colecciones sin indicar el tipo.
Si no usas TypeReference, Jackson no entenderá de qué tipo son los objetos dentro de la colección.
Error n.º 7: excepción al leer/escribir un archivo.
No olvides manejar IOException al trabajar con archivos.
GO TO FULL VERSION