Mappage des valeurs imbriquées avec Jackson

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.

Read more

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.

Read more

Utilisation de @JsonComponent dans Spring Boot

Apprenez à utiliser l'annotation @JsonComponent dans Spring Boot.

Read more

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.