1. 介绍
尽管我们很希望集合能够“开箱即用”地完美序列化/反序列化,但在真实项目里往往不是这样。有时候你需要把某些集合从序列化中隐藏——比如内部缓存数据。有时需要把集合属性重命名以符合外部 API 的约定。还会遇到需要控制哪些元素被保存或忽略,甚至对集合做特殊转换,让最终的 JSON 对其他服务更“友好”、更可读。
幸好,System.Text.Json 提供了简单且透明的方式来控制序列化,通过可以同时应用到集合和单个元素的特性来实现。接下来我们会在图书馆的示例模型上逐步演示这些用法。
2. 排除集合属性: [JsonIgnore]
先从简单的说起。有时候类里有个集合不能序列化——比如临时缓存或敏感数据。怎么办?当然是用 [JsonIgnore]!
假设有个类 Library,里面有个 List<Book> 类型的属性 Cache,只用于快速访问:
using System.Text.Json.Serialization;
public class Library
{
public string Name { get; set; }
public List<Book> Books { get; set; }
[JsonIgnore]
public List<Book> Cache { get; set; } // 不会被序列化!
}
// 使用示例:
var library = new Library
{
Name = "主图书馆",
Books = new List<Book>
{
new Book { Title = "魔法谷", Author = new Author { Name = "图芙·扬森", BirthYear = 1914 } }
},
Cache = new List<Book>
{
new Book { Title = "蝇王", Author = new Author { Name = "威廉·戈尔丁", BirthYear = 1911 } }
}
};
string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json); // 在 JSON 中没有 Cache 属性!
序列化结果大致如下:
{
"Name": "主图书馆",
"Books": [
{
"Title": "魔法谷",
"Author": {
"Name": "图芙·扬森",
"BirthYear": 1914
}
}
]
}
看到了吗?任何标注了 [JsonIgnore] 的东西都会被隐藏,像你脑子里的 Wi-Fi 密码一样保密。
3. 使用 [JsonPropertyName] 重命名集合
经常遇到外部 API 希望看到 "items" 而不是 "Books"?或者你不想改 C# 里的字段名(免得混乱),但 JSON 里要另外的名字?
示例如下:
using System.Text.Json.Serialization;
public class Library
{
public string Name { get; set; }
[JsonPropertyName("items")]
public List<Book> Books { get; set; }
[JsonIgnore]
public List<Book> Cache { get; set; }
}
// 序列化:
var library = new Library { Name = "分馆 №1", Books = new List<Book>() };
string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
输出:
{
"Name": "分馆 №1",
"items": []
}
注意,反序列化也会把 JSON 中的 items 正确映射回 C# 的 Books —— 魔法是双向的。
4. 管理集合及其元素的序列化
为此你会用到 JsonIgnoreCondition.WhenWritingNull 和/或 nullable 类型。
有时候集合只是个可选字段。比如新建的图书馆可能还没有书。如果你不想在 JSON 中看到 books: null,可以通过选项来控制:
var library = new Library { Name = "空图书馆" };
// Books 未初始化 = null
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = true
};
string json = JsonSerializer.Serialize(library, options);
Console.WriteLine(json);
结果:
{
"Name": "空图书馆"
}
如果你有一个空列表(但不是 null),序列化器会输出 "books": []。这两者是有区别的:有时你希望当字段为 null 时刻意隐藏它,但空列表则应保留。
5. 在元素属性上使用 [JsonIgnore]
序列化特性也作用于集合内的元素。你可以把列表中每个对象的某些属性隐藏掉。
public class Book
{
public string Title { get; set; }
public Author Author { get; set; }
[JsonIgnore]
public string InternalCode { get; set; }
}
现在当序列化 Books 集合里的 Book 时,字段 InternalCode 不会出现在 JSON 里。
6. 通过“索引”或嵌套结构访问集合
有时候你需要把集合不是当数组序列化,而是像“映射”(dictionary)那样——比如每本书都有唯一 ID。这种情况下不需要对元素做奇技淫巧,只要声明一个字典属性即可:
public class Library
{
[JsonPropertyName("catalog")]
public Dictionary<string, Book> BookCatalog { get; set; }
}
序列化时字典会变成键值对组成的对象:
var library = new Library
{
BookCatalog = new Dictionary<string, Book>
{
["978-5-699-12345-6"] = new Book { Title = "词典", Author = new Author { Name = "未知", BirthYear = 2000 } }
}
};
JSON:
{
"catalog": {
"978-5-699-12345-6": {
"Title": "词典",
"Author": {
"Name": "未知",
"BirthYear": 2000
}
}
}
}
这种表示适合 API 场景,能保留键和值之间的映射关系。
7. 在管理集合序列化时的错误和困难
如果尝试序列化一个集合,而集合中并非所有元素都被正确初始化(比如列表里有 null),默认情况下 System.Text.Json 会在数组中写入这些 null 元素。
即便你设置了 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,数组内的 null 元素仍会保留——该规则针对的是对象的属性,而不是集合的内容。要避免这种情况,先清理集合:使用 RemoveAll(b => b == null)。
常见的反序列化错误是名字不匹配。如果忘了写 [JsonPropertyName],类会期待 Books,但你发来了 JSON 里的 items:结果集合不会被填充,仍然是空的。务必检查名字是否一致!
8. 表格:主要特性的适用范围
| 特性 | 可以应用到集合吗? | 可以应用到集合元素吗? | 使用示例 |
|---|---|---|---|
|
是 | 是 | 隐藏列表或 Book 内的字段 |
|
是 | 是 | 把 Books 重命名为 items,或把 Title 重命名为 name |
|
是 | 是 | 将私有属性包含到序列化中 |
|
是 | 是 | 为列表指定自定义转换器 |
9. 带特性的集合序列化示意图
+-------------+
| Library |
+-------------+
| Name -- 序列化为 "Name"
| Books -- [JsonPropertyName("items")],序列化为 "items": [...]
| Cache -- [JsonIgnore],不序列化
| BookCatalog -- [JsonPropertyName("catalog")],序列化为 "catalog": {...}
JSON 结果大概如下:
{
"Name": "城市图书馆",
"items": [
{
"Title": "1984",
"Author": {
"Name": "乔治·奥威尔",
"BirthYear": 1903
}
},
{
"Title": "远大前程",
"Author": {
"Name": "查尔斯·狄更斯",
"BirthYear": 1812
}
}
],
"catalog": {
"978-1234567890": {
"Title": "克苏鲁的呼唤",
"Author": {
"Name": "霍华德·菲利普斯·洛夫克拉夫特",
"BirthYear": 1890
}
}
}
}
10. 实践意义以及在面试和真实项目中的注意点
在“实战”中你总要考虑外部 API 的合约和序列化的要求。要会“藏”内部集合、遵守大小写和命名风格,有时还需要根据客户端版本动态改变序列化方案。
面试中常见的问题:
- 如何只序列化部分数据?
- 如何让集合属性不出现在 JSON 中?
- 如果 C# 属性名和 JSON 名称不一样,如何映射?
- 能否把集合中的某些元素从序列化中隐藏(比如敏感信息)?
答案通常围绕着合理使用特性和序列化选项:[JsonIgnore]、[JsonPropertyName]、JsonSerializerOptions,以及对集合内容的认真处理。
GO TO FULL VERSION