CodeGym /課程 /C# SELF /動態結構: JObject,

動態結構: JObject, JArray

C# SELF
等級 47 , 課堂 3
開放

1. 介紹

在之前的講義裡我們通常使用強型別結構。比如,我們有一個 Person 類,會把它序列化成 JSON 或從 JSON 反序列化回來:

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

但有時你事先不知道資料結構。例如:

  • 你在為外部服務寫 parser,回應的結構不是固定的。
  • 你只需要抽取部分資訊,不一定要填滿整個類別。
  • 需要根據動態條件即時編輯或生成 JSON。

這時「動態結構」派上用場 — 物件把 JSON 樹當作鍵值對的集合來保存,無需事先定義 C# 類。

為什麼通常選用 Newtonsoft.Json

在 .NET 裡處理 JSON 有兩個主要選擇:

  • System.Text.Json — Microsoft 內建的函式庫(從 .NET Core 3.0 起發展)。
  • Newtonsoft.Json (Json.NET) — 很流行的函式庫,提供了像 JObjectJArray 這樣的類別。

在撰寫本講義時,System.Text.Json 仍然沒有完全等同於 JObject/JArray 那樣方便的替代方案。所以如果你需要頻繁解析或修改複雜/未知的 JSON 結構,通常會選擇 Newtonsoft.Json

主要類型: JObject, JArray, JValueJToken 家族

  • JToken — 所有 JSON 節點的基底類型。
  • JObject — JSON 物件 { ... },鍵-值對的集合。
  • JArray — 陣列 [ ... ]
  • JValue — 單一的值(42"文字"truenull)。

概念很簡單:解析 JSON — 得到由這些 token 組成的「樹」,然後可以沿著它走、查找、改動、刪除或新增節點。

2. 讀取未知的 JSON

假設我們收到這樣的 JSON,但不想或不能事先為它寫類別:

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

使用 Newtonsoft.Json 我們可以把它轉成樹狀結構並即時檢查。

範例:把 JSON 讀成 JObject

using Newtonsoft.Json.Linq;

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

// 解析字串並取得樹
JObject root = JObject.Parse(json);

// 像從字典取屬性
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items 是陣列,所以是 JArray
JArray items = (JArray)root["items"];

// 遍歷陣列
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"商品: {name}, 數量: {qty}");
}

非常方便:不用宣告類別,可以快速撈出需要的片段。

3. 索引器和動態存取

索引器:

  • 對物件: root["status"], root["items"]
  • 對陣列: items[0], items[1]

想直接得到所需型別的值,可以使用轉型 (string)、(int)、(bool)、(double) — 函式庫會自動做型別轉換。

如果資料可能不存在,要小心:存取不存在的鍵會回傳 null,直接轉型會拋例外。最好使用帶檢查的方法:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // 這種方式比較好:Value<T>() 會直接做轉換
}

巢狀的物件和陣列也很容易讀:

// 取得第二個商品
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

也可以用動態的方式:

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

但記住:用 dynamic 時編譯器不會檢查欄位存取,錯誤會在執行時才浮現。

4. 在執行時修改 JSON 樹

新增元素

root["currency"] = "RUB";        // 新增屬性
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

修改與刪除

root["status"] = "done";         // 修改值
items[0]["qty"] = 5;             // 增加第一個商品的數量
items.RemoveAt(1);               // 刪除第二個商品

最後儲存成字串

string modifiedJson = root.ToString();
// 或者 root.ToString(Formatting.Indented) 讓輸出更好看

5. 從零開始建立 JSON 結構

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
}

當你只想對外回傳部分資料或根據條件組裝 JSON 時,這很有用。

6. 如何從 JObject 組裝出自己的物件

有時需要把彈性的 JSON 轉成嚴格的 C# 物件。選項:

  • 直接反序列化:JsonConvert.DeserializeObject<MyClass>(...)
  • 手動組裝物件,從 JObject 拿出需要的欄位。
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// 假設我們收到這個 JSON:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// 手動組裝物件 — 只取需要的欄位
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField 我們不需要 — 沒關係!
};

7. 範例錯誤與細節:常見陷阱

處理動態 JSON 結構很靈活,但有一些「地雷」要注意:

  • 存取不存在的鍵/元素會回傳 null;把它直接轉成值類型會丟出例外。
  • 期望的型別不符(預期物件但傳來的是值)— 轉型會失敗。
  • 要遍歷物件的欄位,使用 Properties()
foreach (var prop in root.Properties())
{
    Console.WriteLine($"欄位: {prop.Name}, 值: {prop.Value}");
}
  • Newtonsoft.Json 支援類 LINQ 的操作(過濾、搜尋),用起來很方便:
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. 使用 JObject/JArray 時常見的錯誤

錯誤 #1:缺少預期欄位或型別不符合。 很常見的情況是開發者預期物件裡有某個欄位,但它不存在或型別不同。如果預期是一個物件但實際是一個數字,轉型會拋例外。在使用前檢查存在性和型別。

錯誤 #2:在沒有檢查 null 的情況下存取巢狀結構的欄位。 當 JSON 有巢狀物件但鍵名不同或遺失時,存取缺少的欄位會導致錯誤。讀取巢狀節點前先檢查是否為 null

錯誤 #3:把 null 的值轉成值類型。 如果鍵存在但值是 null,像 (int)j["age"] 會拋例外。使用 Value<T>() — 它會回傳預設值(對 int0,對字串是 null)。

錯誤 #4:過度依賴動態物件而不是明確模型。 對過於複雜的結構,還是用 C# 類別來描述會更安全且可讀性更高。只有在結構確實未知或大幅變動時才使用動態方式。

現在你知道如何使用 JObjectJArray 在沒有事先定義模型的情況下快速且安全地解析、修改與生成 JSON 結構。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION