Arbejde med træmodelknudepunkter i Jackson

1. Oversigt

Denne tutorial vil fokusere på at arbejde med træmodelknudepunkter i Jackson.

Vi bruger JsonNode til forskellige konverteringer samt tilføjelse, ændring og fjernelse af noder.

2. Oprettelse af en node

Det første trin i oprettelsen af ​​en knude er at instantiere en ObjectMapper objekt ved hjælp af standardkonstruktøren:

ObjectMapper-kortlægger = ny ObjectMapper ();

Siden oprettelsen af ​​en ObjectMapper objektet er dyrt, anbefales det, at det samme genbruges til flere operationer.

Dernæst har vi tre forskellige måder at oprette en træknude på, når vi først har vores ObjectMapper.

2.1. Konstruer en node fra bunden

Den mest almindelige måde at oprette en node ud af ingenting er som følger:

JsonNode node = mapper.createObjectNode ();

Alternativt kan vi også oprette en node via JsonNodeFactory:

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

2.2. Parse fra en JSON-kilde

Denne metode er godt dækket i Jackson - Marshall String to JsonNode-artiklen. Se det, hvis du har brug for mere info.

2.3. Konverter fra et objekt

En node kan konverteres fra et Java-objekt ved at ringe til valueToTree (Objekt fromValue) metode til ObjectMapper:

JsonNode node = mapper.valueToTree (fromValue);

Det convertValue API er også nyttigt her:

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

Lad os se, hvordan det fungerer i praksis. Antag, at vi har en klasse, der hedder NodeBean:

offentlig klasse NodeBean {privat int id; privat strengnavn; offentlig NodeBean () {} offentlig NodeBean (int id, String name) {this.id = id; dette.navn = navn; } // standard getters og setters}

Lad os skrive en test, der sørger for, at konverteringen sker korrekt:

@Test offentlig ugyldighed 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 ("navn"). textValue ()); }

3. Transformering af en node

3.1. Skriv ud som JSON

Den grundlæggende metode til at omdanne en træknude til en JSON-streng er følgende:

mapper.writeValue (destination, node);

hvor destinationen kan være en Fil, en OutputStream eller a Forfatter.

Ved at genbruge klassen NodeBean erklæret i afsnit 2.3, sikrer en test, at denne metode fungerer som forventet:

endelig String pathToTestFile = "node_to_json_test.json"; @Test offentligt ugyldigt givetANode_whenModifyingIt_thenCorrect () kaster IOException {String newString = "{\" nick \ ": \" cowtowncoder \ "}"; JsonNode newNode = mapper.readTree (newString); JsonNode rootNode = ExampleStructure.getExampleRoot (); ((ObjectNode) rootNode) .set ("navn", newNode); assertFalse (rootNode.path ("navn"). sti ("nick"). erMissingNode ()); assertEquals ("cowtowncoder", rootNode.path ("navn"). sti ("nick"). textValue ()); }

3.2. Konverter til et objekt

Den mest bekvemme måde at konvertere en JsonNode i et Java-objekt er treeToValue API:

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

Hvilket funktionelt svarer til:

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

Vi kan også gøre det gennem en token stream:

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

Lad os endelig implementere en test, der verificerer konverteringsprocessen:

@Test offentlig ugyldighed givenANode_whenConvertingIntoAnObject_thenCorrect () kaster JsonProcessingException {JsonNode node = mapper.createObjectNode (); ((ObjectNode) node) .put ("id", 2016); ((ObjectNode) node) .put ("navn", "baeldung.com"); NodeBean toValue = mapper.treeToValue (node, NodeBean.class); assertEquals (2016, toValue.getId ()); assertEquals ("baeldung.com", toValue.getName ()); }

4. Manipulering af træknudepunkter

Følgende JSON-elementer indeholdt i en fil med navnet eksempel.json, bruges som en basisstruktur for handlinger, der diskuteres i dette afsnit, og som skal tages på:

{"name": {"first": "Tatu", "last": "Saloranta"}, "title": "Jackson grundlægger", "company": "FasterXML"}

Denne JSON-fil, der er placeret på klassestien, er parset i et modeltræ:

public class ExampleStructure {private static ObjectMapper mapper = new ObjectMapper (); statisk JsonNode getExampleRoot () kaster IOException {InputStream eksempelInput = ExampleStructure.class.getClassLoader () .getResourceAsStream ("eksempel.json"); JsonNode rootNode = mapper.readTree (eksempelInput); returner rootNode; }}

Bemærk, at træets rod bruges, når du illustrerer operationer på noder i de følgende underafsnit.

4.1. Lokalisering af en node

Før vi arbejder på en hvilken som helst node, er den første ting, vi skal gøre, at finde og tildele den til en variabel.

Hvis stien til noden er kendt på forhånd, er det ret nemt at gøre. Sig for eksempel, at vi vil have en node med navnet sidst, som er under navn knude:

JsonNode locatedNode = rootNode.path ("navn"). Sti ("sidste");

Alternativt kan eller med API'er kan også bruges i stedet for sti.

Hvis stien ikke er kendt, bliver søgningen naturligvis mere kompleks og iterativ.

Vi kan se et eksempel på iterering over alle knudepunkter i 5. Iterering over knudepunkterne

4.2. Tilføjelse af en ny node

En node kan tilføjes som et barn til en anden node som følger:

ObjectNode newNode = ((ObjectNode) locatedNode) .put (feltnavn, værdi);

Mange overbelastede varianter af sætte kan bruges til at tilføje nye noder af forskellige værdityper.

Mange andre lignende metoder er også tilgængelige, herunder putArray, putObject, PutPOJO, putRawValue og putNull.

Endelig - lad os se på et eksempel - hvor vi tilføjer en hel struktur til træets rodknude:

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

Her er den fulde test, der gennemgår alle disse operationer og verificerer resultaterne:

@Test offentlig ugyldighed givenANode_whenAddingIntoATree_thenCorrect () kaster IOException {JsonNode rootNode = ExampleStructure.getExampleRoot (); ObjectNode addedNode = ((ObjectNode) rootNode) .putObject ("adresse"); addedNode .put ("city", "Seattle") .put ("state", "Washington") .put ("country", "United States"); assertFalse (rootNode.path ("adresse"). erMissingNode ()); assertEquals ("Seattle", rootNode.path ("adresse"). sti ("by"). textValue ()); assertEquals ("Washington", rootNode.path ("adresse"). sti ("stat"). textValue ()); assertEquals ("USA", rootNode.path ("adresse"). sti ("land"). textValue ();}

4.3. Redigering af en node

En ObjectNode eksempel kan ændres ved at påberåbe sig sæt (String fieldName, JsonNode-værdi) metode:

JsonNode locatedNode = locatedNode.set (fieldName, værdi);

Lignende resultater kan opnås ved hjælp af erstatte eller sætAlle metoder på genstande af samme type.

For at bekræfte, at metoden fungerer som forventet, ændrer vi feltets værdi navn under rodknude fra et objekt fra først og sidst ind i en anden, der kun består af nick felt i en test:

@Test offentligt ugyldigt givetANode_whenModifyingIt_thenCorrect () kaster IOException {String newString = "{\" nick \ ": \" cowtowncoder \ "}"; JsonNode newNode = mapper.readTree (newString); JsonNode rootNode = ExampleStructure.getExampleRoot (); ((ObjectNode) rootNode) .set ("navn", newNode); assertFalse (rootNode.path ("navn"). sti ("nick"). erMissingNode ()); assertEquals ("cowtowncoder", rootNode.path ("navn"). sti ("nick"). textValue ()); }

4.4. Fjernelse af en node

En node kan fjernes ved at ringe til fjern (String fieldName) API på dets overordnede knude:

JsonNode fjernetNode = locatedNode.remove (fieldName);

For at fjerne flere noder på én gang kan vi påberåbe en overbelastet metode med parameteren Kollektion type, som returnerer den overordnede node i stedet for den, der skal fjernes:

ObjectNode locatedNode = locatedNode.remove (fieldNames);

I ekstreme tilfælde når vi vil slette alle undernoder i en given node det Fjern alt API er praktisk.

Den følgende test vil fokusere på den første metode, der er nævnt ovenfor - hvilket er det mest almindelige scenario:

@Test offentlig ugyldighed givenANode_whenRemovingFromATree_thenCorrect () kaster IOException {JsonNode rootNode = ExampleStructure.getExampleRoot (); ((ObjectNode) rootNode) .remove ("firma"); assertTrue (rootNode.path ("firma"). isMissingNode ()); }

5. Iterering over noderne

Lad os gentage alle noderne i et JSON-dokument og omformatere dem til YAML. JSON har tre typer node, som er Value, Object og Array.

Så lad os sikre, at vores eksempeldata har alle tre forskellige typer ved at tilføje en Array:

{"name": {"first": "Tatu", "last": "Saloranta"}, "title": "Jackson grundlægger", "company": "FasterXML", "pets": [{"type": "hund", "nummer": 1}, {"type": "fisk", "nummer": 50}]}

Lad os nu se det YAML, vi vil producere:

navn: første: Tatu sidste: Saloranta titel: Jackson grundlægger virksomhed: FasterXML kæledyr: - type: hundenummer: 1 - type: fisk nummer: 50

Vi ved, at JSON-noder har en hierarkisk træstruktur. Så den nemmeste måde at gentage hele JSON-dokumentet er at starte øverst og arbejde os ned gennem alle barneknudepunkter.

Vi sender rodknudepunktet til en rekursiv metode. Metoden kalder sig derefter sammen med hvert barn i den leverede node.

5.1. Test af itterationen

Vi starter med at oprette en simpel test, der kontrollerer, at vi med succes kan konvertere JSON til YAML.

Vores test leverer rodnoden til JSON-dokumentet til vores tilYaml metode og hævder, at den returnerede værdi er, hvad vi forventer:

@Test offentlig ugyldighed givenANodeTree_whenIteratingSubNodes_thenWeFindExpected () kaster IOException {JsonNode rootNode = ExampleStructure.getExampleRoot (); Streng yaml = onTest.toYaml (rootNode); assertEquals (forventet Yaml, yaml); } offentlig streng tilYaml (JsonNode-rod) {StringBuilder yaml = ny StringBuilder (); processNode (rod, yaml, 0); returner yaml.toString (); }}

5.2. Håndtering af forskellige knudetyper

Vi er nødt til at håndtere forskellige typer noder lidt forskelligt. Vi gør dette i vores procesNode metode:

privat ugyldig procesNode (JsonNode jsonNode, StringBuilder yaml, intdybde) {if (jsonNode.isValueNode ()) {yaml.append (jsonNode.asText ()); } ellers hvis (jsonNode.isArray ()) {for (JsonNode arrayItem: jsonNode) {appendNodeToYaml (arrayItem, yaml, depth, true); }} ellers hvis (jsonNode.isObject ()) {appendNodeToYaml (jsonNode, yaml, dybde, falsk); }}

Lad os først overveje en værdiknude. Vi kalder simpelthen som tekst metode til noden for at få en Snor repræsentation af værdien.

Lad os derefter se på en Array-node. Hvert element i Array-noden er i sig selv a JsonNode, så vi gentager over Array og sender hver node til appendNodeToYaml metode. Vi har også brug for at vide, at disse noder er en del af et array.

Desværre indeholder selve noden ikke noget, der fortæller os det, så vi sender et flag ind i vores appendNodeToYaml metode.

Endelig vil vi gentage alle barneknudepunkterne for hver objektknude. En mulighed er at bruge JsonNode.elements. Vi kan dog ikke bestemme feltnavnet fra et element, da det bare indeholder feltværdien:

Objekt {"first": "Tatu", "last": "Saloranta"} Værdi "Jackson Grundlægger" Value "FasterXML" Array [{"type": "dog", "number": 1}, {"type": "fisk", "antal": 50}]

I stedet bruger vi JsonNode.fields da dette giver os adgang til både feltnavn og værdi:

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 hvert felt tilføjer vi feltnavnet til output. Behandl derefter værdien som en undernode ved at sende den til procesNode metode:

privat ugyldigt appendNodeToYaml (JsonNode-node, StringBuilder yaml, intdybde, boolsk isArrayItem) {Iterator felter = node.fields (); boolsk isFirst = sand; while (fields.hasNext ()) {Entry jsonField = fields.next (); addFieldNameToYaml (yaml, jsonField.getKey (), dybde, isArrayItem && isFirst); processNode (jsonField.getValue (), yaml, dybde + 1); isFirst = falsk; }}

Vi kan ikke fortælle fra noden, hvor mange forfædre den har. Så vi passerer et felt kaldet dybde ind i procesNode metode til at holde styr på dette. Vi øger denne værdi hver gang vi får en undernode, så vi korrekt kan indrykke felterne i vores YAML-output:

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

Nu hvor vi har al koden på plads til at gentage over noderne og oprette YAML-output, kan vi køre vores test for at vise, at den fungerer.

6. Konklusion

Denne vejledning dækkede de almindelige API'er og scenarier for at arbejde med en træmodel i Jackson.

Og som altid kan implementeringen af ​​alle disse eksempler og kodestykker findes i over på GitHub - dette er et Maven-baseret projekt, så det skal være let at importere og køre som det er.