1. 介绍
在之前的讲座里我们一直使用严格类型化的结构。例如,我们有一个类 Person,我们把它序列化为 JSON 然后再反序列化回来:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
但有时候你事先不知道数据结构。例如:
- 你在写一个外部服务的解析器,那里的响应结构并不固定。
- 你只需要提取一部分信息,不一定要填满整个类。
- 需要根据动态条件实时编辑或生成 JSON。
这时候就用到“动态结构”——把 JSON 树当成一组键值来存储的对象,不需要预先定义 C# 类。
为什么通常使用 Newtonsoft.Json
在 .NET 里处理 JSON 主要有两个玩家:
- System.Text.Json — Microsoft 的内置库(从 .NET Core 3.0 开始发展)。
- Newtonsoft.Json (Json.NET) — 流行库,给 C# 世界带来了像 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:
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; // 把第一个商品数量改为 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