Jackson 是 Java 裡最強的 JSON 庫,但新手常搞不清楚 JsonNode 的各種遍歷方法,還有 path() 和 get() 的差別。踩過坑以後,整理出來給大家。
JsonNode 遍歷
拿到一個 JsonNode,要怎麼遍歷裡面的欄位?有好幾種方法。
方法 1:用 fields() 取得 Iterator
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
| ObjectMapper mapper = new ObjectMapper(); String json = """ { "name": "Alice", "age": 30, "email": "alice@example.com" } """;
JsonNode node = mapper.readTree(json);
Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); String key = entry.getKey(); JsonNode value = entry.getValue(); System.out.println(key + " = " + value.asText()); }
|
方法 2:用 fieldNames() 只取 key
如果只關心 key:
1 2 3 4 5 6 7 8 9 10
| Iterator<String> fieldNames = node.fieldNames(); while (fieldNames.hasNext()) { String key = fieldNames.next(); System.out.println(key); }
|
方法 3:forEach 遍歷(Java 8+)
更現代的做法,用 forEach:
1 2 3 4 5
| node.fields().forEachRemaining(entry -> { String key = entry.getKey(); JsonNode value = entry.getValue(); System.out.println(key + " = " + value.asText()); });
|
或者直接用 forEach 方法:
1 2 3
| node.forEach(child -> { System.out.println(child); });
|
這個 forEach 直接遍歷值(JsonNode),不包含 key。
方法 4:用 toPrettyString() 看結構
開發時一堆 debug,直接印出來看:
1
| System.out.println(node.toPrettyString());
|
實戰:Template 變數替換
最常見的應用場景:有個模板文字,要用 JSON 的 key-value 替換裡面的變數。
比如模板:
1
| Hello {{name}}, you are {{age}} years old. Contact: {{email}}
|
用 JsonNode 去替換:
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
| String template = "Hello {{name}}, you are {{age}} years old. Contact: {{email}}"; String json = """ { "name": "Alice", "age": 30, "email": "alice@example.com" } """;
ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(json);
String result = template;
Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); String key = entry.getKey(); String value = entry.getValue().asText(); result = result.replace("{{" + key + "}}", value); }
System.out.println(result);
|
或者用 regex 更優雅(但對複雜模板可能有問題):
1 2 3 4 5 6
| String result = template; node.fields().forEachRemaining(entry -> { String key = entry.getKey(); String value = entry.getValue().asText(); result = result.replace("{{" + key + "}}", value); });
|
path() vs get() 的差別
這是最容易踩的坑。
get() - 屬性不存在回傳 null
1 2 3 4 5 6 7 8 9 10
| JsonNode user = mapper.readTree(""" { "name": "Alice", "age": 30 } """);
JsonNode email = user.get("email"); System.out.println(email); System.out.println(email.asText());
|
path() - 屬性不存在回傳 MissingNode
1 2 3 4 5 6 7 8 9 10 11
| JsonNode user = mapper.readTree(""" { "name": "Alice", "age": 30 } """);
JsonNode email = user.path("email"); System.out.println(email); System.out.println(email.asText()); System.out.println(email.isMissingNode());
|
實戰例子
所以安全的做法是永遠用 path(),然後檢查 isMissingNode():
1 2 3 4 5 6 7 8 9 10 11 12
| public String extractEmail(String json) throws Exception { JsonNode node = mapper.readTree(json); JsonNode email = node.path("email"); if (email.isMissingNode()) { return "No email provided"; } return email.asText(); }
|
或者簡單做法,path() 直接 asText() 就會回傳空字串:
1
| String email = node.path("email").asText();
|
複雜路徑
要取嵌套的欄位,path() 特別方便:
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
| String json = """ { "user": { "profile": { "email": "alice@example.com" } } } """;
JsonNode node = mapper.readTree(json);
String email = node.path("user") .path("profile") .path("email") .asText();
System.out.println(email);
String phone = node.path("user") .path("contact") .path("phone") .asText();
System.out.println(phone);
|
常用的轉換方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| JsonNode node = mapper.readTree(json);
String str = node.asText();
int num = node.asInt();
long lng = node.asLong();
double dbl = node.asDouble();
boolean bool = node.asBoolean();
String str = node.asText("default value"); int num = node.asInt(999);
|
檢查型態
有時候要確認欄位是啥型態:
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
| if (node.isObject()) { }
if (node.isArray()) { }
if (node.isTextual()) { }
if (node.isNumber()) { }
if (node.isBoolean()) { }
if (node.isNull()) { }
if (node.isMissingNode()) { }
|
遍歷陣列
JSON 陣列怎麼辦:
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
| String json = """ { "users": [ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Cathy", "age": 28} ] } """;
JsonNode root = mapper.readTree(json); JsonNode users = root.path("users");
for (JsonNode user : users) { String name = user.path("name").asText(); int age = user.path("age").asInt(); System.out.println(name + ": " + age); }
users.forEach(user -> { String name = user.path("name").asText(); int age = user.path("age").asInt(); System.out.println(name + ": " + age); });
|
實務建議
永遠用 path() 而不是 get()
- path() 安全,不存在就回傳 MissingNode,asText() 回傳空字串
- get() 回傳 null,容易踩 NullPointerException
遍歷物件用 fields()
1 2 3 4
| node.fields().forEachRemaining(entry -> { String key = entry.getKey(); JsonNode value = entry.getValue(); });
|
遍歷陣列直接 for-each 或 forEach
1 2 3
| for (JsonNode item : node) { }
|
複雜嵌套路徑用 path() 鏈式呼叫
1
| node.path("a").path("b").path("c").asText()
|
要檢查欄位是否存在就用 isMissingNode()
1 2 3
| if (!node.path("optional_field").isMissingNode()) { }
|
Jackson 其實沒那麼難,關鍵是理解 path() 和 get() 的差別,然後就沒有踩不完的坑了。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️