Cartographie des valeurs imbriquées avec Jackson
1. Vue d'ensemble
Un cas typique d'utilisation de JSON consiste à effectuer une transformation d'un modèle à un autre. Par exemple, nous pourrions vouloir analyser un graphe d'objet complexe et densément imbriqué dans un modèle plus simple à utiliser dans un autre domaine.
Dans cet article rapide, nous allons examinerhow to map nested values with Jackson pour aplatir une structure de données complexe. Nous désérialiserons JSON de trois manières différentes:
-
Utilisation de@JsonProperty
-
Utilisation deJsonNode
-
Utilisation d'unJsonDeserializer personnalisé
Lectures complémentaires:
Utilisation de facultatif avec Jackson
Un aperçu rapide de la manière dont nous pouvons utiliser l’option en option avec Jackson.
Héritage avec Jackson
Ce tutoriel montrera comment gérer l'inclusion de métadonnées de sous-type et ignorer les propriétés héritées des super-classes avec Jackson.
Utilisation de @JsonComponent dans Spring Boot
Apprenez à utiliser l'annotation @JsonComponent dans Spring Boot.
2. Dépendance Maven
Ajoutons d'abord la dépendance suivante àpom.xml:
com.fasterxml.jackson.core
jackson-databind
2.9.4
Nous pouvons trouver les dernières versions dejackson-databind surMaven Central.
3. Source JSON
Considérez le JSON suivant comme matériau source pour nos exemples. Bien que la structure soit artificielle, notez que nous incluons des propriétés imbriquées à deux niveaux:
{
"id": "957c43f2-fa2e-42f9-bf75-6e3d5bb6960a",
"name": "The Best Product",
"brand": {
"id": "9bcd817d-0141-42e6-8f04-e5aaab0980b6",
"name": "ACME Products",
"owner": {
"id": "b21a80b1-0c09-4be3-9ebd-ea3653511c13",
"name": "Ultimate Corp, Inc."
}
}
}
4. Modèle de domaine simplifié
Dans un modèle de domaine aplati décrit par la classeProduct ci-dessous, nous extraironsbrandName, qui est imbriqué à un niveau dans notre JSON source. Nous allons également extraireownerName, qui est imbriqué à deux niveaux de profondeur et dans l’objetbrand imbriqué:
public class Product {
private String id;
private String name;
private String brandName;
private String ownerName;
// standard getters and setters
}
5. Cartographie avec annotations
Pour mapper la propriétébrandName imbriquée, nous devons d'abord décompresser l'objetbrand imbriqué en unMap et extraire la propriéténame. Ensuite, pour mapperownerName, nous décompressons l'objetowner imbriqué en unMap et extrayons sa propriéténame.
Nous pouvons indiquer à Jackson lesunpack the nested property by using a combination of @JsonProperty and some custom logic que nous ajoutons à notre classeProduct:
public class Product {
// ...
@SuppressWarnings("unchecked")
@JsonProperty("brand")
private void unpackNested(Map brand) {
this.brandName = (String)brand.get("name");
Map owner = (Map)brand.get("owner");
this.ownerName = owner.get("name");
}
}
Notre code client peut désormais utiliser unObjectMapper pour transformer notre source JSON, qui existe en tant que constanteStringSOURCE_JSON dans la classe de test:
@Test
public void whenUsingAnnotations_thenOk() throws IOException {
Product product = new ObjectMapper()
.readerFor(Product.class)
.readValue(SOURCE_JSON);
assertEquals(product.getName(), "The Best Product");
assertEquals(product.getBrandName(), "ACME Products");
assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}
6. Cartographie avecJsonNode
Le mappage d'une structure de données imbriquée avecJsonNode nécessite un peu plus de travail. Ici, nous utilisons lesreadTree deObjectMapper pour analyser les champs souhaités:
@Test
public void whenUsingJsonNode_thenOk() throws IOException {
JsonNode productNode = new ObjectMapper().readTree(SOURCE_JSON);
Product product = new Product();
product.setId(productNode.get("id").textValue());
product.setName(productNode.get("name").textValue());
product.setBrandName(productNode.get("brand")
.get("name").textValue());
product.setOwnerName(productNode.get("brand")
.get("owner").get("name").textValue());
assertEquals(product.getName(), "The Best Product");
assertEquals(product.getBrandName(), "ACME Products");
assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}
7. Mappage avec desJsonDeserializer personnalisés
Le mappage d'une structure de données imbriquée avec unJsonDeserializer personnalisé est identique à l'approcheJsonNode du point de vue de l'implémentation. Nous créons d'abord lesJsonDeserializer:
public class ProductDeserializer extends StdDeserializer {
public ProductDeserializer() {
this(null);
}
public ProductDeserializer(Class> vc) {
super(vc);
}
@Override
public Product deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode productNode = jp.getCodec().readTree(jp);
Product product = new Product();
product.setId(productNode.get("id").textValue());
product.setName(productNode.get("name").textValue());
product.setBrandName(productNode.get("brand")
.get("name").textValue());
product.setOwnerName(productNode.get("brand").get("owner")
.get("name").textValue());
return product;
}
}
7.1. Enregistrement manuel du désérialiseur
Pour enregistrer manuellement notre désérialiseur personnalisé, notre code client doit ajouter leJsonDeserializer à unModule, enregistrer leModule avec unObjectMapper et appelerreadValue:
@Test
public void whenUsingDeserializerManuallyRegistered_thenOk()
throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Product.class, new ProductDeserializer());
mapper.registerModule(module);
Product product = mapper.readValue(SOURCE_JSON, Product.class);
assertEquals(product.getName(), "The Best Product");
assertEquals(product.getBrandName(), "ACME Products");
assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}
7.2. Enregistrement automatique du désérialiseur
Comme alternative à l'enregistrement manuel desJsonDeserializer,, nous pouvonsregister the deserializer directly on the class:
@JsonDeserialize(using = ProductDeserializer.class)
public class Product {
// ...
}
Avec cette approche, il n'est pas nécessaire de s'inscrire manuellement. Jetons un coup d'œil à notre code client en utilisant l'inscription automatique:
@Test
public void whenUsingDeserializerAutoRegistered_thenOk()
throws IOException {
ObjectMapper mapper = new ObjectMapper();
Product product = mapper.readValue(SOURCE_JSON, Product.class);
assertEquals(product.getName(), "The Best Product");
assertEquals(product.getBrandName(), "ACME Products");
assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}
8. Conclusion
Dans ce didacticiel, nous avons montré plusieurs façons d'utiliserJackson to parse JSON containing nested values. Jetez un œil à notre page principaleJackson Tutorial pour plus d'exemples.
Et, comme toujours, des extraits de code peuvent être trouvésover on GitHub.