Werken met Tree Model Nodes in Jackson

1. Overzicht

Deze tutorial zal zich richten op het werken met boommodelknooppunten in Jackson.

We zullen gebruiken JsonNode voor verschillende conversies en voor het toevoegen, wijzigen en verwijderen van knooppunten.

2. Een knooppunt maken

De eerste stap bij het maken van een knooppunt is het instantiëren van een ObjectMapper object met behulp van de standaardconstructor:

ObjectMapper-mapper = nieuwe ObjectMapper ();

Sinds de oprichting van een ObjectMapper object is duur, het wordt aanbevolen hetzelfde te hergebruiken voor meerdere bewerkingen.

Vervolgens hebben we drie verschillende manieren om een ​​boomknooppunt te maken zodra we de onze hebben ObjectMapper.

2.1. Bouw een knooppunt vanuit het niets

De meest gebruikelijke manier om uit het niets een knooppunt te maken, is als volgt:

JsonNode-knooppunt = mapper.createObjectNode ();

Als alternatief kunnen we ook een knooppunt maken via de JsonNodeFactory:

JsonNode-knooppunt = JsonNodeFactory.instance.objectNode ();

2.2. Parseer vanuit een JSON-bron

Deze methode wordt goed behandeld in het artikel Jackson - Marshall String to JsonNode. Raadpleeg het als u meer informatie nodig heeft.

2.3. Converteren van een object

Een knooppunt kan worden geconverteerd vanuit een Java-object door de valueToTree (Object fromValue) methode op de ObjectMapper:

JsonNode-knooppunt = mapper.valueToTree (fromValue);

De convertValue API is hier ook handig:

JsonNode-knooppunt = mapper.convertValue (fromValue, JsonNode.class);

Laten we eens kijken hoe het in de praktijk werkt. Stel dat we een klasse hebben met de naam NodeBean:

openbare klasse NodeBean {privé int id; private String naam; public NodeBean () {} public NodeBean (int id, String naam) {this.id = id; this.name = naam; } // standaard getters en setters}

Laten we een test schrijven die ervoor zorgt dat de conversie correct verloopt:

@Test openbare ongeldig gegevenAnObject_whenConvertingIntoNode_thenCorrect () {NodeBean fromValue = nieuwe NodeBean (2016, "baeldung.com"); JsonNode-knooppunt = mapper.valueToTree (fromValue); assertEquals (2016, node.get ("id"). intValue ()); assertEquals ("baeldung.com", node.get ("naam"). textValue ()); }

3. Een knooppunt transformeren

3.1. Schrijf uit als JSON

De basismethode om een ​​boomknooppunt om te zetten in een JSON-string is de volgende:

mapper.writeValue (bestemming, knooppunt);

waar de bestemming een kan zijn het dossier, een OutputStream of een auteur.

Door de klas opnieuw te gebruiken NodeBean verklaard in sectie 2.3, zorgt een test ervoor dat deze methode werkt zoals verwacht:

final String pathToTestFile = "node_to_json_test.json"; @Test openbare ongeldig gegevenANode_whenModifyingIt_thenCorrect () gooit IOException {String newString = "{\" nick \ ": \" cowtowncoder \ "}"; JsonNode newNode = mapper.readTree (newString); JsonNode rootNode = ExampleStructure.getExampleRoot (); ((ObjectNode) rootNode) .set ("naam", newNode); assertFalse (rootNode.path ("naam"). pad ("nick"). isMissingNode ()); assertEquals ("cowtowncoder", rootNode.path ("name"). path ("nick"). textValue ()); }

3.2. Converteren naar een object

De handigste manier om een JsonNode in een Java-object is de treeToValue API:

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

Dat is functioneel gelijk aan:

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

We kunnen dat ook doen via een tokenstream:

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

Laten we tot slot een test uitvoeren die het conversieproces verifieert:

@Test openbare leegte gegevenANode_whenConvertingIntoAnObject_thenCorrect () gooit JsonProcessingException {JsonNode node = mapper.createObjectNode (); ((ObjectNode) node) .put ("id", 2016); ((ObjectNode) node) .put ("naam", "baeldung.com"); NodeBean toValue = mapper.treeToValue (node, NodeBean.class); assertEquals (2016, toValue.getId ()); assertEquals ("baeldung.com", toValue.getName ()); }

4. Boomknooppunten manipuleren

De volgende JSON-elementen, opgenomen in een bestand met de naam voorbeeld.json, worden gebruikt als basisstructuur voor acties die in deze sectie worden besproken en die kunnen worden genomen op:

{"name": {"first": "Tatu", "last": "Saloranta"}, "title": "Jackson oprichter", "bedrijf": "FasterXML"}

Dit JSON-bestand, dat zich op het klassenpad bevindt, wordt geparseerd in een modelboom:

openbare klasse ExampleStructure {privé statische ObjectMapper-mapper = nieuwe ObjectMapper (); statische JsonNode getExampleRoot () gooit IOException {InputStream exampleInput = ExampleStructure.class.getClassLoader () .getResourceAsStream ("example.json"); JsonNode rootNode = mapper.readTree (exampleInput); retourneer rootNode; }}

Merk op dat de wortel van de boom zal worden gebruikt bij het illustreren van bewerkingen op knooppunten in de volgende subsecties.

4.1. Een knooppunt zoeken

Voordat we aan een knooppunt gaan werken, is het eerste dat we moeten doen, het lokaliseren en toewijzen aan een variabele.

Als het pad naar het knooppunt van tevoren bekend is, is dat vrij eenvoudig te doen. Stel dat we een knooppunt met de naam willen laatste, die onder de naam knooppunt:

JsonNode locatedNode = rootNode.path ("naam"). Path ("last");

Als alternatief kan de krijgen of met API's kunnen ook worden gebruikt in plaats van pad.

Als het pad niet bekend is, wordt de zoekopdracht natuurlijk ingewikkelder en iteratief.

We kunnen een voorbeeld zien van iteratie over alle knooppunten in 5. Itereren over de knooppunten

4.2. Een nieuw knooppunt toevoegen

Een knooppunt kan als volgt worden toegevoegd als een kind van een ander knooppunt:

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

Veel overbelaste varianten van leggen kan worden gebruikt om nieuwe knooppunten van verschillende waardetypes toe te voegen.

Er zijn ook veel andere vergelijkbare methoden beschikbaar, waaronder putArray, putObject, Zet POJO, putRawValue en putNull.

Tot slot - laten we eens kijken naar een voorbeeld - waar we een hele structuur toevoegen aan het root-knooppunt van de boom:

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

Hier is de volledige test die al deze bewerkingen doorloopt en de resultaten verifieert:

@Test openbare leegte gegevenANode_whenAddingIntoATree_thenCorrect () gooit IOException {JsonNode rootNode = ExampleStructure.getExampleRoot (); ObjectNode addedNode = ((ObjectNode) rootNode) .putObject ("adres"); addedNode .put ("stad", "Seattle") .put ("staat", "Washington") .put ("land", "Verenigde Staten"); assertFalse (rootNode.path ("adres"). isMissingNode ()); assertEquals ("Seattle", rootNode.path ("adres"). pad ("stad"). textValue ()); assertEquals ("Washington", rootNode.path ("adres"). pad ("staat"). textValue ()); assertEquals ("Verenigde Staten", rootNode.path ("adres"). pad ("land"). textValue ();}

4.3. Een knooppunt bewerken

Een ObjectNode instantie kan worden gewijzigd door een beroep op set (String fieldName, JsonNode-waarde) methode:

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

Vergelijkbare resultaten kunnen worden bereikt door gebruik te maken van vervangen of setAll methoden op objecten van hetzelfde type.

Om te controleren of de methode werkt zoals verwacht, zullen we de waarde van het veld wijzigen naam onder root node van een object van eerste en laatste in een andere die alleen bestaat uit Nick veld in een test:

@Test openbare ongeldig gegevenANode_whenModifyingIt_thenCorrect () gooit IOException {String newString = "{\" nick \ ": \" cowtowncoder \ "}"; JsonNode newNode = mapper.readTree (newString); JsonNode rootNode = ExampleStructure.getExampleRoot (); ((ObjectNode) rootNode) .set ("naam", newNode); assertFalse (rootNode.path ("naam"). pad ("nick"). isMissingNode ()); assertEquals ("cowtowncoder", rootNode.path ("name"). path ("nick"). textValue ()); }

4.4. Een knooppunt verwijderen

Een knooppunt kan worden verwijderd door de remove (String fieldName) API op het bovenliggende knooppunt:

JsonNode removeNode = locatedNode.remove (fieldName);

Om meerdere knooppunten tegelijk te verwijderen, kunnen we een overbelaste methode aanroepen met de parameter van Verzameling type, dat het bovenliggende knooppunt retourneert in plaats van het te verwijderen knooppunt:

ObjectNode locatedNode = locatedNode.remove (fieldNames);

In het extreme geval wanneer we alle subknooppunten van een bepaald knooppunt willen verwijderen de Verwijder alles API is handig.

De volgende test zal zich richten op de eerste hierboven genoemde methode - het meest voorkomende scenario:

@Test openbare leegte gegevenANode_whenRemovingFromATree_thenCorrect () gooit IOException {JsonNode rootNode = ExampleStructure.getExampleRoot (); ((ObjectNode) rootNode) .remove ("bedrijf"); assertTrue (rootNode.path ("bedrijf"). isMissingNode ()); }

5. Itereren over de knooppunten

Laten we alle knooppunten in een JSON-document herhalen en ze opnieuw formatteren naar YAML. JSON heeft drie typen knooppunten, namelijk Waarde, Object en Array.

Laten we er dus voor zorgen dat onze voorbeeldgegevens alle drie de verschillende typen hebben door een Matrix:

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

Laten we nu eens kijken naar de YAML die we willen produceren:

naam: eerste: Tatu laatste: Saloranta titel: Jackson oprichter bedrijf: FasterXML huisdieren: - type: hond nummer: 1 - type: vis nummer: 50

We weten dat JSON-knooppunten een hiërarchische boomstructuur hebben. Dus de gemakkelijkste manier om het hele JSON-document te herhalen, is door bovenaan te beginnen en door alle onderliggende knooppunten naar beneden te werken.

We zullen het rootknooppunt doorgeven aan een recursieve methode. De methode zal zichzelf dan aanroepen met elk kind van het opgegeven knooppunt.

5.1. Testen van de iteratie

We beginnen met het maken van een eenvoudige test die controleert of we de JSON met succes naar YAML kunnen converteren.

Onze test levert het root-knooppunt van het JSON-document aan ons toYaml methode en beweert dat de geretourneerde waarde is wat we verwachten:

@Test openbare leegte gegevenANodeTree_whenIteratingSubNodes_thenWeFindExpected () gooit IOException {JsonNode rootNode = ExampleStructure.getExampleRoot (); String yaml = onTest.toYaml (rootNode); assertEquals (verwachtYaml, yaml); } openbare String toYaml (JsonNode root) {StringBuilder yaml = nieuwe StringBuilder (); processNode (root, yaml, 0); retourneer yaml.toString (); }}

5.2. Omgaan met verschillende soorten knooppunten

We moeten verschillende soorten knooppunten iets anders behandelen. We doen dit in onze processNode 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); }}

Laten we eerst eens kijken naar een waardeknooppunt. We noemen gewoon de asText methode van het knooppunt om een Draad vertegenwoordiging van de waarde.

Laten we vervolgens naar een Array-knooppunt kijken. Elk item binnen het Array-knooppunt is zelf een JsonNode, dus herhalen we de array en geven we elk knooppunt door aan het appendNodeToYaml methode. We moeten ook weten dat deze knooppunten deel uitmaken van een array.

Helaas bevat het knooppunt zelf niets dat ons dat vertelt, dus we zullen een vlag doorgeven aan ons appendNodeToYaml methode.

Ten slotte willen we alle onderliggende knooppunten van elk objectknooppunt herhalen. Een optie is om te gebruiken JsonNode.elements. We kunnen de veldnaam echter niet van een element bepalen, omdat het alleen de veldwaarde bevat:

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

In plaats daarvan gebruiken we JsonNode.fields omdat dit ons toegang geeft tot zowel de veldnaam als de waarde:

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

Voor elk veld voegen we de veldnaam toe aan de uitvoer. Verwerk vervolgens de waarde als een kindknooppunt door deze door te geven aan het processNode methode:

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

Aan het knooppunt kunnen we niet zien hoeveel voorouders het heeft. Dus passeren we een veld met de naam diepte in de processNode methode om dit bij te houden. We verhogen deze waarde elke keer dat we een kindknooppunt krijgen, zodat we de velden in onze YAML-uitvoer correct kunnen laten inspringen:

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

Nu we alle code hebben om de knooppunten te herhalen en de YAML-uitvoer te maken, kunnen we onze test uitvoeren om te laten zien dat het werkt.

6. Conclusie

Deze tutorial behandelde de algemene API's en scenario's van het werken met een boommodel in Jackson.

En zoals altijd is de implementatie van al deze voorbeelden en codefragmenten te vinden in over op GitHub - dit is een op Maven gebaseerd project, dus het zou gemakkelijk moeten kunnen worden geïmporteerd en uitgevoerd zoals het is.