CodeGym /Các khóa học /C# SELF /Cấu trúc động: JObject

Cấu trúc động: JObject, JArray

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

1. Giới thiệu

Trong các bài trước mình luôn làm việc với các cấu trúc kiểu cứng. Ví dụ, ta có lớp Person mà ta serialize thành JSON và ngược lại:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Nhưng đôi khi bạn không biết trước cấu trúc dữ liệu. Ví dụ:

  • Bạn viết parser cho dịch vụ bên ngoài, nơi cấu trúc trả về không cố định.
  • Bạn chỉ cần lấy một phần thông tin, không nhất thiết phải điền cả lớp.
  • Cần sửa hoặc tạo JSON "trên bay" dựa trên điều kiện động.

Lúc này "các cấu trúc động" phát huy tác dụng — các đối tượng lưu cây JSON như tập các cặp khóa-giá trị, không cần lớp C# định trước.

Tại sao thường dùng Newtonsoft.Json

Trong .NET có hai "tay chơi" chính khi làm việc với JSON:

  • System.Text.Json — thư viện tích hợp của Microsoft (phát triển từ .NET Core 3.0).
  • Newtonsoft.Json (Json.NET) — thư viện phổ biến, mang lại cho cộng đồng C# các lớp như JObjectJArray.

Tại thời điểm viết bài, System.Text.Json vẫn chưa có analog đầy đủ của JObject/JArray với cùng mức tiện lợi. Vì vậy, nếu thường xuyên phân tích hoặc chỉnh sửa các cấu trúc JSON phức tạp/không biết trước, họ thường chọn Newtonsoft.Json.

Các kiểu chính: JObject, JArray, JValue và họ JToken

  • JToken — kiểu cơ sở cho tất cả các nút JSON.
  • JObject — đối tượng JSON { ... }, tập các cặp "khóa-giá trị".
  • JArray — mảng [ ... ].
  • JValue — giá trị đơn lẻ (42, "văn bản", true, null).

Ý tưởng đơn giản: parse JSON — nhận "cây" từ các token này và đi trên cây đó, tìm kiếm, thay đổi, xóa và thêm phần tử.

2. Đọc JSON không biết trước

Giả sử ta nhận được JSON như sau, và ta không muốn hoặc không thể viết lớp cho nó trước:

{
  "status": "ok",
  "amount": 150.5,
  "items": [
    {
      "name": "book",
      "qty": 1
    },
    {
      "name": "pen",
      "qty": 3
    }
  ]
}

Với Newtonsoft.Json ta có thể biến nó thành cây và khám phá trên bay.

Ví dụ: đọc JSON vào JObject

using Newtonsoft.Json.Linq;

string json = @"{
  ""status"": ""ok"",
  ""amount"": 150.5,
  ""items"": [
    { ""name"": ""book"", ""qty"": 1 },
    { ""name"": ""pen"", ""qty"": 3 }
  ]
}";

// Parse chuỗi và lấy cây
JObject root = JObject.Parse(json);

// Lấy thuộc tính như từ dictionary
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items là mảng, nên là JArray
JArray items = (JArray)root["items"];

// Duyệt mảng
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"Sản phẩm: {name}, Số lượng: {qty}");
}

Rất tiện: không cần khai báo lớp — có thể nhanh chóng moi ra phần cần thiết.

3. Indexer và truy cập động

Indexers:

  • Với object: root["status"], root["items"]
  • Với mảng: items[0], items[1]

Để lấy giá trị ngay ở kiểu mong muốn, dùng ép kiểu (string), (int), (bool), (double) — thư viện sẽ tự chuyển kiểu.

Nếu dữ liệu có thể không tồn tại, cẩn thận: truy cập khóa không có sẽ trả về null, và ép kiểu trực tiếp sẽ ném. Tốt hơn dùng các method kiểm tra:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // Cách này tiện hơn: Value<T>() – chuyển ngay sang T
}

Đối tượng và mảng lồng nhau cũng đọc đơn giản:

// Lấy sản phẩm thứ hai
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

Cũng có thể dùng dynamic:

dynamic droot = root;
Console.WriteLine(droot.status); // "ok"

Nhưng nhớ: với dynamic compiler sẽ không kiểm tra truy cập trường — lỗi sẽ xuất hiện chỉ khi chạy.

4. Thay đổi cây JSON trên bay

Thêm phần tử

root["currency"] = "RUB";        // Thêm thuộc tính mới
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

Thay đổi và xóa

root["status"] = "done";         // Thay giá trị
items[0]["qty"] = 5;             // Tăng số lượng sản phẩm đầu tiên
items.RemoveAt(1);               // Xóa sản phẩm thứ hai

Lưu lại thành chuỗi

string modifiedJson = root.ToString();
// Hoặc root.ToString(Formatting.Indented) cho đẹp hơn

5. Tạo cấu trúc JSON từ đầu

var person = new JObject
{
    ["name"] = "Alice",
    ["age"] = 22,
    ["languages"] = new JArray { "C#", "Python" },
    ["isStudent"] = true
};

Console.WriteLine(person.ToString(Newtonsoft.Json.Formatting.Indented));
{
  "name": "Alice",
  "age": 22,
  "languages": [
    "C#",
    "Python"
  ],
  "isStudent": true
}

Hữu ích khi cần trả lại cho API ngoài chỉ một phần dữ liệu hoặc xây dựng JSON theo điều kiện.

6. Cách tạo đối tượng của riêng bạn từ JObject

Đôi khi cần biến JSON linh hoạt thành đối tượng C# kiểu cứng. Các cách:

  • Deserialize bình thường: JsonConvert.DeserializeObject<MyClass>(...).
  • Tạo thủ công, lấy các trường cần thiết từ JObject.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Giả sử ta nhận JSON như sau:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// Tạo đối tượng thủ công — chỉ lấy trường cần
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField chúng ta không cần — tốt thôi!
};

7. Ví dụ lỗi và lưu ý: bẫy phổ biến

Làm việc với JSON động rất linh hoạt, nhưng có "bẫy dưới nước":

  • Truy cập khóa/ phần tử không tồn tại trả về null; ép kiểu sang kiểu giá trị sẽ ném exception.
  • Không khớp kiểu mong đợi (mong là object nhưng thực ra là value) — lỗi khi ép kiểu.
  • Để duyệt các trường của object dùng Properties():
foreach (var prop in root.Properties())
{
    Console.WriteLine($"Trường: {prop.Name}, Giá trị: {prop.Value}");
}
  • Newtonsoft.Json tiện khi kết hợp với truy vấn kiểu LINQ (lọc, tìm kiếm):
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. Lỗi thường gặp khi làm việc với JObject/JArray

Lỗi #1: thiếu trường mong đợi hoặc không khớp kiểu. Rất thường dev kỳ vọng trường có trong object, nhưng nó bị thiếu hoặc có kiểu khác. Nếu mong object mà nhận số thì khi ép kiểu sẽ ném. Hãy kiểm tra tồn tại và kiểu trước khi dùng.

Lỗi #2: truy cập các trường của cấu trúc lồng nhau mà không kiểm tra null. Khi JSON có object lồng nhau, mà khóa khác hoặc không có, cố truy cập trường vắng có thể làm sập. Kiểm tra null trước khi đọc giá trị của nút con.

Lỗi #3: ép sang kiểu giá trị khi value là null. Nếu khóa tồn tại nhưng giá trị là null, biểu thức như (int)j["age"] sẽ ném. Dùng Value<T>() — nó trả giá trị mặc định (với int0, với string là null).

Lỗi #4: quá lạm dụng object động thay vì model rõ ràng. Những cấu trúc quá phức tạp tốt hơn là mô tả bằng lớp C#: giảm lỗi và tăng khả đọc. Dùng dynamic khi cấu trúc thực sự không biết trước hoặc biến động mạnh.

Bây giờ bạn đã biết cách parse nhanh và an toàn, thay đổi và tạo cấu trúc JSON bằng JObjectJArray, ngay cả khi không có model định trước.

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