CodeGym /课程 /JAVA 25 SELF /处理动态数据结构:Map、List、JsonNode

处理动态数据结构:Map、List、JsonNode

JAVA 25 SELF
第 46 级 , 课程 3
可用

1. 将 JSON 读入 Map 和 List

通常在处理 JSON 时,我们事先知道其结构,并可以用 Java 类描述它。但在实践中往往并非如此可预期:字段可能出现或消失,嵌套层级也会变化。此时,使用通用的数据结构——MapList——或 JSON 树会更方便。

为每一种变体都建一个 Java 模型既耗时又脆弱。通用集合和树能让你灵活地只提取所需部分,而不受固定模式的束缚。

将 JSON 对象反序列化为 Map

JSON 示例:

{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "active": true
}

反序列化为 Map:

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"id\":123,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"active\":true}";

        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> data = mapper.readValue(json, Map.class);

        System.out.println(data);
        // 输出: {id=123, name=Alice, email=alice@example.com, active=true}
    }
}

现在你可以像访问字典元素一样访问任意字段:

System.out.println(data.get("name")); // Alice

将对象数组反序列化为 List

JSON 数组:

[
  { "id": 1, "name": "Alice" },
  { "id": 2, "name": "Bob" }
]

反序列化:

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;

public class UsersReadExample {
    public static void main(String[] args) throws Exception {
        String json = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";

        ObjectMapper mapper = new ObjectMapper();
        List<Map<String, Object>> users = mapper.readValue(json, List.class);

        for (Map<String, Object> user : users) {
            System.out.println(user.get("name"));
        }
        // 输出:
        // Alice
        // Bob
    }
}

重要细节:读取为通用结构时,所有嵌套对象都会变成 Map,而数组会变成 List。对于复杂结构,需要谨慎进行类型转换并检查其类型:

Object items = data.get("items");
if (items instanceof List) {
    List<?> itemList = (List<?>) items;
    // ...
}

2. JsonNode:Jackson 中的 JSON 树

使用 Map/List 虽然方便,但并不安全:可能会弄错类型或忽略某层嵌套。Jackson 提供了更强大的工具——类 JsonNode。它是一个通用的树结构,每个节点都可以是对象、数组、值或 null

获取 JsonNode

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

public class JsonNodeStart {
    public static void main(String[] args) throws Exception {
        String json = "{\"id\":123,\"name\":\"Alice\",\"tags\":[\"java\",\"json\"]}";
        ObjectMapper mapper = new ObjectMapper();

        JsonNode root = mapper.readTree(json);
        // root 是 JSON 树的根节点
    }
}

访问字段

int id = root.get("id").asInt();         // 123
String name = root.get("name").asText();  // Alice
JsonNode tags = root.get("tags");         // 数组

System.out.println("姓名: " + name);

遍历数组

for (JsonNode tag : tags) {
    System.out.println(tag.asText());
}
// 输出:
// java
// json

嵌套对象

复杂 JSON 示例:

{
  "user": {
    "id": 1,
    "profile": {
      "nickname": "java_guru",
      "age": 25
    }
  }
}

提取嵌套值:

JsonNode profile = root.get("user").get("profile");
String nickname = profile.get("nickname").asText();
System.out.println(nickname); // java_guru

安全访问:getpath

- get("键") —— 如果键不存在,将返回 null。对类似 asText() 的调用作用在 null 上会导致 NullPointerException
- path("键") —— 如果键不存在,将返回“空”节点,此时 asText() 返回 ""asInt() 返回 0

String phone = root.path("phone").asText(); // 如果字段不存在,则为 ""

检查节点类型

if (root.has("tags") && root.get("tags").isArray()) {
    for (JsonNode tag : root.get("tags")) {
        // ...
    }
}

修改 JsonNode

JsonNode 是不可变的。要创建/修改树,请使用 ObjectNode/ArrayNode

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

ObjectMapper mapper = new ObjectMapper();

// 创建新对象
ObjectNode obj = mapper.createObjectNode();
obj.put("id", 10);
obj.put("name", "Bob");

// 添加数组
ArrayNode arr = mapper.createArrayNode();
arr.add("Java").add("JSON");
obj.set("tags", arr);

System.out.println(obj.toPrettyString());
/*
{
  "id" : 10,
  "name" : "Bob",
  "tags" : [ "Java", "JSON" ]
}
*/

3. 使用 Gson:JsonElement, JsonObject, JsonArray

Jackson 并非唯一选择。Gson 同样提供了用于动态 JSON 的便捷 API:JsonElementJsonObjectJsonArray

解析为 JsonElement

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

String json = "{\"id\":123,\"name\":\"Alice\",\"tags\":[\"java\",\"json\"]}";
JsonElement root = JsonParser.parseString(json);

访问字段

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

JsonObject obj = root.getAsJsonObject();
int id = obj.get("id").getAsInt();
String name = obj.get("name").getAsString();
JsonArray tags = obj.getAsJsonArray("tags");

for (JsonElement tag : tags) {
    System.out.println(tag.getAsString());
}

嵌套与安全性

if (obj.has("email")) {
    String email = obj.get("email").getAsString();
}

处理数组

String arrJson = "[{\"id\":1},{\"id\":2}]";
JsonArray arr = JsonParser.parseString(arrJson).getAsJsonArray();

for (JsonElement el : arr) {
    JsonObject item = el.getAsJsonObject();
    System.out.println(item.get("id").getAsInt());
}

修改

Gson 中对象是可变的——可以添加和删除字段:

import com.google.gson.JsonObject;

JsonObject newObj = new JsonObject();
newObj.addProperty("id", 42);
newObj.add("tags", tags);
System.out.println(newObj.toString());

4. 实战:从未知结构的 JSON 中提取数据

任务: 假设我们有一个 JSON 配置,其结构可能变化:

{
  "service": "mail",
  "enabled": true,
  "params": {
    "host": "smtp.example.com",
    "port": 587
  }
}

使用 hostport 通过 Jackson 提取:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode params = root.path("params");
String host = params.path("host").asText();
int port = params.path("port").asInt();

System.out.println(host + ":" + port); // smtp.example.com:587

使用 Gson 完成同样的操作:

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

JsonObject rootObj = JsonParser.parseString(json).getAsJsonObject();
JsonObject params = rootObj.getAsJsonObject("params");
String host = params.get("host").getAsString();
int port = params.get("port").getAsInt();

System.out.println(host + ":" + port);

5. 常见错误与注意事项

错误 1:错误的类型转换。 如果你期望得到字符串,而字段里是数字或 null,调用 asText()getAsString() 可能产生意外结果或抛出异常。例如,当字段不存在时,root.get("foo").asText() 会导致 NullPointerException

错误 2:缺少 null 检查。 尤其是在链式访问时:root.get("params").get("host")。如果 params 不存在——会出现 NPE。在 Jackson 中使用 path();在 Gson 中通过 has 检查是否存在,并通过 isJsonObject()/isJsonArray() 检查类型。

错误 3:混淆节点类型。 如果字段是数组,却把它当作对象来访问——会出错。请检查类型:Jackson——isArray()/isObject(),Gson——isJsonArray()/isJsonObject()

错误 4:使用 Map/List 时丢失类型信息。 反序列化到 Map<String, Object> 后,嵌套结构会变成一连串的 Map/List,这会使导航变得复杂,并导致大量的类型转换和 instanceof 检查。

错误 5:误解树的(不)可变性。 在 Jackson 中,JsonNode 是不可变的,需要通过 ObjectNodeArrayNode 来修改。在 Gson 中对象是可变的;请记住,修改某个节点会影响当前树中对它的所有引用。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION