Mit Baummodellknoten in Jackson arbeiten

Arbeiten mit Baummodellknoten in Jackson

1. Überblick

Dieses Tutorial konzentriert sich auf die Arbeit mittree model nodes in Jackson.

Wir verwendenJsonNode für verschiedene Konvertierungen sowie zum Hinzufügen, Ändern und Entfernen von Knoten.

2. Erstellen eines Knotens

Der erste Schritt beim Erstellen eines Knotens besteht darin, einObjectMapper-Objekt mithilfe des Standardkonstruktors zu instanziieren:

ObjectMapper mapper = new ObjectMapper();

Da die Erstellung einesObjectMapper-Objekts teuer ist, wird empfohlen, dasselbe Objekt für mehrere Vorgänge wiederzuverwenden.

Als nächstes haben wir drei verschiedene Möglichkeiten, einen Baumknoten zu erstellen, sobald wir unsereObjectMapper haben.

2.1. Erstellen Sie einen Knoten von Grund auf neu

Die gebräuchlichste Methode zum Erstellen eines Knotens aus dem Nichts lautet wie folgt:

JsonNode node = mapper.createObjectNode();

Alternativ können wir auch einen Knoten überJsonNodeFactory erstellen:

JsonNode node = JsonNodeFactory.instance.objectNode();

2.2. Analysieren aus einer JSON-Quelle

Diese Methode wird im ArtikelJackson – Marshall String to JsonNodeausführlich behandelt. Bitte beziehen Sie sich darauf, wenn Sie weitere Informationen benötigen.

2.3. Von einem Objekt konvertieren

Ein Knoten kann aus einem Java-Objekt konvertiert werden, indem die MethodevalueToTree(Object fromValue) fürObjectMapper aufgerufen wird:

JsonNode node = mapper.valueToTree(fromValue);

DieconvertValue API ist auch hier hilfreich:

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

Lassen Sie uns sehen, wie es in der Praxis funktioniert. Angenommen, wir haben eine Klasse namensNodeBean:

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
}

Schreiben wir einen Test, der sicherstellt, dass die Konvertierung korrekt erfolgt:

@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. Einen Knoten transformieren

3.1. Schreiben Sie als JSON

Die grundlegende Methode zum Transformieren eines Baumknotens in eine JSON-Zeichenfolge lautet wie folgt:

mapper.writeValue(destination, node);

wobei das Ziel einFile, einOutputStream oder einWriter sein kann. Durch die Wiederverwendung der in Abschnitt 2.3 deklarierten KlasseNodeBean stellt ein Test sicher, dass diese Methode wie erwartet funktioniert:

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. In ein Objekt konvertieren

Der bequemste Weg, einJsonNode in ein Java-Objekt zu konvertieren, ist dietreeToValue API:

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

Welches ist funktional äquivalent zu:

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

Wir können das auch über einen Token-Stream tun:

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

Lassen Sie uns abschließend einen Test implementieren, der den Konvertierungsprozess überprüft:

@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. Baumknoten bearbeiten

Die folgenden JSON-Elemente, die in einer Datei mit dem Namenexample.json enthalten sind, werden als Basisstruktur für die in diesem Abschnitt beschriebenen Aktionen verwendet:

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

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

Diese JSON-Datei im Klassenpfad wird in einen Modellbaum zerlegt:

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;
    }
}

Beachten Sie, dass die Wurzel des Baums verwendet wird, wenn Operationen an Knoten in den folgenden Unterabschnitten dargestellt werden.

4.1. Suchen eines Knotens

Bevor wir an einem Knoten arbeiten, müssen wir ihn zuerst lokalisieren und einer Variablen zuweisen.

Wenn der Pfad zum Knoten im Voraus bekannt ist, ist dies ziemlich einfach. Angenommen, wir möchten einen Knoten mit dem Namenlast, der sich unter dem Knotenname befindet:

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

Alternativ können anstelle derpath auch die APIsget oderwith verwendet werden.

Wenn der Pfad nicht bekannt ist, wird die Suche natürlich komplexer und iterativer.

Wir können ein Beispiel für die Iteration über alle Knoten in5. Iterating Over the Nodes sehen

4.2. Hinzufügen eines neuen Knotens

Ein Knoten kann wie folgt als untergeordnetes Element eines anderen Knotens hinzugefügt werden:

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

Viele überladene Varianten vonput können verwendet werden, um neue Knoten mit unterschiedlichen Werttypen hinzuzufügen.

Viele andere ähnliche Methoden sind ebenfalls verfügbar, einschließlichputArray,putObject,PutPOJO,putRawValue undputNull.

Schauen wir uns zum Schluss ein Beispiel an, in dem wir dem Wurzelknoten des Baums eine ganze Struktur hinzufügen:

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

Hier ist der vollständige Test, der alle diese Vorgänge durchläuft und die Ergebnisse überprüft:

@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. Bearbeiten eines Knotens

Die Instanz vonObjectNodekann durch Aufrufen der Methodeset(String fieldName, JsonNode value)geändert werden:

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

Ähnliche Ergebnisse können mit den Methodenreplace odersetAll für Objekte des gleichen Typs erzielt werden.

Um zu überprüfen, ob die Methode wie erwartet funktioniert, ändern wir den Wert des Feldsname unter dem Wurzelknoten von einem Objekt ausfirst undlast in ein anderes Objekt, das nur ausnickbesteht. s Feld in einem 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. Knoten entfernen

Ein Knoten kann durch Aufrufen derremove(String fieldName)-API auf seinem übergeordneten Knoten entfernt werden:

JsonNode removedNode = locatedNode.remove(fieldName);

Um mehrere Knoten gleichzeitig zu entfernen, können wir eine überladene Methode mit dem ParameterCollection<String> aufrufen, die den übergeordneten Knoten anstelle des zu entfernenden Knotens zurückgibt:

ObjectNode locatedNode = locatedNode.remove(fieldNames);

Im Extremfall, wenn wir alle Unterknoten eines bestimmten Knotens löschen möchten, ist die APIremoveAllnützlich.

Der folgende Test konzentriert sich auf die erste oben erwähnte Methode - das häufigste Szenario:

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

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

5. Iterieren über die Knoten

Lassen Sie uns alle Knoten in einem JSON-Dokument durchlaufen und sie in YAML neu formatieren. JSON verfügt über drei Knotentypen: Value, Object und Array.

Stellen wir also sicher, dass unsere Beispieldaten alle drei verschiedenen Typen haben, indem wirArray: hinzufügen

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

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

Nun sehen wir uns die YAML an, die wir produzieren möchten:

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

Wir wissen, dass JSON-Knoten eine hierarchische Baumstruktur haben. Der einfachste Weg, das gesamte JSON-Dokument zu durchlaufen, besteht darin, oben zu beginnen und uns durch alle untergeordneten Knoten zu arbeiten.

Wir übergeben den Stammknoten an eine rekursive Methode. Die Methode ruft sich dann mit jedem untergeordneten Knoten des angegebenen Knotens auf.

5.1. Testen der Iteration

Zunächst erstellen wir einen einfachen Test, mit dem überprüft wird, ob JSON erfolgreich in YAML konvertiert werden kann.

Unser Test liefert den Stammknoten des JSON-Dokuments an unseretoYaml-Methode und bestätigt, dass der zurückgegebene Wert unseren Erwartungen entspricht:

@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. Umgang mit verschiedenen Knotentypen

Wir müssen verschiedene Knotentypen leicht unterschiedlich behandeln. Wir tun dies in unsererprocessNode-Methode:

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);
    }
}

Betrachten wir zunächst einen Werteknoten. Wir rufen einfach dieasText-Methode des Knotens auf, um eineString-Darstellung des Werts zu erhalten.

Schauen wir uns als nächstes einen Array-Knoten an. Jedes Element innerhalb des Array-Knotens ist selbst einJsonNode, daher iterieren wir über das Array und übergeben jeden Knoten an dieappendNodeToYaml-Methode. Wir müssen auch wissen, dass diese Knoten Teil eines Arrays sind.

Leider enthält der Knoten selbst nichts, was uns dies sagt, sodass wir ein Flag an die MethodeappendNodeToYamlübergeben.

Schließlich möchten wir alle untergeordneten Knoten jedes Objektknotens durchlaufen. Eine Option ist die Verwendung vonJsonNode.elements. Wir können den Feldnamen jedoch nicht aus einem Element ermitteln, da er nur den Feldwert enthält:

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

Stattdessen verwenden wirJsonNode.fields, da wir dadurch sowohl auf den Feldnamen als auch auf den Wert zugreifen können:

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}]

Für jedes Feld wird der Feldname zur Ausgabe hinzugefügt. Verarbeiten Sie dann den Wert als untergeordneten Knoten, indem Sie ihn an die MethodeprocessNodeübergeben:

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;
    }

}

Wir können dem Knoten nicht entnehmen, wie viele Vorfahren er hat. Wir übergeben also ein Feld namens Tiefe an dieprocessNode-Methode, um dies zu verfolgen. Wir erhöhen diesen Wert jedes Mal, wenn wir einen untergeordneten Knoten erhalten, damit wir die Felder in unserer YAML-Ausgabe korrekt einrücken können:

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(": ");
}

Nachdem der gesamte Code vorhanden ist, um die Knoten zu durchlaufen und die YAML-Ausgabe zu erstellen, können wir unseren Test ausführen, um zu zeigen, dass er funktioniert.

6. Fazit

In diesem Tutorial wurden die gängigen APIs und Szenarien für die Arbeit mit einem Baummodell in Jackson behandelt.

Und wie immer finden Sie die Implementierung all dieser Beispiele und Codefragmente inover on GitHub  - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.