Bekerja dengan Nod Model Pokok di Jackson

1. Gambaran keseluruhan

Tutorial ini akan memberi tumpuan untuk bekerja dengan simpul model pokok di Jackson .

Kami akan menggunakan JsonNode untuk pelbagai penukaran serta menambah, mengubah dan membuang nod.

2. Membuat Node

Langkah pertama dalam membuat simpul adalah untuk membuat objek ObjectMapper dengan menggunakan konstruktor lalai:

ObjectMapper mapper = new ObjectMapper();

Oleh kerana penciptaan objek ObjectMapper itu mahal, disarankan agar objek yang sama digunakan semula untuk beberapa operasi.

Seterusnya, kita mempunyai tiga cara yang berbeza untuk membuat simpul pokok setelah kita mempunyai ObjectMapper kita .

2.1. Bina Node dari Gores

Kaedah yang paling biasa untuk membuat node tanpa apa-apa adalah seperti berikut:

JsonNode node = mapper.createObjectNode();

Sebagai alternatif, kita juga boleh membuat nod melalui JsonNodeFactory :

JsonNode node = JsonNodeFactory.instance.objectNode();

2.2. Dihuraikan dari Sumber JSON

Kaedah ini dibahas dengan baik dalam artikel Jackson - Marshall String to JsonNode. Silakan merujuknya jika anda memerlukan lebih banyak maklumat.

2.3. Tukar dari Objek

Node boleh ditukarkan dari objek Java dengan memanggil kaedah valueToTree (Object fromValue) pada ObjectMapper :

JsonNode node = mapper.valueToTree(fromValue);

The convertValue API juga membantu di sini:

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

Mari kita lihat bagaimana ia berfungsi dalam praktik. Andaikan kita mempunyai kelas bernama 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 }

Mari tulis ujian yang memastikan bahawa penukaran berlaku dengan betul:

@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. Mengubah Node

3.1. Tulis sebagai JSON

Kaedah asas untuk mengubah simpul pokok menjadi rentetan JSON adalah seperti berikut:

mapper.writeValue(destination, node);

di mana tujuannya boleh menjadi File , OutputStream atau Writer .

Dengan menggunakan semula kelas NodeBean yang dinyatakan dalam bahagian 2.3, ujian memastikan kaedah ini berfungsi seperti yang diharapkan:

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. Tukar ke Objek

Cara paling mudah untuk menukar JsonNode menjadi objek Java adalah treeToValue API:

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

Fungsinya setara dengan:

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

Kami juga boleh melakukannya melalui aliran token:

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

Akhirnya, mari kita laksanakan ujian yang mengesahkan proses penukaran:

@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. Memanipulasi Nod Pokok

Elemen JSON berikut, yang terdapat dalam file bernama example.json , digunakan sebagai struktur dasar agar tindakan yang dibahas dalam bahagian ini dapat diambil:

{ "name": { "first": "Tatu", "last": "Saloranta" }, "title": "Jackson founder", "company": "FasterXML" }

Fail JSON ini, terletak di classpath, diuraikan ke dalam pohon model:

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

Perhatikan bahawa akar pokok akan digunakan ketika menggambarkan operasi pada nod dalam sub-bahagian berikut.

4.1. Mencari Node

Sebelum mengerjakan sebarang nod, perkara pertama yang perlu kita lakukan ialah mencari dan memberikannya kepada pemboleh ubah.

Sekiranya jalan ke nod diketahui sebelumnya, itu cukup mudah dilakukan. Sebagai contoh, katakan kita mahukan nod yang diberi nama terakhir , yang berada di bawah simpul nama :

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

Sebagai alternatif, get atau dengan API juga dapat digunakan sebagai ganti jalan .

Sekiranya jalannya tidak diketahui, tentunya pencarian akan menjadi lebih kompleks dan berulang.

Kita dapat melihat contoh lelaran ke atas semua nod di 5. Iterating Over the Nodes

4.2. Menambah Nod Baru

Node boleh ditambahkan sebagai turunan nod lain seperti berikut:

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

Banyak varian put yang terlalu banyak digunakan untuk menambah simpul baru dari pelbagai jenis nilai.

Banyak kaedah lain yang serupa juga tersedia, termasuk putArray , putObject , PutPOJO , putRawValue dan putNull .

Akhirnya - mari kita lihat contoh - di mana kita menambah keseluruhan struktur ke simpul akar pokok:

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

Inilah ujian penuh yang menjalani semua operasi ini dan mengesahkan hasilnya:

@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. Menyunting Node

Contoh ObjectNode boleh diubah dengan kaedah set (String fieldName, nilai JsonNode) :

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

Hasil yang serupa mungkin dicapai dengan menggunakan kaedah ganti atau setAll pada objek dengan jenis yang sama.

Untuk mengesahkan bahawa kaedah ini berfungsi seperti yang diharapkan, kami akan mengubah nilai nama bidang di bawah simpul root dari objek yang pertama dan terakhir menjadi yang lain yang hanya terdiri dari bidang nick dalam ujian:

@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. Mengeluarkan Node

Node boleh dikeluarkan dengan memanggil API remove (String fieldName) pada nod induknya:

JsonNode removedNode = locatedNode.remove(fieldName);

Untuk membuang beberapa node sekaligus, kita boleh menggunakan kaedah yang terlalu banyak dengan parameter jenis Koleksi , yang mengembalikan nod induk dan bukan yang akan dikeluarkan:

ObjectNode locatedNode = locatedNode.remove(fieldNames);

Dalam kes yang melampau apabila kita hendak memadam semua subnodes nod tertentu - yang removeAll API datang dalam berguna.

Ujian berikut akan menumpukan pada kaedah pertama yang disebutkan di atas - yang merupakan senario yang paling biasa:

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

5. Mengulangi Node

Mari ulangi semua nod dalam dokumen JSON dan bentuk semula menjadi YAML. JSON mempunyai tiga jenis nod, iaitu Nilai, Objek, dan Array.

Oleh itu, mari kita pastikan data sampel kita mempunyai ketiga-tiga jenis dengan menambahkan Array:

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

Sekarang, mari kita lihat YAML yang ingin kita hasilkan:

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

Kami tahu bahawa nod JSON mempunyai struktur pokok hierarki. Oleh itu, cara termudah untuk mengulangi keseluruhan dokumen JSON adalah bermula dari bahagian atas dan selesaikan semua nod anak.

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.

Dan, seperti biasa, pelaksanaan semua contoh dan coretan kod ini dapat dijumpai di GitHub - ini adalah projek berasaskan Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.