在C#开发中,对象与JSON字符串之间的转换几乎是绕不开的操作,无论是编写Web API接口、读取配置文件,还是与第三方系统通信,都需要用到序列化与反序列化。自.NET Core 3.0开始,微软推出了官方的System.Text.Json库,它凭借更高的性能和更低的内存占用,逐渐取代了传统的Newtonsoft.Json(Json.NET),成为.NET平台默认的JSON处理方案。本文将系统讲解System.Text.Json的核心用法和常见配置技巧,帮助你快速上手并避开典型陷阱。

一、基础用法:序列化与反序列化
System.Text.Json的核心类是JsonSerializer,它提供了静态方法Serialize和Deserialize来完成对象与JSON字符串的互转。使用前需要引入命名空间System.Text.Json。先定义一个简单的实体类:
using System;
using System.Text.Json;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
class Program
{
static void Main()
{
var person = new Person { Name = "张三", Age = 28, Email = "zhangsan@ipipp.com" };
// 序列化:对象转JSON字符串
string json = JsonSerializer.Serialize(person);
Console.WriteLine(json);
// 反序列化:JSON字符串转对象
Person result = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine(result.Name);
}
}上面的代码输出结果为{"Name":"张三","Age":28,"Email":"zhangsan@ipipp.com"}。可以看到默认情况下属性名保持PascalCase原样输出,不会像Newtonsoft.Json那样有驼峰转换,这一点在迁移老项目时要特别注意。
反序列化时需要注意,目标类必须有公共的无参构造函数(或者使用[JsonConstructor]标注带参构造函数),属性也必须是可写的。如果JSON中存在目标类没有的属性,默认不会报错而是直接忽略;反过来,如果目标类的必填属性在JSON中缺失,默认也不会抛异常,会保留默认值。如果希望严格校验,可以设置JsonUnmappedMemberHandling.Disallow或使用required关键字配合.NET 8的严格模式。
二、常用序列化配置选项
直接调用Serialize的重载虽然简单,但实际项目中往往需要自定义输出格式,比如驼峰命名、缩进美化、忽略空值等。这就需要用到JsonSerializerOptions:
var options = new JsonSerializerOptions
{
// 属性名转为驼峰命名
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// 输出缩进格式化的JSON,便于阅读
WriteIndented = true,
// 忽略值为null的属性
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
// 忽略只读属性
IgnoreReadOnlyProperties = true,
// 枚举以字符串形式输出而非数字
Converters = { new JsonStringEnumConverter() }
};
string json = JsonSerializer.Serialize(person, options);配置完成后,输出会变成驼峰命名的缩进格式,null属性不再出现。这里特别提醒两点:第一,JsonStringEnumConverter非常实用,不加它时枚举会被序列化成数字,前端拿到1、2这样的值很难理解;第二,如果序列化中文后出现类似\u5F20\u4E09的转义序列,这是默认编码器出于安全考虑的结果,可以设置Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping来让中文直接输出,但要确认下游消费方能正确处理非ASCII字符。
关于选项对象的复用也有讲究。JsonSerializerOptions内部会缓存序列化元数据,重复new一个新实例会导致缓存无法命中,性能明显下降。官方建议将配置好的options声明为单例或静态字段复用,例如JsonSerializerOptions.Web就是一个内置的常用Web默认配置。此外,也可以通过特性精细控制单个属性,例如[JsonPropertyName("user_name")]指定JSON字段名,[JsonIgnore]排除某个属性。
三、处理循环引用与复杂类型
实体之间存在双向导航关系时(比如订单引用客户、客户又包含订单列表),直接序列化会抛出JsonException,提示检测到循环引用。解决方式是开启引用处理:
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve
};
// 输出的JSON会带 $id 和 $ref 元数据来保持对象关系
string json = JsonSerializer.Serialize(order, options);ReferenceHandler.Preserve会在JSON中写入$id和$ref元数据来打破循环,反序列化时也能还原对象图。这种方式虽然解决了问题,但生成的JSON不是标准格式,如果JSON要给第三方系统消费,更推荐的做法是使用DTO(数据传输对象)切断循环引用,或者在导航属性上加[JsonIgnore]。
对于字典类型,默认要求键为字符串。如果键是int或枚举,.NET 7之前需要自定义转换器,之后版本已原生支持非字符串键的字典序列化。日期类型方面,DateTime默认输出ISO 8601格式(如2024-01-15T10:30:00),如需自定义格式可以注册自定义的JsonConverter。多态序列化则需要配合[JsonDerivedType]特性,在基类上声明所有派生类型,否则派生类的特有属性会丢失。
四、性能进阶:流式与异步操作
处理大对象或高频调用场景时,字符串方式的序列化会产生大量临时字符串分配,给GC带来压力。System.Text.Json提供了基于Utf8BufferWriter和流的API来缓解这个问题:
using System.Text.Json;
// 将对象直接写入文件流,避免生成超大字符串
using (var fs = File.Create("data.json"))
{
await JsonSerializer.SerializeAsync(fs, bigOrderList, options);
}
// 从文件流异步读取并反序列化
using (var fs = File.OpenRead("data.json"))
{
var list = await JsonSerializer.DeserializeAsync<List<Order>>(fs, options);
}流式API直接操作UTF-8字节,省去了字符串编码转换的中间环节,官方基准测试显示其吞吐量比Newtonsoft.Json高出数倍,内存分配也大幅减少。对于超大JSON文档,还可以使用JsonDocument做只读解析,或用Utf8JsonReader/Utf8JsonWriter进行底层高性能读写,不过这两个API使用门槛较高,一般情况下用JsonSerializer的流式重载已经足够。
迁移方面,如果项目原本使用Newtonsoft.Json,主要差异包括:默认命名策略不同、大小写敏感的反序列化匹配、日期格式处理、JsonPropertyName替代JsonProperty等。微软提供了System.Text.Json的兼容包可以弥补部分功能差距,但建议新项目直接采用原生API,遵循官方最佳实践,既能获得性能收益,也能减少依赖。
总结一下,System.Text.Json的入门门槛并不高,掌握JsonSerializer的几个核心重载和JsonSerializerOptions的常用配置,就能覆盖绝大多数业务场景。重点记住三件事:复用options实例提升性能、处理好中文编码与枚举格式、用流式API应对大对象,你的JSON处理代码就会既高效又健壮。
C#序列化JSONSystem.Text.JsonJsonSerializer修改时间:2026-09-01 04:34:51