CodeGym /课程 /C# SELF /动态结构: JObject, JArray

动态结构: JObject, JArray

C# SELF
第 47 级, 课程 3
可用

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 结构,即使没有预先定义的模型。

2
任务
C# SELF, 第 47 级, 课程 3
已锁定
动态生成 JSON
动态生成 JSON
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION