Utilisation des noeuds de modèle d’arbre à Jackson

Travailler avec des noeuds de modèle d'arbre à Jackson

1. Vue d'ensemble

Ce tutoriel se concentrera sur l'utilisation detree model nodes in Jackson.

Nous utiliseronsJsonNode pour diverses conversions ainsi que pour ajouter, modifier et supprimer des nœuds.

2. Créer un nœud

La première étape de la création d'un nœud consiste à instancier un objetObjectMapper en utilisant le constructeur par défaut:

ObjectMapper mapper = new ObjectMapper();

La création d’un objetObjectMapper étant onéreuse, il est recommandé de réutiliser le même objet pour plusieurs opérations.

Ensuite, nous avons trois façons différentes de créer un nœud d'arbre une fois que nous avons nosObjectMapper.

2.1. Construire un nœud à partir de zéro

La manière la plus courante de créer un nœud à partir de rien est la suivante:

JsonNode node = mapper.createObjectNode();

Alternativement, nous pouvons également créer un nœud via lesJsonNodeFactory:

JsonNode node = JsonNodeFactory.instance.objectNode();

2.2. Analyser à partir d'une source JSON

Cette méthode est bien couverte dans l'article deJackson – Marshall String to JsonNode. S'il vous plaît se référer si vous avez besoin de plus d'informations.

2.3. Convertir à partir d'un objet

Un nœud peut être converti à partir d'un objet Java en appelant la méthodevalueToTree(Object fromValue) sur lesObjectMapper:

JsonNode node = mapper.valueToTree(fromValue);

L'APIconvertValue est également utile ici:

JsonNode node = mapper.convertValue(fromValue, JsonNode.class);

Voyons comment cela fonctionne dans la pratique. Supposons que nous ayons une classe nomméeNodeBean:

public class NodeBean {
    private int id;
    private String name;

    public NodeBean() {
    }

    public NodeBean(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // standard getters and setters
}

Écrivons un test pour nous assurer que la conversion se déroule correctement:

@Test
public void givenAnObject_whenConvertingIntoNode_thenCorrect() {
    NodeBean fromValue = new NodeBean(2016, "example.com");

    JsonNode node = mapper.valueToTree(fromValue);

    assertEquals(2016, node.get("id").intValue());
    assertEquals("example.com", node.get("name").textValue());
}

3. Transformer un nœud

3.1. Écrire en JSON

La méthode de base pour transformer un noeud d'arborescence en une chaîne JSON est la suivante:

mapper.writeValue(destination, node);

où la destination peut être unFile, unOutputStream ou unWriter. En réutilisant la classeNodeBean déclarée dans la section 2.3, un test s'assure que cette méthode fonctionne comme prévu:

final String pathToTestFile = "node_to_json_test.json";

@Test
public void givenANode_whenModifyingIt_thenCorrect() throws IOException {
    String newString = "{\"nick\": \"cowtowncoder\"}";
    JsonNode newNode = mapper.readTree(newString);

    JsonNode rootNode = ExampleStructure.getExampleRoot();
    ((ObjectNode) rootNode).set("name", newNode);

    assertFalse(rootNode.path("name").path("nick").isMissingNode());
    assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue());
}

3.2. Convertir en objet

Le moyen le plus pratique de convertir unJsonNode en objet Java est l'APItreeToValue:

NodeBean toValue = mapper.treeToValue(node, NodeBean.class);

Ce qui est fonctionnellement équivalent à:

NodeBean toValue = mapper.convertValue(node, NodeBean.class)

Nous pouvons également le faire via un flux de jetons:

JsonParser parser = mapper.treeAsTokens(node);
NodeBean toValue = mapper.readValue(parser, NodeBean.class);

Enfin, mettons en œuvre un test qui vérifie le processus de conversion:

@Test
public void givenANode_whenConvertingIntoAnObject_thenCorrect()
  throws JsonProcessingException {
    JsonNode node = mapper.createObjectNode();
    ((ObjectNode) node).put("id", 2016);
    ((ObjectNode) node).put("name", "example.com");

    NodeBean toValue = mapper.treeToValue(node, NodeBean.class);

    assertEquals(2016, toValue.getId());
    assertEquals("example.com", toValue.getName());
}

4. Manipulation des nœuds d'arbre

Les éléments JSON suivants, contenus dans un fichier nomméexample.json, sont utilisés comme structure de base pour les actions décrites dans cette section à entreprendre:

{
    "name":
        {
            "first": "Tatu",
            "last": "Saloranta"
        },

    "title": "Jackson founder",
    "company": "FasterXML"
}

Ce fichier JSON, situé sur le chemin de classe, est analysé dans une arborescence du modèle:

public class ExampleStructure {
    private static ObjectMapper mapper = new ObjectMapper();

    static JsonNode getExampleRoot() throws IOException {
        InputStream exampleInput =
          ExampleStructure.class.getClassLoader()
          .getResourceAsStream("example.json");

        JsonNode rootNode = mapper.readTree(exampleInput);
        return rootNode;
    }
}

Notez que la racine de l'arborescence sera utilisée pour illustrer les opérations sur les nœuds dans les sous-sections suivantes.

4.1. Localisation d'un nœud

Avant de travailler sur un nœud, la première chose à faire est de le localiser et de l’affecter à une variable.

Si le chemin d'accès au nœud est connu à l'avance, c'est assez facile à faire. Par exemple, disons que nous voulons un nœud nommélast, qui se trouve sous le nœudname:

JsonNode locatedNode = rootNode.path("name").path("last");

Sinon, les APIget ouwith peuvent également être utilisées à la place depath.

Si le chemin n'est pas connu, la recherche deviendra bien sûr plus complexe et itérative.

Nous pouvons voir un exemple d'itération sur tous les nœuds dans5. Iterating Over the Nodes

4.2. Ajout d'un nouveau nœud

Un nœud peut être ajouté en tant qu'enfant d'un autre nœud comme suit:

ObjectNode newNode = ((ObjectNode) locatedNode).put(fieldName, value);

De nombreuses variantes surchargées deput peuvent être utilisées pour ajouter de nouveaux nœuds de différents types de valeurs.

De nombreuses autres méthodes similaires sont également disponibles, notammentputArray,putObject,PutPOJO,putRawValue etputNull.

Enfin - regardons un exemple - où nous ajoutons une structure entière au nœud racine de l’arbre:

"address":
{
    "city": "Seattle",
    "state": "Washington",
    "country": "United States"
}

Voici le test complet passant par toutes ces opérations et vérifiant les résultats:

@Test
public void givenANode_whenAddingIntoATree_thenCorrect() throws IOException {
    JsonNode rootNode = ExampleStructure.getExampleRoot();
    ObjectNode addedNode = ((ObjectNode) rootNode).putObject("address");
    addedNode
      .put("city", "Seattle")
      .put("state", "Washington")
      .put("country", "United States");

    assertFalse(rootNode.path("address").isMissingNode());

    assertEquals("Seattle", rootNode.path("address").path("city").textValue());
    assertEquals("Washington", rootNode.path("address").path("state").textValue());
    assertEquals(
      "United States", rootNode.path("address").path("country").textValue();
}

4.3. Modifier un nœud

Une instanceObjectNode peut être modifiée en appelant la méthodeset(String fieldName, JsonNode value):

JsonNode locatedNode = locatedNode.set(fieldName, value);

Des résultats similaires peuvent être obtenus en utilisant les méthodesreplace ousetAll sur des objets du même type.

Pour vérifier que la méthode fonctionne comme prévu, nous allons changer la valeur du champname sous le nœud racine d'un objet defirst etlast en un autre composé uniquement denickchamp s dans un test:

@Test
public void givenANode_whenModifyingIt_thenCorrect() throws IOException {
    String newString = "{\"nick\": \"cowtowncoder\"}";
    JsonNode newNode = mapper.readTree(newString);

    JsonNode rootNode = ExampleStructure.getExampleRoot();
    ((ObjectNode) rootNode).set("name", newNode);

    assertFalse(rootNode.path("name").path("nick").isMissingNode());
    assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue());
}

4.4. Suppression d'un nœud

Un nœud peut être supprimé en appelant l'APIremove(String fieldName) sur son nœud parent:

JsonNode removedNode = locatedNode.remove(fieldName);

Afin de supprimer plusieurs nœuds à la fois, nous pouvons invoquer une méthode surchargée avec le paramètre de typeCollection<String>, qui retourne le nœud parent au lieu de celui à supprimer:

ObjectNode locatedNode = locatedNode.remove(fieldNames);

Dans le cas extrême où nous voulons supprimer tous les sous-nœuds d'un nœud donné, l'APIremoveAll est pratique.

Le test suivant portera sur la première méthode mentionnée ci-dessus - le scénario le plus courant:

@Test
public void givenANode_whenRemovingFromATree_thenCorrect() throws IOException {
    JsonNode rootNode = ExampleStructure.getExampleRoot();
    ((ObjectNode) rootNode).remove("company");

    assertTrue(rootNode.path("company").isMissingNode());
}

5. Itération sur les nœuds

Répétons tous les nœuds d'un document JSON et reformatons-les en YAML. JSON comporte trois types de nœuds, à savoir Value, Object et Array.

Alors, assurons-nous que nos exemples de données ont les trois types différents en ajoutant unArray:

{
    "name":
        {
            "first": "Tatu",
            "last": "Saloranta"
        },

    "title": "Jackson founder",
    "company": "FasterXML",
    "pets" : [
        {
            "type": "dog",
            "number": 1
        },
        {
            "type": "fish",
            "number": 50
        }
    ]
}

Voyons maintenant le YAML que nous voulons produire:

name:
  first: Tatu
  last: Saloranta
title: Jackson founder
company: FasterXML
pets:
- type: dog
  number: 1
- type: fish
  number: 50

Nous savons que les nœuds JSON ont une structure arborescente hiérarchique. Ainsi, le moyen le plus simple de parcourir l'ensemble du document JSON consiste à commencer par le haut, puis à parcourir tous les nœuds enfants.

Nous allons passer le nœud racine dans une méthode récursive. La méthode s’appellera alors avec chaque enfant du noeud fourni.

5.1. Test de l'itération

Nous allons commencer par créer un test simple qui vérifie que nous pouvons réussir à convertir le JSON en YAML.

Notre test fournit le nœud racine du document JSON à notre méthodetoYaml et affirme que la valeur retournée est ce que nous attendons:

@Test
public void givenANodeTree_whenIteratingSubNodes_thenWeFindExpected() throws IOException {
    JsonNode rootNode = ExampleStructure.getExampleRoot();

    String yaml = onTest.toYaml(rootNode);

    assertEquals(expectedYaml, yaml);
}

public String toYaml(JsonNode root) {
    StringBuilder yaml = new StringBuilder();
    processNode(root, yaml, 0);
    return yaml.toString(); }
}

5.2. Gestion de différents types de nœuds

Nous devons gérer différents types de nœuds de manière légèrement différente. Nous allons le faire dans notre méthodeprocessNode:

private void processNode(JsonNode jsonNode, StringBuilder yaml, int depth) {
    if (jsonNode.isValueNode()) {
        yaml.append(jsonNode.asText());
    }
    else if (jsonNode.isArray()) {
        for (JsonNode arrayItem : jsonNode) {
            appendNodeToYaml(arrayItem, yaml, depth, true);
        }
    }
    else if (jsonNode.isObject()) {
        appendNodeToYaml(jsonNode, yaml, depth, false);
    }
}

Tout d'abord, considérons un nœud de valeur. Nous appelons simplement la méthodeasText du nœud pour obtenir une représentationString de la valeur.

Examinons ensuite un nœud Array. Chaque élément du nœud Array est lui-même unJsonNode, donc nous parcourons le Array et passons chaque nœud à la méthodeappendNodeToYaml. Nous devons également savoir que ces nœuds font partie d'un tableau.

Malheureusement, le nœud lui-même ne contient rien qui nous dise cela, nous allons donc passer un drapeau dans notre méthodeappendNodeToYaml.

Enfin, nous souhaitons parcourir tous les nœuds enfants de chaque nœud Object. Une option consiste à utiliserJsonNode.elements. Cependant, nous ne pouvons pas déterminer le nom du champ à partir d'un élément car il contient uniquement la valeur du champ:

Object  {"first": "Tatu", "last": "Saloranta"}
Value  "Jackson Founder"
Value  "FasterXML"
Array  [{"type": "dog", "number": 1},{"type": "fish", "number": 50}]

À la place, nous utiliseronsJsonNode.fields car cela nous donne accès au nom et à la valeur du champ:

Key="name", Value=Object  {"first": "Tatu", "last": "Saloranta"}
Key="title", Value=Value  "Jackson Founder"
Key="company", Value=Value  "FasterXML"
Key="pets", Value=Array  [{"type": "dog", "number": 1},{"type": "fish", "number": 50}]

Pour chaque champ, nous ajoutons le nom du champ à la sortie. Traitez ensuite la valeur en tant que nœud enfant en la passant à la méthodeprocessNode:

private void appendNodeToYaml(JsonNode node, StringBuilder yaml, int depth, boolean isArrayItem) {
    Iterator> fields = node.fields();
    boolean isFirst = true;
    while (fields.hasNext()) {
        Entry jsonField = fields.next();
        addFieldNameToYaml(yaml, jsonField.getKey(), depth, isArrayItem && isFirst);
        processNode(jsonField.getValue(), yaml, depth+1);
        isFirst = false;
    }

}

Nous ne pouvons pas dire à partir du nœud combien d’ancêtres il a. Nous passons donc un champ appelé profondeur dans la méthodeprocessNode pour garder une trace de cela. Nous incrémentons cette valeur chaque fois que nous obtenons un nœud enfant afin que nous puissions indenter correctement les champs dans notre sortie YAML:

private void addFieldNameToYaml(
  StringBuilder yaml, String fieldName, int depth, boolean isFirstInArray) {
    if (yaml.length()>0) {
        yaml.append("\n");
        int requiredDepth = (isFirstInArray) ? depth-1 : depth;
        for(int i = 0; i < requiredDepth; i++) {
            yaml.append("  ");
        }
        if (isFirstInArray) {
            yaml.append("- ");
        }
    }
    yaml.append(fieldName);
    yaml.append(": ");
}

Maintenant que nous avons tout le code en place pour parcourir les nœuds et créer la sortie YAML, nous pouvons exécuter notre test pour montrer que cela fonctionne.

6. Conclusion

Ce tutoriel couvrait les API communes et les scénarios d'utilisation d'un modèle d'arborescence dans Jackson.

Et, comme toujours, l'implémentation de tous ces exemples et extraits de code peut être trouvée dansover on GitHub  - c'est un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.