Contactez-nous

Sérialisation et désérialisation JSON/XML (Jackson/JAXB)

Comprenez comment Spring Boot convertit automatiquement les objets Java en JSON/XML (sérialisation) et vice-versa (désérialisation) grâce à Jackson et JAXB.

La magie derrière la conversion de données dans les API REST

Lorsque vous construisez une API REST avec Spring Boot, vous manipulez principalement des objets Java dans vos contrôleurs. Cependant, les clients qui consomment votre API (navigateurs, applications mobiles, autres services) communiquent généralement via des formats textuels standardisés comme JSON (JavaScript Object Notation) ou, dans certains cas, XML (eXtensible Markup Language). Le processus de conversion d'un objet Java en une représentation JSON ou XML est appelé sérialisation, tandis que le processus inverse, convertir du JSON ou XML entrant en un objet Java, est la désérialisation.

Heureusement, Spring Boot simplifie énormément ces processus grâce à son système de HttpMessageConverter. Ces convertisseurs sont responsables de lire les données du corps des requêtes HTTP entrantes et de les transformer en objets Java (désérialisation pour @RequestBody), ainsi que de prendre les objets Java retournés par vos méthodes de contrôleur et de les écrire dans le corps de la réponse HTTP (sérialisation pour les retours de méthodes dans un @RestController ou via ResponseEntity).

Spring Boot, via l'auto-configuration, détecte les bibliothèques de conversion présentes dans le classpath et enregistre automatiquement les convertisseurs correspondants. Nous allons nous concentrer sur les deux plus importantes : Jackson pour JSON (le choix par défaut) et JAXB (ou Jackson pour XML) pour XML.

Jackson : le roi de la conversion JSON (par défaut)

JSON est devenu le format de facto pour les API REST en raison de sa simplicité, de sa lisibilité et de sa facilité de traitement par JavaScript et de nombreux autres langages. Spring Boot intègre par défaut la bibliothèque Jackson, une des plus populaires et performantes pour la manipulation de JSON en Java. Lorsque vous incluez le starter spring-boot-starter-web, Jackson est automatiquement ajouté à vos dépendances et le MappingJackson2HttpMessageConverter est configuré.

Comment ça marche ? C'est presque transparent pour le développeur :

  • Désérialisation (Requête -> Objet) : Quand une méthode de contrôleur a un paramètre annoté avec @RequestBody et que la requête entrante a l'en-tête Content-Type: application/json, Spring délègue la lecture du corps JSON et sa conversion en l'objet Java spécifié (ex: UserDto) à Jackson.
  • Sérialisation (Objet -> Réponse) : Quand une méthode d'un @RestController retourne un objet (ou qu'un ResponseEntity contient un objet dans son corps) et que le client accepte du JSON (via l'en-tête Accept: application/json, souvent le défaut), Spring utilise Jackson pour convertir cet objet Java en une chaîne JSON qui sera écrite dans le corps de la réponse HTTP avec le Content-Type: application/json.

Pour que Jackson puisse fonctionner correctement, vos objets Java (POJOs ou DTOs) doivent généralement respecter certaines conventions : avoir un constructeur public sans argument et/ou des méthodes getter et setter publiques pour les champs que vous souhaitez sérialiser/désérialiser. Jackson est cependant assez flexible et peut aussi fonctionner avec des champs publics ou des constructeurs spécifiques avec les bonnes annotations.

Exemple simple (POJO et contrôleur) :

// --- Dans votre DTO --- 
package com.certiquizz.monappliboot.dto;

public class ProductDto {
    private Long id;
    private String name;
    private Double price;
    private List tags;

    // Constructeur sans argument (important pour Jackson)
    public ProductDto() { }

    // Getters et Setters (importants pour Jackson)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
    public List getTags() { return tags; }
    public void setTags(List tags) { this.tags = tags; }
}

// --- Dans votre Contrôleur --- 
package com.certiquizz.monappliboot.controller;

import com.certiquizz.monappliboot.dto.ProductDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/api/json-products")
public class JsonProductController {

    // Désérialisation: POST avec JSON -> ProductDto
    @PostMapping
    public ResponseEntity createProduct(@RequestBody ProductDto newProduct) {
        System.out.println("Produit reçu (JSON): " + newProduct.getName());
        // Simule la sauvegarde
        newProduct.setId(123L);
        return ResponseEntity.ok(newProduct);
    }

    // Sérialisation: ProductDto -> JSON en réponse
    @GetMapping("/{id}")
    public ProductDto getProduct(@PathVariable Long id) {
        // Simule la récupération
        ProductDto product = new ProductDto();
        product.setId(id);
        product.setName("Exemple Produit JSON");
        product.setPrice(99.99);
        product.setTags(Arrays.asList("json", "exemple"));
        // L'objet product sera automatiquement sérialisé en JSON
        return product;
    }
}

Personnalisation de Jackson : Vous pouvez configurer le comportement de Jackson via des propriétés dans application.properties ou application.yml. Quelques options courantes :

  • spring.jackson.serialization.indent_output=true : Indente joliment le JSON en sortie (utile en développement).
  • spring.jackson.default-property-inclusion=non_null : N'inclut pas les champs ayant une valeur null dans le JSON généré.
  • spring.jackson.date-format=yyyy-MM-dd HH:mm:ss : Définit un format standard pour la sérialisation des dates/heures.
  • spring.jackson.deserialization.fail-on-unknown-properties=false : Ignore les propriétés inconnues dans le JSON entrant au lieu de lever une erreur.

Pour des configurations plus avancées, vous pouvez définir votre propre bean ObjectMapper.

Support XML avec JAXB ou Jackson-Dataformat-XML

Bien que moins populaire que JSON pour les API REST modernes, XML est parfois requis pour des raisons d'héritage ou d'intégration avec certains systèmes. Spring Boot peut également gérer la sérialisation/désérialisation XML si les dépendances nécessaires sont présentes.

Traditionnellement, la manipulation XML en Java se faisait avec JAXB (Java Architecture for XML Binding). Cependant, JAXB a été retiré du JDK standard à partir de Java 11, il faut donc ajouter les dépendances manuellement si nécessaire. Une approche plus moderne et cohérente avec l'écosystème Jackson consiste à utiliser le module jackson-dataformat-xml. Si cette dépendance est ajoutée à votre projet, Spring Boot configurera automatiquement un MappingJackson2XmlHttpMessageConverter.



    com.fasterxml.jackson.dataformat
    jackson-dataformat-xml

Pour que la conversion XML fonctionne, vous devez généralement annoter vos POJOs/DTOs avec des annotations JAXB (même si vous utilisez `jackson-dataformat-xml`, car il les reconnaît). Les annotations de base incluent :

  • @XmlRootElement : Sur la classe, pour définir le nom de l'élément racine XML.
  • @XmlElement : Sur les champs ou les getters, pour mapper les propriétés aux éléments XML.
  • @XmlAttribute : Pour mapper une propriété à un attribut XML.

Exemple (POJO annoté et contrôleur) :

// --- Dans votre DTO --- 
package com.certiquizz.monappliboot.dto;

// Annotations JAXB pour la conversion XML
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
// ou import javax.xml.bind.annotation.*; si vous utilisez des dépendances JAXB plus anciennes

@XmlRootElement(name = "product") // Définit l'élément racine XML
public class ProductXmlDto {
    private Long id;
    private String name;
    private Double price;

    // Constructeur sans argument
    public ProductXmlDto() { }

    @XmlElement // Mappe à un élément 
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    @XmlElement // Mappe à un élément 
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @XmlElement // Mappe à un élément 
    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
}

// --- Dans votre Contrôleur --- 
package com.certiquizz.monappliboot.controller;

import com.certiquizz.monappliboot.dto.ProductXmlDto;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/xml-products")
public class XmlProductController {

    // Désérialisation: POST avec XML -> ProductXmlDto
    @PostMapping(consumes = MediaType.APPLICATION_XML_VALUE, // Accepte uniquement XML
                 produces = MediaType.APPLICATION_XML_VALUE) // Produit du XML
    public ResponseEntity createProductFromXml(@RequestBody ProductXmlDto newProduct) {
        System.out.println("Produit reçu (XML): " + newProduct.getName());
        newProduct.setId(456L);
        return ResponseEntity.ok(newProduct);
    }

    // Sérialisation: ProductXmlDto -> XML en réponse
    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_XML_VALUE) // Produit du XML
    public ProductXmlDto getProductAsXml(@PathVariable Long id) {
        ProductXmlDto product = new ProductXmlDto();
        product.setId(id);
        product.setName("Exemple Produit XML");
        product.setPrice(123.45);
        // L'objet sera automatiquement sérialisé en XML grâce aux annotations JAXB
        // et au HttpMessageConverter XML (Jackson ou JAXB)
        return product;
    }
}

Dans cet exemple, nous utilisons les attributs consumes et produces de nos annotations de mapping pour spécifier explicitement que ces méthodes travaillent avec le type de média application/xml. L'objet ProductXmlDto, annoté avec JAXB, sera correctement sérialisé et désérialisé.

Négociation de contenu : laisser le client choisir

Un aspect puissant de l'utilisation des `HttpMessageConverter` est la capacité de Spring à effectuer une négociation de contenu. Si votre API peut produire à la fois du JSON et du XML (parce que les deux convertisseurs sont configurés et que vos DTOs sont correctement annotés), le client peut indiquer son format préféré via l'en-tête HTTP Accept.

Par exemple, si une méthode de contrôleur est mappée sans produces spécifique et retourne un ProductDto (qui pourrait être sérialisé en JSON et XML) :

  • Si le client envoie Accept: application/json, Spring utilisera Jackson pour retourner du JSON.
  • Si le client envoie Accept: application/xml, Spring utilisera le convertisseur XML (Jackson ou JAXB) pour retourner du XML.
  • Si le client envoie Accept: */* ou ne spécifie pas d'en-tête Accept, Spring choisira généralement le format par défaut (JSON si Jackson est présent).

De même, l'en-tête Content-Type envoyé par le client dans sa requête (pour POST, PUT, PATCH) indique au serveur le format des données dans le corps, permettant à Spring de choisir le bon convertisseur pour la désérialisation avec @RequestBody.

Cette gestion automatique de la sérialisation, de la désérialisation et de la négociation de contenu est l'une des grandes forces de Spring Boot pour le développement d'API REST, vous permettant de vous concentrer sur la logique métier tout en offrant une flexibilité dans les formats de données échangés.