1. Introduction à Jackson
L’échange de données entre une application Java et le monde extérieur se résume très souvent à cette question : « Comment transformer un objet Java en JSON — et inversement ? » On peut bien sûr écrire son propre parseur avec String.split et des expressions régulières (et même y trouver un plaisir coupable), mais dans les projets réels, personne ne fait ça.
En Java, plusieurs bibliothèques populaires existent pour manipuler JSON. La principale — Jackson. Elle est tellement répandue qu’elle fait partie de Spring Boot et est utilisée par de nombreux autres frameworks et bibliothèques.
Qu’est-ce que Jackson ?
Jackson est une bibliothèque puissante et flexible pour la sérialisation (conversion d’un objet Java en JSON) et la désérialisation (l’opération inverse). Elle est composée de plusieurs modules, mais pour 90 % des cas, deux suffisent :
- jackson-core — le cœur, un parseur bas niveau.
- jackson-databind — un module haut niveau qui sait transformer des objets Java en JSON et inversement.
Tout ce que vous devez savoir pour commencer : si vous voyez la classe ObjectMapper, c’est Jackson.
Jackson est considéré comme la norme de facto pour travailler avec JSON en Java, car il allie simplicité et puissance. Pour sérialiser ou désérialiser des données, quelques lignes de code suffisent, ce qui rend la bibliothèque très accessible, même pour les débutants. Et elle ne se limite pas aux fonctionnalités de base : grâce aux annotations et à de nombreux réglages, on peut contrôler finement la façon dont les données sont transformées en objets et inversement, qu’il s’agisse de collections, d’entités imbriquées ou de dates dans différents formats.
Il est également important de noter que Jackson est très rapide et économe en ressources, ce qui est crucial pour les projets réels manipulant de gros volumes de données. Les développeurs de la bibliothèque prennent en charge les nouvelles versions de Java et les évolutions du standard JSON : Jackson reste un choix fiable, aussi bien pour des applications simples que pour de grands systèmes d’entreprise.
2. Lecture de JSON (désérialisation)
Essayons de lire une chaîne JSON et de la transformer en un objet Java. Pour cela, il nous faut :
- La classe de données (par exemple, Person)
- La classe ObjectMapper de Jackson
Ajouter Jackson
Si vous utilisez Maven, ajoutez dans pom.xml :
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
Si vous utilisez Gradle — de la même façon :
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
Exemple de classe
public class Person {
public String name;
public int age;
}
Remarque : Pour simplifier, les champs sont publics. Nous verrons plus tard comment travailler avec des champs privés et des getters/setters.
Exemple de JSON
{
"name": "Alice",
"age": 30
}
Désérialisation : transformer un JSON en objet
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
}
}
Ici, Jackson analyse la chaîne JSON, trouve les champs dont le nom correspond aux champs ou aux getters de la classe, et remplit l’objet avec les valeurs correspondantes à l’aide de readValue.
Désérialisation d’une liste d’objets
Supposons que nous ayons un tableau :
[
{ "name": "Bob", "age": 22 },
{ "name": "Eve", "age": 27 }
]
Désérialisons-le en liste :
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 + ")");
}
Vous vous demandez peut-être : pourquoi ne pas simplement écrire mapper.readValue(json, List.class) ? Rappelez-vous que les génériques en Java sont effacés à la compilation. Nous avons donc besoin de TypeReference pour que Jackson comprenne que la liste contient des objets de type Person.
3. Écriture de JSON (sérialisation)
Faisons maintenant l’opération inverse : transformons un objet Java en chaîne JSON.
Exemple
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}
Sérialisation d’une liste d’objets
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}]
Écriture dans un fichier
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
mapper.writeValue(new File("person.json"), person);
// Le fichier person.json contient désormais un objet JSON
JSON joliment formaté (pretty)
Par défaut, Jackson écrit tout sur une seule ligne. Mais il existe une variante plus « lisible » — pretty printing. Cela signifie que la chaîne JSON est produite dans un format lisible : avec des retraits, des retours à la ligne et un formatage soigné.
Contrairement au JSON « classique », souvent écrit sur une seule ligne pour la compacité, la version « jolie » est faite pour les humains — afin de parcourir facilement la structure des données dans les logs, les fichiers ou à l’écran.
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. Annotations Jackson
Jackson prend en charge de nombreuses annotations permettant de piloter la sérialisation et la désérialisation. Voici les plus utiles :
@JsonProperty
Permet d’indiquer le nom du champ dans le JSON s’il diffère de celui du champ dans la classe.
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
@JsonProperty("full_name")
public String name;
public int age;
}
{"full_name": "Olga", "age": 28}
Jackson comprendra que le champ full_name du JSON doit être écrit dans la propriété name de l’objet.
@JsonIgnore
Si vous ne souhaitez pas sérialiser ou désérialiser un champ donné :
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Person {
public String name;
@JsonIgnore
public int age;
}
Le JSON ne contiendra pas le champ age, même s’il existe dans l’objet.
@JsonInclude
Contrôle quels champs seront inclus dans le JSON. Par exemple, ne sérialiser que les champs non nuls :
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
public String name;
public Integer age;
}
Si age == null, la clé "age" ne sera pas présente dans le JSON.
@JsonFormat
Permet de définir le format de sérialisation des dates et heures.
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"}
Exemple : tout ensemble
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;
// Les getters et setters sont indispensables pour les champs privés !
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. Pratique : sérialisation et désérialisation avec annotations
Élargissons votre application d’apprentissage — nous avons maintenant une classe User avec des champs privés, une date d’inscription et un mot de passe qui ne doit pas figurer dans le 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;
// Les getters et setters sont indispensables !
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; }
}
Sérialisation
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"); // Ne sera pas dans le JSON !
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(json);
/*
{
"login" : "superuser",
"age" : 42,
"registered" : "2024-06-07"
}
*/
Désérialisation
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 (et c’est très bien !)
6. Erreurs courantes avec Jackson
Erreur n° 1 : absence de constructeur sans argument.
Jackson ne pourra pas créer un objet de la classe s’il n’y a pas de constructeur sans argument. Cela arrive souvent dans les classes où seul un constructeur avec paramètres a été déclaré.
Erreur n° 2 : champs privés sans getters/setters.
Si vous avez rendu tous les champs privés mais oublié d’ajouter des getters et setters, Jackson ne pourra pas les renseigner lors de la désérialisation (par défaut).
Erreur n° 3 : incohérence des noms de champs.
Quand le nom d’un champ dans le JSON diffère du nom du champ/getter en Java, Jackson ne trouvera pas la correspondance. Utilisez @JsonProperty.
Erreur n° 4 : format de date incorrect.
Si le format de la date dans le JSON ne correspond pas au format attendu en Java, Jackson lèvera une erreur d’analyse. Utilisez @JsonFormat pour le configurer.
Erreur n° 5 : tentative de sérialiser des champs annotés @JsonIgnore.
Ces champs ne figureront pas dans le JSON — ce n’est pas un bug, c’est une fonctionnalité.
Erreur n° 6 : sérialiser/désérialiser des collections sans indiquer le type.
Si vous n’utilisez pas TypeReference, Jackson ne saura pas de quel type sont les objets contenus dans la collection.
Erreur n° 7 : exception lors de la lecture/écriture d’un fichier.
N’oubliez pas de gérer IOException lors des opérations sur les fichiers.
GO TO FULL VERSION