Arbeiten mit Baummodellknoten in Jackson

1. Übersicht

Dieses Tutorial konzentriert sich auf die Arbeit mit Baummodellknoten in Jackson .

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

2. Erstellen eines Knotens

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

ObjectMapper mapper = new ObjectMapper();

Da die Erstellung eines ObjectMapper- 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 unseren ObjectMapper haben .

2.1. Erstellen Sie einen Knoten von Grund auf neu

Der häufigste Weg, einen Knoten aus dem Nichts zu erstellen, ist folgender:

JsonNode node = mapper.createObjectNode();

Alternativ können wir auch einen Knoten über die JsonNodeFactory erstellen :

JsonNode node = JsonNodeFactory.instance.objectNode();

2.2. Analysieren aus einer JSON-Quelle

Diese Methode wird im Artikel Jackson - Marshall String to JsonNode ausführlich behandelt. Bitte beziehen Sie sich darauf, wenn Sie weitere Informationen benötigen.

2.3. Von einem Objekt konvertieren

Ein Knoten kann durch den Aufruf der von einem Java - Objekt umgewandelt werden valueToTree (Object fromValue) Methode auf dem ObjectMapper :

JsonNode node = mapper.valueToTree(fromValue);

Die convertValue- 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 namens NodeBean :

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, "baeldung.com"); JsonNode node = mapper.valueToTree(fromValue); assertEquals(2016, node.get("id").intValue()); assertEquals("baeldung.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);

Dabei kann das Ziel eine Datei , ein OutputStream oder ein Writer sein .

Durch die Wiederverwendung der in Abschnitt 2.3 deklarierten Klasse NodeBean 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

Die bequemste Möglichkeit, einen JsonNode in ein Java-Objekt zu konvertieren, ist die treeToValue- 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", "baeldung.com"); NodeBean toValue = mapper.treeToValue(node, NodeBean.class); assertEquals(2016, toValue.getId()); assertEquals("baeldung.com", toValue.getName()); }

4. Bearbeiten von Baumknoten

Die folgenden JSON-Elemente, die in einer Datei mit dem Namen example.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 einem Modellbaum analysiert:

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 zunächst suchen 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 Namen last , der sich unter dem Namen node befindet:

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

Alternativ kann anstelle des Pfads auch die API get oder with verwendet werden .

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

In 5 sehen wir ein Beispiel für das Iterieren über alle Knoten. Iterieren über die Knoten

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 Put- Varianten können verwendet werden, um neue Knoten mit unterschiedlichen Werttypen hinzuzufügen.

Viele andere ähnliche Methoden sind ebenfalls verfügbar, einschließlich putArray , putObject , PutPOJO , putRawValue und putNull .

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

Eine ObjectNode- Instanz kann durch Aufrufen der Methode set (String fieldName, JsonNode-Wert) geändert werden :

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

Similar results might be achieved by using replace or setAll methods on objects of the same type.

To verify that the method works as expected, we will change the value of the field name under root node from an object of first and last into another one consisting of only nick field in a 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. Removing a Node

A node can be removed by calling the remove(String fieldName) API on its parent node:

JsonNode removedNode = locatedNode.remove(fieldName);

In order to remove multiple nodes at once, we can invoke an overloaded method with the parameter of Collection type, which returns the parent node instead of the one to be removed:

ObjectNode locatedNode = locatedNode.remove(fieldNames);

In the extreme case when we want to delete all subnodes of a given node the removeAll API comes in handy.

The following test will focus on the first method mentioned above – which is the most common scenario:

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

5. Iterating Over the Nodes

Let's iterate over all the nodes in a JSON document and reformat them into YAML. JSON has three types of node, which are Value, Object, and Array.

So, let's ensure our sample data has all three different types by adding an Array:

{ "name": { "first": "Tatu", "last": "Saloranta" }, "title": "Jackson founder", "company": "FasterXML", "pets" : [ { "type": "dog", "number": 1 }, { "type": "fish", "number": 50 } ] }

Now, let's see the YAML we want to produce:

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

We know that JSON nodes have a hierarchical tree structure. So, the easiest way to iterate over the whole JSON document is to start at the top and work our way down through all the child nodes.

We'll pass the root node into a recursive method. The method will then call itself with each child of the supplied node.

5.1. Testing the Iteration

We'll start by creating a simple test that checks that we can successfully convert the JSON to YAML.

Our test supplies the root node of the JSON document to our toYaml method and asserts the returned value is what we expect:

@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. Handling Different Node Types

We need to handle different types of node slightly differently. We'll do this in our processNode method:

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

First, let's consider a Value node. We simply call the asText method of the node to get a String representation of the value.

Next, let's look at an Array node. Each item within the Array node is itself a JsonNode, so we iterate over the Array and pass each node to the appendNodeToYaml method. We also need to know that these nodes are part of an array.

Unfortunately, the node itself does not contain anything that tells us that, so we'll pass a flag into our appendNodeToYaml method.

Finally, we want to iterate over all the child nodes of each Object node. One option is to use JsonNode.elements. However, we can't determine the field name from an element as it just contains the field value:

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

Instead, we'll use JsonNode.fields as this gives us access to both the field name and value:

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

For each field, we add the field name to the output. Then process the value as a child node by passing it to the processNode method:

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

We can't tell from the node how many ancestors it has. So we pass a field called depth into the processNode method to keep track of this. We increment this value each time we get a child node so that we can correctly indent the fields in our YAML output:

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

Now that we have all the code in place to iterate over the nodes and create the YAML output, we can run our test to show that it works.

6. Conclusion

This tutorial covered the common APIs and scenarios of working with a tree model in Jackson.

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