Bekerja dengan Tree Model Nodes di Jackson

1. Ikhtisar

Tutorial ini akan fokus pada bekerja dengan node model pohon di Jackson .

Kami akan menggunakan JsonNode untuk berbagai konversi serta menambahkan, memodifikasi, dan menghapus node.

2. Membuat Node

Langkah pertama dalam pembuatan node adalah membuat instance objek ObjectMapper dengan menggunakan konstruktor default:

ObjectMapper mapper = new ObjectMapper();

Karena pembuatan objek ObjectMapper mahal, disarankan agar objek yang sama digunakan kembali untuk beberapa operasi.

Selanjutnya, kami memiliki tiga cara berbeda untuk membuat simpul pohon setelah kami memiliki ObjectMapper kami .

2.1. Buat Node dari Awal

Cara paling umum untuk membuat simpul dari ketiadaan adalah sebagai berikut:

JsonNode node = mapper.createObjectNode();

Alternatifnya, kita juga bisa membuat node melalui JsonNodeFactory :

JsonNode node = JsonNodeFactory.instance.objectNode();

2.2. Parse dari Sumber JSON

Metode ini tercakup dengan baik dalam artikel Jackson - Marshall String to JsonNode. Silakan lihat jika Anda membutuhkan info lebih lanjut.

2.3. Konversi dari Objek

Node dapat diubah dari objek Java dengan memanggil metode valueToTree (Object fromValue) di ObjectMapper :

JsonNode node = mapper.valueToTree(fromValue);

The convertValue API juga membantu di sini:

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

Mari kita lihat cara kerjanya dalam praktik. Asumsikan kita memiliki 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 tes yang memastikan bahwa konversi terjadi dengan benar:

@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

Metode dasar untuk mengubah node pohon menjadi string JSON adalah sebagai berikut:

mapper.writeValue(destination, node);

di mana tujuannya bisa menjadi File , OutputStream atau Writer .

Dengan menggunakan kembali kelas NodeBean yang dideklarasikan di bagian 2.3, pengujian memastikan metode 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. Ubah menjadi Objek

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

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

Yang secara fungsional setara dengan:

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

Kami juga dapat melakukannya melalui aliran token:

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

Terakhir, mari terapkan pengujian yang memverifikasi proses konversi:

@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 Node Pohon

Elemen JSON berikut, yang terdapat dalam file bernama example.json , digunakan sebagai struktur dasar untuk tindakan yang dibahas di bagian ini:

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

File JSON ini, yang terletak di jalur kelas, diurai menjadi 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 bahwa root dari pohon akan digunakan saat menggambarkan operasi pada node di sub-bagian berikut.

4.1. Menemukan sebuah Node

Sebelum mengerjakan node apa pun, hal pertama yang perlu kita lakukan adalah mencari dan menugaskannya ke variabel.

Jika jalur ke node diketahui sebelumnya, itu cukup mudah dilakukan. Sebagai contoh, katakanlah kita menginginkan sebuah simpul bernama terakhir , yang berada di bawah simpul nama :

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

Alternatifnya, get atau with API juga bisa digunakan sebagai pengganti jalur .

Jika jalurnya tidak diketahui, pencarian tentu saja akan menjadi lebih kompleks dan berulang.

Kita dapat melihat contoh iterasi pada semua node di 5. Iterasi pada node

4.2. Menambahkan Node Baru

Sebuah node dapat ditambahkan sebagai anak dari node lain sebagai berikut:

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

Banyak varian put yang kelebihan beban dapat digunakan untuk menambahkan node baru dari jenis nilai yang berbeda.

Banyak metode serupa lainnya juga tersedia, termasuk putArray , putObject , PutPOJO , putRawValue dan putNull .

Terakhir - mari kita lihat contoh - di mana kita menambahkan seluruh struktur ke simpul akar pohon:

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

Berikut tes lengkap melalui semua operasi ini dan memverifikasi 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. Mengedit Node

Sebuah contoh ObjectNode dapat dimodifikasi dengan memanggil metode set (String fieldName, JsonNode value) :

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

Hasil serupa dapat dicapai dengan menggunakan metode replace atau setAll pada objek dengan tipe yang sama.

Untuk memverifikasi bahwa metode ini berfungsi seperti yang diharapkan, kami akan mengubah nilai nama bidang di bawah node root dari objek pertama dan terakhir menjadi objek lain yang hanya terdiri dari bidang nick dalam pengujian:

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

Sebuah node dapat dihapus dengan memanggil API remove (String fieldName) pada node induknya:

JsonNode removedNode = locatedNode.remove(fieldName);

Untuk menghapus beberapa node sekaligus, kita dapat menjalankan metode yang kelebihan beban dengan parameter tipe Collection , yang mengembalikan node induk, bukan yang akan dihapus:

ObjectNode locatedNode = locatedNode.remove(fieldNames);

Dalam kasus ekstrim ketika kita ingin menghapus semua subnodes dari node yang diberikan - yang removeAll API berguna.

Tes berikut akan fokus pada metode pertama yang disebutkan di atas - yang merupakan skenario paling umum:

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

5. Iterasi Over the Nodes

Mari kita ulangi semua node dalam dokumen JSON dan memformatnya ulang menjadi YAML. JSON memiliki tiga tipe node, yaitu Value, Object, dan Array.

Jadi, mari kita pastikan data sampel kita memiliki ketiga jenis yang berbeda 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 kami hasilkan:

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

Kita tahu bahwa node JSON memiliki struktur pohon hierarki. Jadi, cara termudah untuk mengulang seluruh dokumen JSON adalah memulai dari atas dan terus ke bawah melalui semua node turunan.

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, penerapan semua contoh dan cuplikan kode ini dapat ditemukan di lebih dari GitHub - ini adalah proyek berbasis Maven, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.