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) — 很流行的函式庫,提供了像 JObject 和 JArray 這樣的類別。
在撰寫本講義時,System.Text.Json 仍然沒有完全等同於 JObject/JArray 那樣方便的替代方案。所以如果你需要頻繁解析或修改複雜/未知的 JSON 結構,通常會選擇 Newtonsoft.Json。
主要類型: JObject, JArray, JValue 和 JToken 家族
- JToken — 所有 JSON 節點的基底類型。
- JObject — JSON 物件 { ... },鍵-值對的集合。
- JArray — 陣列 [ ... ]。
- JValue — 單一的值(42、"文字"、true、null)。
概念很簡單:解析 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>() — 它會回傳預設值(對 int 是 0,對字串是 null)。
錯誤 #4:過度依賴動態物件而不是明確模型。 對過於複雜的結構,還是用 C# 類別來描述會更安全且可讀性更高。只有在結構確實未知或大幅變動時才使用動態方式。
現在你知道如何使用 JObject 和 JArray 在沒有事先定義模型的情況下快速且安全地解析、修改與生成 JSON 結構。
GO TO FULL VERSION