C# JSON 序列化
System.Text.Json 的 JsonSerializer、选项控制、自定义转换器、多态与源生成的速查手册,附完整示例与易错点解析。
基本序列化
基本写法:序列化为 JSON
JsonSerializer.Serialize(<对象>, [<选项>]);
// 对象转 JSON 字符串
string json = JsonSerializer.Serialize(user);
基本写法:泛型序列化
JsonSerializer.Serialize<<类型>>(<对象>);
// 显式指定类型序列化
string json = JsonSerializer.Serialize<User>(user);
基本写法:反序列化
JsonSerializer.Deserialize<<类型>>(<json>);
// JSON 字符串转对象
var user = JsonSerializer.Deserialize<User>(json);
基本写法:异步序列化到流
await JsonSerializer.SerializeAsync(<流>, <对象>);
// 异步写入流,适合大对象
using var fs = File.Create("out.json");
await JsonSerializer.SerializeAsync(fs, users);
基本写法:异步反序列化
await JsonSerializer.DeserializeAsync<<类型>>(<流>);
// 从流异步读取并反序列化
using var fs = File.OpenRead("in.json");
var data = await JsonSerializer.DeserializeAsync<List<User>>(fs);
序列化选项
基本写法:缩进格式化
JsonSerializerOptions <变量> = new() { WriteIndented = true };
// 输出带缩进的可读 JSON
var opts = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(user, opts);
基本写法:驼峰命名
<选项>.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
// 属性名转为 camelCase
var opts = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
基本写法:忽略 null 值
<选项>.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
// 值为 null 的属性不输出
var opts = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
基本写法:允许尾随逗号与注释
<选项>.ReadCommentHandling = JsonCommentHandling.Skip;
// 容忍注释与尾随逗号
var opts = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};
基本写法:大小写不敏感
<选项>.PropertyNameCaseInsensitive = true;
// 反序列化时属性名大小写不敏感
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
属性控制
基本写法:自定义属性名
[JsonPropertyName("<名称>")]
// 指定 JSON 中的属性名
public class User
{
[JsonPropertyName("user_name")]
public string Name { get; set; }
}
基本写法:忽略属性
[JsonIgnore]
// 序列化时忽略该属性
public class User
{
public string Name { get; set; }
[JsonIgnore]
public string Password { get; set; }
}
基本写法:条件忽略
[JsonIgnore(Condition = <条件>)]
// 仅在值为 null 时忽略
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Nick { get; set; }
基本写法:属性顺序
[JsonPropertyOrder(<序号>)]
// 控制属性输出顺序
public class User
{
[JsonPropertyOrder(0)]
public int Id { get; set; }
[JsonPropertyOrder(1)]
public string Name { get; set; }
}
集合与字典
基本写法:序列化集合
JsonSerializer.Serialize(<集合>);
// 列表转 JSON 数组
string json = JsonSerializer.Serialize(new List<int> { 1, 2, 3 });
基本写法:字典序列化
JsonSerializer.Serialize<<字典类型>>(<字典>);
// 字典转 JSON 对象
var dict = new Dictionary<string, int> { ["a"] = 1 };
string json = JsonSerializer.Serialize(dict);
基本写法:非字符串键字典
JsonSerializerOptions <变量> = new() { DictionaryKeyPolicy = <策略> };
// 非字符串键需要键策略或自定义转换器
var opts = new JsonSerializerOptions { DictionaryKeyPolicy = JsonNamingPolicy.CamelCase };
自定义转换器
基本写法:实现 JsonConverter
public class <类名> : JsonConverter<<类型>> { }
// 自定义类型转换器
public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type t, JsonSerializerOptions o)
=> DateTime.Parse(reader.GetString()!);
public override void Write(Utf8JsonWriter writer, DateTime v, JsonSerializerOptions o)
=> writer.WriteStringValue(v.ToString("yyyy-MM-dd"));
}
基本写法:应用转换器
<选项>.Converters.Add(new <转换器>());
// 全局注册转换器
var opts = new JsonSerializerOptions();
opts.Converters.Add(new DateTimeConverter());
基本写法:特性应用转换器
[JsonConverter(typeof(<转换器>))]
// 单属性应用转换器
public class Order
{
[JsonConverter(typeof(DateTimeConverter))]
public DateTime CreatedAt { get; set; }
}
多态序列化
基本写法:声明派生类
[JsonDerivedType(typeof(<派生类>), "<鉴别名>")]
// 基类声明所有派生类型
[JsonDerivedType(typeof(Circle), "circle")]
[JsonDerivedType(typeof(Square), "square")]
public abstract class Shape { }
基本写法:多态反序列化
JsonSerializer.Deserialize<<基类>>(<json>);
// JSON 含 $type 字段自动识别类型
Shape shape = JsonSerializer.Deserialize<Shape>(json);
流式读写 Utf8JsonReader/Writer
基本写法:Utf8JsonWriter 写入
using var <写流> = new Utf8JsonWriter(<输出>);
// 手动生成 JSON,性能最高
using var writer = new Utf8JsonWriter(File.Create("out.json"));
writer.WriteStartObject();
writer.WriteString("name", "Alice");
writer.WriteNumber("age", 30);
writer.WriteEndObject();
基本写法:Utf8JsonReader 读取
ref Utf8JsonReader <变量> = ...;
// 手动解析 JSON 字节,零分配
var reader = new Utf8JsonReader(jsonBytes);
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName) { }
}
节点模型 JsonDocument/JsonNode
基本写法:JsonDocument 只读解析
using var <文档> = JsonDocument.Parse(<json>);
// DOM 风格只读访问
using var doc = JsonDocument.Parse(json);
string name = doc.RootElement.GetProperty("name").GetString()!;
基本写法:JsonNode 可读写 DOM
JsonNode <变量> = JsonNode.Parse(<json>);
// 可读写的 DOM
JsonNode node = JsonNode.Parse(json)!;
node["age"] = 31;
string json2 = node.ToJsonString();
基本写法:创建 JsonObject
JsonObject <变量> = new() { ["<键>"] = <值> };
// 直接构造 JSON 对象
var obj = new JsonObject { ["name"] = "Alice", ["age"] = 30 };
string json = obj.ToJsonString();
源生成器
基本写法:JsonSerializerContext 源生成
[JsonSerializable(typeof(<类型>))]
// 编译时生成序列化代码,AOT 友好
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(User))]
public partial class MyContext : JsonSerializerContext { }
基本写法:使用源生成上下文
JsonSerializer.Serialize(<对象>, <Context>.Default.<类型>);
// 使用生成的元数据序列化
string json = JsonSerializer.Serialize(user, MyContext.Default.User);
完整示例:对象与 JSON 的往返
以下程序可直接运行,演示序列化、选项控制与反序列化:
using System.Text.Json;
using System.Text.Json.Serialization;
record User(int Id, string Name, string? Nick, string Password);
var user = new User(1, "张三", null, "secret");
var opts = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
string json = JsonSerializer.Serialize(user, opts);
Console.WriteLine(json);
// 反序列化:属性名大小写默认敏感,来自前端时建议开启不敏感
var copy = JsonSerializer.Deserialize<User>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
Console.WriteLine(copy!.Name);
// 输出:
// {
// "id": 1,
// "name": "张三",
// "password": "secret"
// }
// 张三
// (nick 为 null 被忽略;record 支持构造函数反序列化)
常见陷阱
属性名默认完全区分大小写。System.Text.Json 默认严格匹配 Name 与 name(这点与 Newtonsoft.Json 不同)。对接 JavaScript 前端或第三方 API 时,要么配 PropertyNamingPolicy = CamelCase 序列化 + PropertyNameCaseInsensitive = true 反序列化,要么在属性上标注 [JsonPropertyName("...")]。
复用同一个 JsonSerializerOptions 实例。首次使用某组选项时,序列化器要为相关类型构建并缓存元数据,这一步开销不小。Web 应用中应把选项缓存为单例(ASP.NET Core 会自动缓存 JsonOptions);每次 new JsonSerializerOptions 会让缓存失效,造成隐性性能损耗。
循环引用会直接抛异常。Order.Customer.Address 又指回 Order 这类对象图,默认配置下抛 JsonException(.NET 5+)而非死循环。双向导航属性序列化需要 ReferenceHandler.Preserve(输出 $id/$ref),更常见的解法是用 DTO 只序列化需要的单向数据。
不可变类型与 record。record 与只有 get、带构造函数的类型都能反序列化——序列化器按参数名匹配构造函数参数(可用 [JsonConstructor] 指定)。但带 init 且无构造函数匹配路径的极端自定义类型需要注意:record 是”开箱即用”的最安全选择。
DictionaryKeyPolicy 只影响序列化。它控制字典键的命名策略(输出时转 camelCase),反序列化时并不做反向转换;非字符串键(如 Dictionary<int, T>)会被转成字符串键,读回来需要自定义转换器。API 设计上应尽量使用字符串键字典。
默认不支持多态序列化。声明类型是基类时,派生类的额外属性不会输出;必须用 [JsonDerivedType] 在基类上显式声明派生类型(配合类型鉴别符 $type 或自定义鉴别符字段),反序列化才能还原出正确子类。这是与 Newtonsoft.Json 行为差异最大的点之一。
源生成是 AOT/trimming 的硬要求。PublishAot 或 PublishTrimmed 的应用不能依赖运行时反射,必须改用 JsonSerializerContext 源生成(见上文)并把上下文类型传给 Serialize/Deserialize。普通应用也建议在热路径使用源生成:更快、更省内存。
分层小结
- 记住:
JsonSerializer.Serialize/Deserialize;WriteIndented与CamelCase两个高频选项;[JsonPropertyName]改名、[JsonIgnore]忽略。 - 理解:选项实例应复用;大小写默认敏感;循环引用与多态需要显式声明;源生成与 AOT 的关系。
- 应用:对外 API 统一封装一份全局
JsonSerializerOptions;模型层全面使用record+ 特性标注;高并发/AOT 场景切换到JsonSerializerContext。Web 场景的整体接入见 C# Web API。