直接用JsonSerializer.Deserialize<T>()映射多层嵌套结构最稳妥,需确保类字段名、类型与JSON严格匹配,配合JsonPropertyName、可空类型、DateTimeOffset及JsonSerializerOptions容错配置。

用 JsonSerializer.Deserialize<T>() 直接映射多层嵌套结构最稳
别一上来就用 JObject 或 JToken 手动遍历——除非你真需要动态字段或结构不固定。C# 9+ 的 System.Text.Json 原生支持深度嵌套对象自动绑定,只要类定义和 JSON 字段名、类型对得上,一行代码就能解出来。
常见错误现象:JsonSerializer.Deserialize<Root>(json) 返回 null 或某层属性为 null,大概率是字段名大小写不匹配(JSON 默认小驼峰,C# 类默认 PascalCase),或没加 [JsonPropertyName] 显式声明。
- 用
[JsonPropertyName("user_name")]标记 C# 属性,对应 JSON 中的"user_name" - 嵌套数组字段(如
"orders": [{ "id": 1 }])直接声明为public List<Order> Orders { get; set; } - 可空字段(如可能为
null的字符串)用string?,避免反序列化失败 - 遇到
"created_at": "2024-03-15T10:22:00Z"这种时间戳,属性类型用DateTimeOffset比DateTime更安全
遇到字段缺失或类型混乱时,用 JsonSerializerOptions 控制容错行为
真实接口返回的数据经常“不守规矩”:字段偶尔消失、数字有时是字符串("count": "5")、布尔值写成小写字符串("active": "true")。硬绑会直接抛 JsonException。
解决办法不是换库,而是调 JsonSerializerOptions 的几个关键开关:
-
PropertyNameCaseInsensitive = true:忽略大小写,省得每个字段都加[JsonPropertyName] -
NumberHandling = JsonNumberHandling.AllowReadingFromString:让数字字段能接受字符串形式的数值 -
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull:只影响序列化,但搭配JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)可控制输出 - 不要开
ReadCommentHandling = JsonCommentHandling.Skip—— 生产接口几乎不用注释,开了反而掩盖字段名拼写错误
JObject.Parse() 仅在字段动态、结构不可预知的场景下才值得用
比如第三方 API 返回的 "data" 是个黑盒对象,字段名由业务规则生成("field_2024_q1"、"field_2024_q2"),或者需要根据某个字段值决定后续解析路径("type": "user" → 解成 UserDto,"type": "product" → 解成 ProductDto)。
这时候 JObject 是工具,不是替代方案。容易踩的坑:
- 写
obj["items"][0]["name"].ToString()前,必须先判空:obj["items"]?.FirstOrDefault()?["name"]?.ToString() -
JToken.ToObject<T>()内部仍走JsonSerializer,所以字段名/类型问题一样存在,别以为用了JObject就能绕过契约 - 频繁用
ToString()提取值,不如直接用Value<string>("name")或Value<int?>("count"),更安全且不抛异常
别忽略 JsonSerializerOptions 的线程安全性与复用成本
每次解析都 new 一个 JsonSerializerOptions 实例,性能损耗不小;但把它当全局单例用又容易被并发修改(比如有人临时改了 Converters)。实际项目里最简做法是:按用途建静态只读实例。
- 定义
public static readonly JsonSerializerOptions ApiOptions = new() { PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; - 千万别在
ApiOptions.Converters.Add(...)后还把它传给别的模块——Converters是可变集合,加了就污染全局 - 如果某次解析要特殊处理(比如把所有
"amount"当货币转decimal),单独 new 一个带 converter 的 options,用完丢弃
嵌套越深,类定义和 JSON 的对齐细节越容易漏——尤其是中间某层字段名拼错、类型写成 int 却收到 null,或者忘了给可空引用类型加 ?。这些地方不报编译错,只在运行时静默失败或抛异常。










