JsonPath 不是 System.Text.Json 原生功能,需依赖 Newtonsoft.Json 等第三方库;手动遍历 JsonElement 要用 TryGetProperty 和类型检查防崩溃;固定路径应优先使用强类型反序列化而非 JsonPath。

JsonPath 在 C# 里根本不是 .NET 原生支持的
System.Text.Json 自带的 JsonElement 和 JsonProperty 没有内置 JsonPath 解析能力。你写 $..name 或 $.data[?(@.age > 30)],直接报错——它压根不认识这语法。
想用 JsonPath,必须引入第三方库,最常用的是 Newtonsoft.Json(即 Json.NET)搭配 JToken.SelectTokens();或者用轻量替代方案 Manatee.Json 或 JsonPath.Net。但注意:System.Text.Json 本身不提供 SelectTokens,别在官方文档里白找。
- 如果你项目已用
Newtonsoft.Json,直接上JObject.Parse(json).SelectTokens("path") - 如果坚持用
System.Text.Json,只能手动遍历JsonElement,或封装递归查找逻辑(比如匹配字段名、数组索引、简单条件) - 混合使用两种解析器容易出问题:比如把
JsonElement强转成JToken会抛InvalidOperationException
System.Text.Json 手动查嵌套字段怎么写才不崩
没有 JsonPath,就得靠 GetProperty() 和 EnumerateArray() 一层层钻。关键不是“能不能”,而是“怎么避免空引用崩溃”和“路径不存在时安静失败”。
比如查 $.store.book[0].author,不能连写 root.GetProperty("store").GetProperty("book")[0].GetProperty("author")——中间任意一环为 null 或类型不对(比如 "book" 是字符串不是对象),立刻炸。
- 永远用
TryGetProperty("xxx", out var result)替代GetProperty() - 数组访问前先确认是
JsonValueKind.Array,再用EnumerateArray()遍历,别硬用[0] - 类型检查不能少:取值前用
result.ValueKind == JsonValueKind.String判断,否则result.GetString()可能返回 null 而非抛异常 - 路径深了建议写个辅助方法,传入
JsonElement和 string[] path,逐段TryGetProperty
Newtonsoft.Json 的 SelectTokens 性能和坑点
SelectTokens 写起来爽,$.items[*].price 一行搞定,但它不是零成本魔法。底层要构建 AST、遍历整棵树,对大 JSON(>1MB)或高频调用场景,比手写 GetProperty 慢 3–5 倍。
更麻烦的是语义陷阱:比如 $..id 默认深度优先遍历所有层级,可能返回意外的嵌套字段;[?(@.status == 'active')] 中字符串比较默认区分大小写,而 System.Text.Json 默认不支持这种表达式。
- 用
SelectTokens前先JObject.Parse(json),别反复解析同一份 JSON 字符串 - 过滤表达式里避免复杂逻辑,
[?(@.x > @.y)]这种跨字段比较在某些版本会静默失败 - 结果是
IEnumerable<jtoken></jtoken>,别直接.ToList()存着——JToken 是引用,原JObject被 GC 后它就失效 - 若只取第一个匹配项,用
SelectToken(单数)比SelectTokens(复数)略快且语义更明确
什么时候该放弃 JsonPath,改用序列化模型
如果你查的路径是固定的(比如总要取 response.data.items[].name),而且结构稳定,硬写 JsonPath 或手动遍历反而增加维护成本。这时候直接定义 C# 类,用 System.Text.Json.JsonSerializer.Deserialize<t>()</t> 更安全、更快、IDE 还能补全。
JsonPath 真正有用,是当路径来自配置、用户输入或需要动态拼接时——比如 API 返回结构不统一,靠字段名开关控制提取逻辑。
- 模型类字段加
[JsonPropertyName("api_field_name")]处理命名差异 - 不确定是否存在字段,用
string?或可空类型(如int?),反序列化自动为 null - 嵌套数组用
List<t></t>,别用T[]——后者反序列化失败时不报错,而是整个对象为 null - 别为了“灵活”把所有字段都定义成
JsonElement,那等于放弃了类型安全和性能优势
真正难的从来不是怎么写路径表达式,而是判断:这个查询到底是临时调试用,还是会长期嵌在业务逻辑里。后者几乎总是该收进模型类。


















