1. .NET对象转JSON的常见场景与需求
在.NET开发中,对象与JSON之间的转换是日常开发中最频繁的操作之一。无论是Web API的请求响应、前端数据交互,还是微服务间的通信,JSON作为轻量级的数据交换格式都扮演着关键角色。根据我的项目经验,一个中等规模的Web应用平均每天要进行上万次这样的转换操作。
为什么这个操作如此重要?首先,JSON格式具有良好的可读性和跨平台特性,几乎被所有现代编程语言支持。其次,在前后端分离架构中,JSON是前后端通信的事实标准。更重要的是,合理的序列化配置直接影响API的性能和安全性。
2. 基础序列化方式:JsonSerializer
2.1 System.Text.Json基础用法
.NET Core 3.0开始引入的System.Text.Json是目前官方推荐的JSON处理库,它相比之前的Newtonsoft.Json有更好的性能表现。基础用法非常简单:
using System.Text.Json; var user = new User { Id = 1, Name = "张三" }; string json = JsonSerializer.Serialize(user); // 输出: {"Id":1,"Name":"张三"} User deserializedUser = JsonSerializer.Deserialize<User>(json);这个基础版本已经能满足大多数简单场景,但实际项目中我们通常需要更多控制:
2.2 常用配置选项
通过JsonSerializerOptions可以定制序列化行为:
var options = new JsonSerializerOptions { WriteIndented = true, // 美化输出 PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 驼峰命名 IgnoreNullValues = true, // 忽略null值 Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping // 宽松的字符编码 }; string json = JsonSerializer.Serialize(user, options);重要提示:JavaScriptEncoder.UnsafeRelaxedJsonEscaping会减少不必要的转义,但可能带来XSS风险,仅限在可信环境中使用。
2.3 性能优化技巧
- 重用JsonSerializerOptions实例:每次创建新实例会导致策略缓存失效
- 使用Source Generation:.NET 6+支持编译时代码生成
[JsonSerializable(typeof(User))] public partial class UserContext : JsonSerializerContext {} // 使用生成的序列化器 string json = JsonSerializer.Serialize(user, UserContext.Default.User);3. 高级序列化控制
3.1 自定义属性名称
通过[JsonPropertyName]特性可以指定属性在JSON中的名称:
public class User { [JsonPropertyName("user_id")] public int Id { get; set; } [JsonIgnore] // 完全忽略该属性 public string Password { get; set; } }3.2 处理循环引用
当对象存在循环引用时,默认会抛出异常。可以通过以下方式处理:
options.ReferenceHandler = ReferenceHandler.Preserve; // 输出: {"$id":"1","Id":1,"Manager":{"$ref":"1"}}3.3 多态序列化
处理继承体系时,需要特别配置:
[JsonDerivedType(typeof(Student), typeDiscriminator: "student")] [JsonDerivedType(typeof(Teacher), typeDiscriminator: "teacher")] public class Person { ... } var people = new List<Person> { new Student(), new Teacher() }; string json = JsonSerializer.Serialize(people, options); // 输出包含类型鉴别器字段4. Newtonsoft.Json的对比使用
4.1 基本差异
虽然System.Text.Json是官方推荐,但Newtonsoft.Json(Json.NET)仍广泛使用,主要区别:
| 特性 | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| 性能 | 更高 | 较低 |
| 内存分配 | 更少 | 较多 |
| 功能丰富度 | 基础 | 全面 |
| 依赖注入支持 | 内置 | 需扩展 |
4.2 典型使用场景
Newtonsoft.Json在以下场景仍有优势:
- 需要复杂转换逻辑时
- 处理动态JSON时
- 需要更灵活的类型转换时
// Newtonsoft.Json的动态处理 dynamic obj = JObject.Parse(json); string name = obj.Name; // 自定义转换器 public class DateTimeConverter : JsonConverter<DateTime> { public override DateTime ReadJson(...) { ... } public override void WriteJson(...) { ... } }5. 实战中的性能优化
5.1 基准测试对比
通过BenchmarkDotNet测试不同方案的性能:
| 方法 | 均值(ns) | 分配内存 |
|---|---|---|
| System.Text.Json | 120 | 192 B |
| Newtonsoft.Json | 350 | 512 B |
| Source Generation | 85 | 128 B |
5.2 内存池的使用
对于高频场景,使用ArrayBufferWriter减少GC压力:
var buffer = new ArrayBufferWriter<byte>(); using var writer = new Utf8JsonWriter(buffer); JsonSerializer.Serialize(writer, user); // 从buffer.WrittenSpan获取结果5.3 异步序列化
处理大对象时使用异步避免阻塞:
await using var stream = new MemoryStream(); await JsonSerializer.SerializeAsync(stream, largeObject);6. 安全注意事项
6.1 反序列化风险
不安全的反序列化可能导致:
- 类型注入攻击
- 敏感数据泄露
- 服务拒绝
防护措施:
var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { ValidateTypeInfo } } }; static void ValidateTypeInfo(JsonTypeInfo typeInfo) { if (typeInfo.Type == typeof(FileInfo)) throw new NotSupportedException("危险类型禁止反序列化"); }6.2 深度限制
防止栈溢出攻击:
options.MaxDepth = 32; // 默认647. 特殊场景处理
7.1 日期时间格式
统一日期格式的最佳实践:
options.Converters.Add(new DateTimeConverter("yyyy-MM-dd HH:mm:ss"));7.2 枚举处理
控制枚举的序列化方式:
options.Converters.Add(new JsonStringEnumConverter()); // 作为字符串7.3 字典键的处理
自定义字典键的序列化:
options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;8. 调试与问题排查
8.1 常见异常处理
- JsonException: 格式错误时抛出
- NotSupportedException: 不支持的类型
- ArgumentException: 无效参数
8.2 诊断工具
使用JsonDocument分析JSON结构:
using JsonDocument doc = JsonDocument.Parse(json); foreach (JsonProperty prop in doc.RootElement.EnumerateObject()) { Console.WriteLine($"{prop.Name}: {prop.Value}"); }8.3 性能分析
使用DiagnosticSource监听序列化事件:
var listener = new JsonEventListener(); using IDisposable subscription = DiagnosticListener.AllListeners.Subscribe(listener); class JsonEventListener : IObserver<DiagnosticListener> { public void OnNext(DiagnosticListener listener) { if (listener.Name == "System.Text.Json") { listener.Subscribe(new JsonEventCollector()); } } // 其他接口实现... }在实际项目中,我遇到过因为不当的序列化配置导致API性能下降50%的情况。经过分析发现是频繁创建JsonSerializerOptions实例导致的。通过将其缓存为静态实例,性能立即恢复到正常水平。另一个常见问题是循环引用导致的栈溢出,这在对象关系复杂的领域模型中尤其常见,需要特别注意处理。