1.概述
使用JsonNode進行各種轉換以及新增,修改和刪除節點。
2.建立一個節點
建立節點的第一步是使用預設建構函式例項化ObjectMapper物件:
1 | ObjectMapper mapper = new ObjectMapper(); |
由於建立ObjectMapper物件非常昂貴,因此建議將同一物件重複用於多個操作。
接下來,一旦有了ObjectMapper,就有三種不同的方式來建立樹節點。
2.1 從頭開始構建節點
建立節點的最常見方法如下:
1 | JsonNode node = mapper.createObjectNode(); |
另外,也可以透過JsonNodeFactory建立一個節點:
1 | JsonNode node = JsonNodeFactory.instance.objectNode(); |
2.2從JSON來源解析
之前文章《JSON字串轉換為JsonNode》
2.3從物件轉換
可以透過在ObjectMapper上呼叫valueToTree(Object fromValue)方法來從Java物件轉換節點:
1 | JsonNode node = mapper.valueToTree(fromValue); |
convertValue API在這裡也很有幫助:
1 | JsonNode node = mapper.convertValue(fromValue, JsonNode.class); |
如何使用,建立NodeBean
1 2 3 4 5 6 7 | @Data @AllArgsConstructor @NoArgsConstructor public class NodeBean {<!-- --> private int id; private String name; } |
撰寫單元測試,確保轉化正確
1 2 3 4 5 | NodeBean fromValue = new NodeBean(2016, "123456.com"); ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.valueToTree(fromValue); assertEquals(2016, node.get("id").intValue()); assertEquals("123456.com", node.get("name").textValue()); |
3.轉換節點
3.1寫為JSON
將樹節點轉換為JSON字串的基本方法如下:
1 | mapper.writeValue(destination, node); |
其中目標可以是File,OutputStream或Writer。
1 2 3 4 5 6 7 | public class ExampleStructure {<!-- --> private static ObjectMapper mapper = new ObjectMapper(); public static JsonNode getExampleRoot() throws IOException {<!-- --> return mapper.createObjectNode(); } } |
1 2 3 4 5 6 7 8 9 10 11 | String newString = "{"nick": "cowtowncoder"}"; ObjectMapper mapper = new ObjectMapper(); JsonNode newNode = mapper.readTree(newString); JsonNode rootNode = ExampleStructure.getExampleRoot(); ((ObjectNode) rootNode).set("name", newNode); //{"name":{"nick":"cowtowncoder"}} System.out.println(rootNode.toString()); assertFalse(rootNode.path("name").path("nick").isMissingNode()); assertEquals("cowtowncoder", rootNode.path("name").path("nick").textValue()); |
3.2轉換為物件
將JsonNode轉換為Java物件的最方便的方法是treeToValue API:
1 | NodeBean toValue = mapper.treeToValue(node, NodeBean.class); |
在功能上等效於:
1 | NodeBean toValue = mapper.convertValue(node, NodeBean.class) |
還可以透過令牌流來做到這一點:
1 2 | JsonParser parser = mapper.treeAsTokens(node); NodeBean toValue = mapper.readValue(parser, NodeBean.class); |
範例:
1 2 3 4 5 6 7 8 9 | ObjectMapper mapper = new ObjectMapper(); ObjectNode node = mapper.createObjectNode(); node.put("id", 2016); node.put("name", "baeldung.com"); NodeBean toValue = mapper.treeToValue(node, NodeBean.class); assertEquals(2016, toValue.getId()); assertEquals("baeldung.com", toValue.getName()); |
4.操縱樹節點
json資料結構如下:
1 2 3 4 5 6 7 8 9 10 | {<!-- --> "name": {<!-- --> "first": "Tatu", "last": "Saloranta" }, "title": "Jackson founder", "company": "FasterXML" } |
4.1定位節點
在任何節點上工作之前,要做的第一件事是找到並將其分配給變數。
如果事先知道節點的路徑,那將很容易做到。 例如,想要一個名為last的節點,該節點位於名稱node下:
1 | JsonNode locatedNode = rootNode.path("name").path("last"); |
1 2 3 4 5 | String json = "{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML"}"; ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); JsonNode locatedNode = rootNode.path("name").path("last"); System.out.println(locatedNode); //Saloranta |
另外,也可以使用get或with API代替path。
4.2新增一個新節點
可以將一個節點新增為另一個節點的子節點,如下所示:
1 | ObjectNode newNode = ((ObjectNode) locatedNode).put(fieldName, value); |
put的許多過載變體可以用於新增不同實值型別的新節點。
還可以使用許多其他類似方法,包括putArray,putObject,PutPOJO,putRawValue和putNull。
最後,讓看一個範例,在該範例中,將整個結構新增到樹的根節點:
1 2 3 4 5 6 | "address": {<!-- --> "city": "Seattle", "state": "Washington", "country": "United States" } |
1 2 3 4 5 6 7 8 9 10 | String json = "{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML"}"; ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); ObjectNode addedNode = ((ObjectNode) rootNode).putObject("address"); addedNode .put("city", "Seattle") .put("state", "Washington") .put("country", "United States"); System.out.println(rootNode); //{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML","address":{"city":"Seattle","state":"Washington","country":"United States"}} |
4.3編輯節點
可以透過呼叫set(String fieldName,JsonNode value)方法來修改ObjectNode例項:
1 | JsonNode locatedNode = locatedNode.set(fieldName, value); |
透過對相同型別的物件使用replace或setAll方法,可以得到類似的結果。
1 2 3 4 5 6 7 8 9 | ObjectMapper mapper = new ObjectMapper(); String newString = "{"nick": "cowtowncoder"}"; JsonNode newNode = mapper.readTree(newString); JsonNode rootNode = mapper.createObjectNode(); ((ObjectNode) rootNode).set("name", newNode); System.out.println(rootNode);//{"name":{"nick":"cowtowncoder"}} System.out.println(rootNode.path("name").path("nick").isMissingNode()); //false System.out.println(rootNode.path("name").path("nick").textValue());//cowtowncoder |
4.4移除一個節點
可以透過在父節點上呼叫remove(String fieldName)API來刪除該節點:
1 | JsonNode removedNode = locatedNode.remove(fieldName); |
為了一次刪除多個節點,可以使用Collection 型別的引數呼叫一個過載方法,該方法將回傳父節點而不是要刪除的父節點:
1 | ObjectNode locatedNode = locatedNode.remove(fieldNames); |
範例:
1 2 3 4 5 6 | String json = "{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML"}"; ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); ((ObjectNode) rootNode).remove("company"); System.out.println(rootNode.path("company").isMissingNode());//true System.out.println(rootNode); //{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder"} |
5.遍歷節點
遍歷JSON檔案中的所有節點,並將它們重新格式化為YAML。 JSON具有三種型別的節點,分別是**Value,Object和Array**。
資料結構如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | {<!-- --> "name": {<!-- --> "first": "Tatu", "last": "Saloranta" }, //Object型別節點 "title": "Jackson founder", //Value型別節點 "company": "FasterXML", //Value型別節點 "pets" : [ //Array型別節點 {<!-- --> "type": "dog", "number": 1 }, {<!-- --> "type": "fish", "number": 50 } ] } |
生成的YAML:
1 2 3 4 5 6 7 8 9 10 | name: first: Tatu last: Saloranta title: Jackson founder company: FasterXML pets: - type: dog number: 1 - type: fish number: 50 |
JSON節點具有分層樹結構。 因此,遍歷整個JSON檔案的最簡單方法是從頂部開始,然後逐步向下遍歷所有子節點。
將根節點傳遞給遞迴方法。 然後,該方法將使用提供的節點的每個子節點呼叫自身。
5.1測試遍歷
建立一個簡單的測試開始,該測試檢查是否可以成功將JSON轉換為YAML。
將JSON檔案的根節點提供給的toYaml方法,輸出轉化結果:
1 2 3 4 5 6 7 8 9 | @Test public void test15() throws IOException {<!-- --> String json = "{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML","pets":[{"type":"dog","number":1},{"type":"fish","number":50}]}"; ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); JsonNodeIterator jsonNodeIterator = new JsonNodeIterator(); String s = jsonNodeIterator.toYaml(rootNode); System.out.println(s); } |
1 2 3 4 5 6 | //JsonNodeIterator類 public String toYaml(JsonNode root) {<!-- --> StringBuilder yaml = new StringBuilder(); processNode(root, yaml, 0); return yaml.toString(); } } |
5.2處理不同的節點型別
需要稍微不同地處理不同型態的節點。 在processNode方法中執行此操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private void processNode(JsonNode jsonNode, StringBuilder yaml, int depth) {<!-- --> //是一個value 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); } } |
首先,考慮一個Value節點。 只需呼叫節點的asText方法即可取得該值的String表示形式。
接下來,看一下Array節點。 Array節點中的每個專案本身都是JsonNode,因此需要遍歷Array並將每個節點傳遞給appendNodeToYaml方法。 還需要知道這些節點是陣列的一部分。
但是,節點本身不包含任何告訴的內容,因此需要將一個標誌傳遞給appendNodeToYaml方法。
最後,要遍歷每個Object節點的所有子節點。 一種選擇是使用JsonNode.elements。 但是,無法從元素中確定欄位名稱,因為它僅包含欄位值,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 | @Test public void test16() throws IOException {<!-- --> String json = "{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML","pets":[{"type":"dog","number":1},{"type":"fish","number":50}]}"; ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); Iterator<JsonNode> elements = rootNode.elements(); while(elements.hasNext()){<!-- --> JsonNode next = elements.next(); //緊輸出值 不包含欄位名稱 System.out.println(next); } } |
輸出結果如下:
1 2 3 4 5 6 | Object {"first": "Tatu", "last": "Saloranta"} Value "Jackson Founder" Value "FasterXML" Array [{"type": "dog", "number": 1},{"type": "fish", "number": 50}] 不包含 Obejct Value Array |
相反,可以使用JsonNode.fields,因為可以訪問欄位名稱和值:
1 2 3 4 5 6 7 8 9 10 11 | @Test public void test17() throws IOException {<!-- --> String json = "{"name":{"first":"Tatu","last":"Saloranta"},"title":"Jackson founder","company":"FasterXML","pets":[{"type":"dog","number":1},{"type":"fish","number":50}]}"; ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); Iterator<Map.Entry<String, JsonNode>> fields = rootNode.fields(); while(fields.hasNext()){<!-- --> Map.Entry<String, JsonNode> next = fields.next(); System.out.println(next); } } |
輸出結果如下:
1 2 3 4 | name={"first":"Tatu","last":"Saloranta"} title="Jackson founder" company="FasterXML" pets=[{"type":"dog","number":1},{"type":"fish","number":50}] |
對於每個欄位,將欄位名稱新增到輸出中。 然後將值傳遞給processNode方法,將其作為子節點處理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private void appendNodeToYaml(JsonNode node, StringBuilder yaml, int depth, boolean isArrayItem) {<!-- --> //用於訪問此JSON物件的所有欄位(包含名稱和值)的方法。 Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); boolean isFirst = true; while (fields.hasNext()) {<!-- --> //取得 jsonNode 名稱 和 值 key=value Map.Entry<String, JsonNode> jsonField = fields.next(); //jsonField.getKey() 取得 key addFieldNameToYaml(yaml, jsonField.getKey(), depth, isArrayItem && isFirst); //jsonField.getValue() 取得value processNode(jsonField.getValue(), yaml, depth+1); isFirst = false; } } |
無法從該節點知道它深度是多少。 因此,將一個稱為depth的欄位傳遞到processNode方法中,以對此進行追蹤。 每次獲得子節點時,我們都會增加此值,以便可以正確縮進YAML輸出中的欄位:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private void addFieldNameToYaml( StringBuilder yaml, String fieldName, int depth, boolean isFirstInArray) {<!-- --> if (yaml.length()>0) {<!-- --> yaml.append(" "); int requiredDepth = (isFirstInArray) ? depth-1 : depth; for(int i = 0; i < requiredDepth; i++) {<!-- --> yaml.append(" "); } if (isFirstInArray) {<!-- --> yaml.append("- "); } } yaml.append(fieldName); yaml.append(": "); } |
現在,已經準備好所有程式碼以遍歷節點並建立YAML輸出,可以執行測試以表明其有效。
完成程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | public class JsonNodeIterator {<!-- --> private static final String NEW_LINE = " "; private static final String FIELD_DELIMITER = ": "; private static final String ARRAY_PREFIX = "- "; private static final String YAML_PREFIX = " "; public String toYaml(JsonNode root) {<!-- --> StringBuilder yaml = new StringBuilder(); processNode(root, yaml, 0); return yaml.toString(); } private void processNode(JsonNode jsonNode, StringBuilder yaml, int depth) {<!-- --> //是一個value 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); } } private void appendNodeToYaml(JsonNode node, StringBuilder yaml, int depth, boolean isArrayItem) {<!-- --> //用於訪問此JSON物件的所有欄位(包含名稱和值)的方法。 Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); boolean isFirst = true; while (fields.hasNext()) {<!-- --> //取得 jsonNode 名稱 和 值 key=value Map.Entry<String, JsonNode> jsonField = fields.next(); //jsonField.getKey() 取得 key addFieldNameToYaml(yaml, jsonField.getKey(), depth, isArrayItem && isFirst); //jsonField.getValue() 取得value processNode(jsonField.getValue(), yaml, depth+1); isFirst = false; } } private void addFieldNameToYaml(StringBuilder yaml, String fieldName, int depth, boolean isFirstInArray) {<!-- --> if (yaml.length()>0) {<!-- --> yaml.append(NEW_LINE); int requiredDepth = (isFirstInArray) ? depth-1 : depth; for(int i = 0; i < requiredDepth; i++) {<!-- --> yaml.append(YAML_PREFIX); } if (isFirstInArray) {<!-- --> yaml.append(ARRAY_PREFIX); } } yaml.append(fieldName); yaml.append(FIELD_DELIMITER); } } |