CodeGym /Các khóa học /JAVA 25 SELF /Làm việc với cấu trúc động: Map, List, JsonNode

Làm việc với cấu trúc động: Map, List, JsonNode

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Đọc JSON vào Map và List

Thông thường, khi làm việc với JSON, chúng ta biết trước cấu trúc của nó và có thể mô tả nó dưới dạng các lớp Java. Nhưng trong thực tế thì không luôn như vậy: các trường có thể xuất hiện hoặc biến mất, mức độ lồng nhau có thể thay đổi. Trong những trường hợp như vậy, việc thao tác với các cấu trúc dữ liệu chung — Map, List — hoặc cây JSON sẽ tiện hơn nhiều.

Tạo model Java cho từng biến thể — vừa tốn thời gian vừa dễ vỡ. Các collection và cây tổng quát cho phép trích xuất linh hoạt chỉ những phần cần thiết mà không bị ràng buộc vào một schema cố định.

Giải tuần tự JSON object vào Map

Ví dụ JSON:

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

Giải tuần tự vào 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);
        // Kết quả: {id=123, name=Alice, email=alice@example.com, active=true}
    }
}

Giờ bạn có thể truy cập bất kỳ trường nào như một phần tử của Map:

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

Giải tuần tự mảng các đối tượng vào List

Mảng JSON:

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

Giải tuần tự:

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"));
        }
        // Kết quả:
        // Alice
        // Bob
    }
}

Lưu ý quan trọng: khi đọc vào các cấu trúc tổng quát, mọi đối tượng lồng nhau sẽ trở thành Map, còn mảng — thành List. Với cấu trúc phức tạp, bạn sẽ phải ép kiểu và kiểm tra cẩn thận:

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

2. JsonNode: cây JSON trong Jackson

Làm việc với Map/List thì tiện nhưng không an toàn: bạn có thể nhầm kiểu hoặc bỏ sót mức lồng nhau. Jackson cung cấp công cụ mạnh mẽ hơn — lớp JsonNode. Đây là một cây tổng quát, trong đó mỗi nút có thể là đối tượng, mảng, giá trị hoặc null.

Lấy 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 — là gốc của cây JSON
    }
}

Truy cập trường

int id = root.get("id").asInt();         // 123
String name = root.get("name").asText();  // Alice
JsonNode tags = root.get("tags");         // mảng

System.out.println("Tên: " + name);

Duyệt mảng

for (JsonNode tag : tags) {
    System.out.println(tag.asText());
}
// Kết quả:
// java
// json

Đối tượng lồng nhau

Ví dụ JSON phức tạp:

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

Trích xuất giá trị lồng nhau:

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

Truy cập an toàn: get vs path

- get("key") — nếu không có khóa, sẽ trả về null. Bất kỳ lời gọi như asText() trên null sẽ dẫn tới NullPointerException.
- path("key") — nếu không có khóa, sẽ trả về nút “rỗng”, và asText() sẽ cho "", asInt()0.

String phone = root.path("phone").asText(); // "" nếu không có trường

Kiểm tra kiểu nút

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

Sửa đổi JsonNode

JsonNode là bất biến. Để tạo/sửa cây, hãy dùng 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();

// Tạo đối tượng mới
ObjectNode obj = mapper.createObjectNode();
obj.put("id", 10);
obj.put("name", "Bob");

// Thêm mảng
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. Làm việc với Gson: JsonElement, JsonObject, JsonArray

Jackson không phải lựa chọn duy nhất. Gson cũng cung cấp API thuận tiện cho JSON động: JsonElement, JsonObject, JsonArray.

Phân tích cú pháp vào 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);

Truy cập trường

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

Lồng nhau và an toàn

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

Làm việc với mảng

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

Sửa đổi

Trong Gson các đối tượng là có thể thay đổi — có thể thêm và xóa trường:

import com.google.gson.JsonObject;

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

4. Thực hành: trích xuất dữ liệu từ JSON không biết trước

Bài toán: Giả sử chúng ta có một cấu hình JSON, trong đó cấu trúc có thể thay đổi:

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

Trích xuất hostport bằng 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

Tương tự với 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. Lỗi thường gặp và lưu ý

Lỗi số 1: Ép kiểu sai. Nếu bạn kỳ vọng một chuỗi nhưng trường lại là số hoặc null, gọi asText() hoặc getAsString() sẽ cho kết quả không mong muốn hoặc ném ngoại lệ. Ví dụ, khi không có trường, root.get("foo").asText() sẽ dẫn tới NullPointerException.

Lỗi số 2: Không kiểm tra null. Đặc biệt với các truy cập lồng nhau: root.get("params").get("host"). Nếu params không tồn tại — sẽ NPE. Trong Jackson hãy dùng path(), trong Gson — kiểm tra sự tồn tại bằng has và kiểu bằng isJsonObject()/isJsonArray().

Lỗi số 3: Nhầm lẫn kiểu nút. Nếu một trường là mảng nhưng bạn truy cập như đối tượng — sẽ gặp lỗi. Hãy kiểm tra kiểu: Jackson — isArray()/isObject(), Gson — isJsonArray()/isJsonObject().

Lỗi số 4: Mất thông tin kiểu khi làm việc với Map/List. Khi giải tuần tự vào Map<String, Object>, các cấu trúc lồng nhau trở thành một chuỗi Map/List, làm việc điều hướng khó hơn và dẫn tới nhiều lần ép kiểu và kiểm tra instanceof.

Lỗi số 5: Không hiểu tính (bất) biến của cây. Trong Jackson JsonNode là bất biến, và việc thay đổi phải thông qua ObjectNodeArrayNode. Trong Gson các đối tượng là có thể thay đổi; hãy nhớ rằng việc thay đổi một nút sẽ ảnh hưởng đến mọi tham chiếu đến nó trong cây hiện tại.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION