当前位置: 首页 > news >正文

手把手教你用System.Text.Json搞定C#里那些‘不听话’的JSON数据(含自定义转换器实战)

手把手教你用System.Text.Json搞定C#里那些‘不听话’的JSON数据(含自定义转换器实战)

在对接第三方API时,开发者经常会遇到JSON数据结构与C#实体类不匹配的棘手问题。比如API返回的字段类型与实体类定义不符,或者JSON中存在动态属性、嵌套层级过深等情况。这些问题如果处理不当,轻则导致反序列化失败,重则引发生产环境的数据解析异常。

System.Text.Json作为.NET Core 3.0引入的官方JSON库,相比Newtonsoft.Json在性能上有显著优势,但在处理"不标准"的JSON数据时,需要掌握一些特殊技巧。本文将从一个真实API对接案例出发,演示如何用System.Text.Json的各种高级功能驯服这些"不听话"的JSON数据。

1. 理解System.Text.Json的核心机制

System.Text.Json采用了一种基于Utf8JsonReader/Writer的低分配设计,这是其性能优势的关键。与Newtonsoft.Json不同,它在默认情况下执行严格的类型检查,这既是优点也是需要特别注意的地方。

典型问题场景示例

{ "userId": "12345", "price": "99.99", "metadata": { "createdAt": "2023-07-20T12:00:00Z", "tags": ["urgent", "vip"] } }

对应的C#实体类:

public class Order { public int UserId { get; set; } // JSON中是字符串 public decimal Price { get; set; } // JSON中是字符串 public Metadata Meta { get; set; } } public class Metadata { public DateTime CreatedAt { get; set; } public List<string> Tags { get; set; } }

直接使用JsonSerializer.Deserialize<Order>(jsonString)会抛出异常,因为类型不匹配。这就是我们需要解决的问题。

2. 基础解决方案:JsonPropertyName与JsonIgnore

对于简单的字段名不匹配问题,可以使用[JsonPropertyName]特性:

public class Order { [JsonPropertyName("userId")] public int UserId { get; set; } [JsonIgnore] public decimal OriginalPrice { get; set; } [JsonPropertyName("price")] public string PriceString { get; set; } [JsonIgnore] public decimal Price => decimal.Parse(PriceString); }

这种方法适用于:

  • JSON字段名与C#属性名不一致
  • 需要忽略某些属性
  • 需要进行简单的格式转换

局限性

  • 无法处理复杂的类型转换
  • 需要手动处理null值
  • 代码会变得冗长

3. 高级技巧:自定义JsonConverter实战

对于更复杂的场景,我们需要创建自定义转换器。以下是一个处理字符串与数值类型互转的通用转换器:

public class FlexibleNumberConverter : JsonConverter<object> { public override bool CanConvert(Type typeToConvert) { return typeToConvert == typeof(int) || typeToConvert == typeof(long) || typeToConvert == typeof(decimal) || typeToConvert == typeof(double); } public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { var str = reader.GetString(); if (string.IsNullOrEmpty(str)) return null; return typeToConvert switch { Type t when t == typeof(int) => int.Parse(str), Type t when t == typeof(long) => long.Parse(str), Type t when t == typeof(decimal) => decimal.Parse(str), Type t when t == typeof(double) => double.Parse(str), _ => throw new JsonException() }; } else if (reader.TokenType == JsonTokenType.Number) { return typeToConvert switch { Type t when t == typeof(int) => reader.GetInt32(), Type t when t == typeof(long) => reader.GetInt64(), Type t when t == typeof(decimal) => reader.GetDecimal(), Type t when t == typeof(double) => reader.GetDouble(), _ => throw new JsonException() }; } throw new JsonException(); } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); } }

使用方法:

var options = new JsonSerializerOptions { Converters = { new FlexibleNumberConverter() } }; var order = JsonSerializer.Deserialize<Order>(jsonString, options);

4. 处理动态属性和复杂嵌套结构

对于包含动态属性或深度嵌套的JSON,可以使用JsonDocumentJsonElement进行灵活处理:

public class DynamicOrder { [JsonExtensionData] public Dictionary<string, JsonElement> ExtensionData { get; set; } public T GetDynamicValue<T>(string propertyName) { if (ExtensionData.TryGetValue(propertyName, out var element)) { return element.ValueKind switch { JsonValueKind.String => (T)(object)element.GetString(), JsonValueKind.Number => (T)(object)element.GetDecimal(), JsonValueKind.True => (T)(object)true, JsonValueKind.False => (T)(object)false, _ => default }; } return default; } }

使用场景对比表

场景推荐方案优点缺点
简单字段映射JsonPropertyName简单直接功能有限
复杂类型转换自定义JsonConverter灵活强大实现复杂
动态属性JsonExtensionData处理未知字段需要手动解析
临时处理JsonDocument完全控制代码量大

5. 性能优化与最佳实践

System.Text.Json的性能优势体现在多个方面:

  1. 重用JsonSerializerOptions
// 错误做法:每次创建新options var order = JsonSerializer.Deserialize<Order>(jsonString, new JsonSerializerOptions()); // 正确做法:重用options实例 private static readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true, Converters = { new FlexibleNumberConverter() } };
  1. 使用源生成器(.NET 6+)
[JsonSerializable(typeof(Order))] public partial class OrderContext : JsonSerializerContext {} // 使用生成的序列化代码 var order = JsonSerializer.Deserialize(jsonString, OrderContext.Default.Order);
  1. 异步流处理
await using var stream = File.OpenRead("large.json"); var orders = await JsonSerializer.DeserializeAsync<List<Order>>(stream, _options);

性能对比数据

  • 小型对象(<1KB):System.Text.Json快2-3倍
  • 大型对象(>100KB):快1.5-2倍
  • 连续处理1000个对象:内存占用减少30%

在实际项目中,根据JSON数据的复杂度和性能要求选择合适的处理方式。对于简单的DTO,直接使用属性标记即可;对于复杂的业务对象,自定义转换器是更好的选择;而当需要处理未知结构时,JsonElement提供了最大的灵活性。

http://www.cnnetsun.cn/news/2697487.html

相关文章:

  • 告别Spoon客户端!手把手教你用SpringCloud+Vue2搭建Kettle Web版数据集成平台
  • YOLOv8实战:手把手教你调NMS和IoU,让目标检测框更准更干净
  • 安稳顺利毕业:6款2026年高效AI论文网站深度测评
  • 构建全球虚假新闻评估网络:AI与区块链技术赋能信息可信度
  • 物联网国赛备赛指南:手把手教你用SX1276 LoRa模块实现光照传感与控制(附完整代码)
  • 基于三角剖分算法的Illustrator智能填充引擎技术解析
  • 5分钟掌握PPTist:零安装在线PPT编辑器的终极解决方案
  • 零基础小白如何学习自动化测试
  • Layerdivider终极指南:3分钟将单张图片转换为专业PSD分层文件
  • AMD Ryzen系统调试终极指南:快速掌握SMUDebugTool的实战应用
  • Qt5.15项目里QWebEngine加载网页慢到超时?别急着改源码,先试试这个Windows证书策略
  • 【限时开放】Sora 2包装结构专利图谱首次公开:含折叠应力模拟报告与环保降本17.3%关键路径
  • 【Sora 2色彩空间设置终极指南】:20年VFX总监亲授RGB/Rec.709/DCI-P3三域精准映射避坑法
  • ArcGIS栅格裁剪踩坑实录:为什么你的MaxEnt模型总报‘地理范围不匹配’?
  • 别再手动调参了!用这个R包5分钟搞定Seurat差异基因的炫酷火山图
  • 工商在册就算“在产“吗?天下工厂产业研究院怎么划那条停产边界
  • AI智能体:大模型时代的大学生进阶指南,3大方向+5步路径助你抢占先机!
  • 如何在3D打印中创建完美配合的螺纹?Fusion 360螺纹优化配置指南
  • SmolLM-360M-Instruct-openmind常见问题解答:性能优化、错误处理与最佳实践
  • DeBERTa-v3-large_boolq模型架构详解:理解DeBERTa-v3的先进技术
  • BigBird-Pegasus-large-arxiv常见问题解答:从安装到使用的全面排错指南 [特殊字符]
  • 家庭WiFi网络全面优化指南:从硬件选购到配置调优
  • 唐朝历代皇帝完整脉络全解析:贞观盛唐到晚唐落幕,二百九十年盛世沉浮
  • 微信聊天记录本地化处理实战指南:WeChatMsg深度解析
  • 你的字为什么“趴着”?王铎这幅诗轴,藏着你一直没练透的一个动作
  • 用FireWire唤醒沉睡iPod:老设备电源故障诊断与修复指南
  • 终极Cursor试用重置指南:三步快速解除AI编程助手限制的完整解决方案
  • free-solar-evo-v0.1-openmind提示词工程指南:解锁7B参数模型的文本生成潜力 [特殊字符]
  • Video2X:用AI魔法让老旧视频重获新生的完整教程
  • 如何永久保存微信聊天记录:WeChatMsg终极数据留痕解决方案