JsonNode 是 .NET 6+ 解析任意结构 JSON 的唯一干净解法,支持延迟解析、链式访问、无需预定义类型,适用于字段不固定、嵌套不定等场景。

用 JsonNode 解析任意结构 JSON(.NET 6+)
直接用 JsonNode.Parse(),别碰 JObject 或 dynamic + JsonConvert.DeserializeObject —— 后者在未知结构下容易抛 JsonSerializationException,而且改起来反人类。
场景很常见:API 返回字段不固定、配置项嵌套深度不定、前端传来的表单数据结构松散。这时候 JsonNode 是唯一干净解法。
-
JsonNode是只读、延迟解析的树形结构,不绑定类型,也不强制要求提前定义 class - 支持
["key"]、[0]、.AsArray()、.AsObject()等链式访问,写起来接近 JavaScript - 注意:它不支持 LINQ to JSON 那套
SelectTokens路径语法,要用AsObject().TryGetPropertyValue()或递归遍历 - 性能比
JObject略好(无反射、无属性缓存开销),但比强类型反序列化慢;够用,别微优化
示例:
var json = "{\"user\":{\"name\":\"Alice\",\"tags\":[\"dev\",\"c#\"]},\"meta\":{\"v\":1}}";<br>var root = JsonNode.Parse(json);<br>string name = root["user"]?["name"]?.GetValueKind() == JsonValueKind.String<br> ? root["user"]["name"].GetValue<string>()<br> : null;<br>JsonArray tags = root["user"]?["tags"]?.AsArray();
老项目还在用 Newtonsoft.Json?用 JObject 但避开 dynamic
如果你卡在 .NET Framework 或 .NET 5 以下,JObject 是事实标准,但别用 dynamic 接收——它会让编译器放弃所有类型检查,运行时出错才报,且 IDE 不提示字段名。
错误现象:Newtonsoft.Json.JsonReaderException: Unexpected character encountered 常出现在字段缺失或类型错配时,而 dynamic 让这种问题更难定位。
- 用
JObject.Parse()后,统一走obj["field"]?.ToString()或obj["field"]?.Value<int>("field")显式取值 - 判断字段是否存在用
obj.ContainsKey("field"),别依赖== null(空字符串、0、false 都可能被误判) - 数组用
obj["list"]?.ToArray(),再 foreach;别用foreach (var item in obj["list"])—— 它会把整个JArray当一个元素 -
JObject默认不忽略大小写,如果后端字段命名混乱(如UserID和userid并存),得手动设JsonSerializerSettings.PropertyNameComparison = StringComparison.OrdinalIgnoreCase
字段名含点号、中括号、$ 符号怎么办
JSON 键里出现 "user.name"、"items[0]"、"$ref" 这类非法 C# 标识符时,dynamic 和强类型反序列化基本瘫痪,JObject 和 JsonNode 也得绕道走。
根本原因:点号和中括号会被解析器当成路径操作符,不是字面 key。
-
JObject下用obj.SelectToken("$.'user.name'")(单引号包裹键名),或obj["user.name"]—— 只要不用点号访问,直接方括号就安全 -
JsonNode下必须用AsObject().TryGetPropertyValue("user.name", out var value),不能写node["user.name"](会报ArgumentException: Invalid token) - 遇到
$ref这类 JSON Schema 关键字,别急着当业务字段处理;先确认是不是服务端返回了未解析的引用,需要手动做 JSON Pointer 解析
什么时候真该建 class,而不是硬扛动态解析
动态解析不是银弹。一旦你发现自己在三处以上重复写 root["data"]?["items"]?.AsArray(),或者开始手写递归函数来 flatten 嵌套对象,说明结构其实有隐含契约 —— 只是没人写下来。
这时建 class 成本远低于维护一堆脆弱的字符串 key。
- 用
record类 +[JsonPropertyName("api_field_name")],兼顾简洁和可读性 - 字段可空用
string?、int?,配合JsonSerializerOptions.Default.IgnoreNullValues = true - 嵌套数组统一用
IReadOnlyList<T>,避免意外修改原始数据 - 如果字段 80% 固定、20% 动态(比如日志里的
extra字段),就拆成主 class +JsonElement extra或Dictionary<string, JsonNode> extra
最常被忽略的一点:动态解析掩盖了接口契约退化。今天能跑,明天字段名一改,所有地方静默失效 —— 而编译器不会提醒你。










