1. .NET对象转JSON的常见场景与需求
在.NET开发中,对象与JSON之间的转换几乎每天都会遇到。无论是Web API的响应、前端数据交互,还是微服务间的通信,JSON作为轻量级的数据交换格式已经成为事实标准。我见过太多项目因为序列化方式选择不当导致的性能问题和兼容性坑,今天就来系统梳理下.NET中对象转JSON的各种姿势。
为什么需要多种转换方式?不同的场景对JSON序列化有不同要求:
- Web API开发需要完善的循环引用处理
- 高性能场景关注序列化速度
- 移动端交互可能需要更紧凑的JSON结构
- 旧系统维护要兼容特定的日期格式
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 内置方案:JavaScriptSerializer
这是.NET Framework时代最古老的方案,位于System.Web.Extensions程序集。虽然现在看起来有点过时,但在维护老系统时还是会遇到:
csharp复制var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(new { Name = "张三", Age = 30 });
特点:
- 最早支持的JSON序列化器
- 需要手动处理循环引用
- 性能较差(比后面的方案慢3-5倍)
- 不支持.NET Core/.NET 5+
实际项目中除非维护旧代码,否则不建议使用。我曾接手过一个老系统,就因为盲目替换这个序列化器导致日期格式全部崩溃。
3. 主流方案:Json.NET(Newtonsoft.Json)
Newtonsoft.Json是.NET生态中使用最广泛的JSON库,直到现在仍是许多项目的首选。通过NuGet安装:
bash复制Install-Package Newtonsoft.Json
基础用法:
csharp复制var product = new Product { Name = "Laptop", Price = 999.99m };
string json = JsonConvert.SerializeObject(product);
核心特性:
3.1 高级序列化控制
csharp复制JsonConvert.SerializeObject(product, new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore,
DateFormatString = "yyyy-MM-dd",
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
3.2 处理复杂场景
csharp复制// 循环引用处理
settings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
// 动态忽略属性
settings.ContractResolver = new DynamicContractResolver(
shouldSerialize: prop => !prop.Name.StartsWith("Internal"));
性能对比:
| 操作 | 耗时(ms) |
|---|---|
| 简单对象 | 120 |
| 复杂对象 | 450 |
| 大数组 | 2200 |
我在电商项目中处理订单数据时,通过配置ContractResolver实现了敏感字段的自动过滤,比手动处理效率提升70%
4. 官方方案:System.Text.Json
.NET Core 3.0开始引入的官方库,性能优于Json.NET:
csharp复制var options = new JsonSerializerOptions {
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string json = JsonSerializer.Serialize(product, options);
关键优势:
4.1 极致性能
- 比Json.NET快2-3倍
- 内存分配更少
- 支持Utf8直接读写
csharp复制// Utf8JsonWriter直接写入
var buffer = new ArrayBufferWriter<byte>();
using var writer = new Utf8JsonWriter(buffer);
writer.WriteStartObject();
writer.WriteString("name", "value");
writer.WriteEndObject();
4.2 特性对比
| 特性 | Newtonsoft.Json | System.Text.Json |
|---|---|---|
| 默认忽略null | 需配置 | 是 |
| 循环引用 | 支持 | 有限支持 |
| 动态类型 | 支持 | 有限支持 |
| 自定义转换器 | 完善 | 需要更多代码 |
实际踩坑: 在迁移旧项目时,发现System.Text.Json对Dictionary的key类型限制更严格,遇到非字符串key需要特别处理。
5. 高性能方案:Utf8Json与Jil
对于极致性能要求的场景:
5.1 Utf8Json
csharp复制var json = Utf8Json.JsonSerializer.Serialize(new { Message = "Hello" });
特点:
- 零内存分配的序列化
- 支持异步IO
- 需要预编译模型
5.2 Jil
csharp复制string json = Jil.JSON.Serialize(
data,
Options.ISO8601IncludeInherited);
基准测试结果(序列化10000次):
| 库 | 耗时(ms) |
|---|---|
| Newtonsoft | 420 |
| System.Text.Json | 180 |
| Utf8Json | 90 |
| Jil | 70 |
在日志处理服务中,我从Newtonsoft切换到Jil后,序列化时间从占总处理时间的15%降到3%
6. 特殊场景处理方案
6.1 循环引用问题
csharp复制// System.Text.Json的解决方案
options.ReferenceHandler = ReferenceHandler.Preserve;
// 输出会包含$id和$ref元数据
6.2 多态序列化
csharp复制[JsonDerivedType(typeof(Student), "student")]
public class Person { }
var person = new Student();
var json = JsonSerializer.Serialize<Person>(person); // 包含类型判别器
6.3 自定义转换器
处理特殊的DateTime格式:
csharp复制public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(...) { /* 解析逻辑 */ }
public override void Write(...) { /* 写入逻辑 */ }
}
7. 实战经验与性能优化
-
缓存JsonSerializerOptions:每次new选项会造成性能损失
csharp复制private static readonly JsonSerializerOptions _options = new() { ... }; -
使用源生成器(.NET 6+):
csharp复制[JsonSerializable(typeof(Product))] public partial class AppJsonContext : JsonSerializerContext { } -
批量处理技巧:
csharp复制// 不好的做法:循环内序列化 foreach(var item in list) { Serialize(item); } // 好的做法:整体序列化 Serialize(list); -
内存优化:
csharp复制// 使用ArrayBufferWriter代替MemoryStream var buffer = new ArrayBufferWriter<byte>(); using var writer = new Utf8JsonWriter(buffer);
我在处理物联网设备数据时,通过组合使用源生成器和ArrayBufferWriter,使序列化吞吐量从1万条/秒提升到8万条/秒。
8. 各方案选型指南
根据场景选择最合适的工具:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 常规Web API | System.Text.Json | 官方支持,性能平衡 |
| 旧系统维护 | Newtonsoft.Json | 兼容性好 |
| 高频小对象 | Jil | 极致速度 |
| 大文件处理 | Utf8Json | 流式处理 |
| 需要最大控制 | Newtonsoft.Json | 功能最全 |
版本兼容性提醒:
- .NET Framework 4.x:优先Newtonsoft
- .NET Core 3.1+:可混合使用
- .NET 6+:首选System.Text.Json+源生成
9. 常见问题排查
-
日期格式不一致:
- Newtonsoft默认ISO8601
- System.Text.Json默认带时区
-
大小写策略冲突:
csharp复制// 统一配置命名策略 options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; -
异步序列化卡顿:
- 避免在async方法中同步序列化大对象
- 使用PipeWriter实现真异步
-
内存泄漏:
- JsonSerializerOptions缓存时要小心
- 避免频繁创建JsonDocument
最近帮同事排查的一个典型问题:ASP.NET Core中混合使用Newtonsoft和System.Text.Json导致模型绑定失败,最终通过统一配置解决:
csharp复制services.AddControllers()
.AddJsonOptions(options => { /* 配置 */ })
.AddNewtonsoftJson(); // 明确使用Newtonsoft
每种序列化方案都有其适用场景,没有绝对的好坏之分。我的经验法则是:新项目直接用System.Text.Json并开启源生成,老项目逐步迁移,性能关键路径考虑Jil或Utf8Json。当遇到复杂序列化需求时,Newtonsoft.Json仍然是功能最全面的选择。
