CodeGym /课程 /C# SELF /管理集合序列化

管理集合序列化

C# SELF
第 46 级, 课程 4
可用

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. 表格:主要特性的适用范围

特性 可以应用到集合吗? 可以应用到集合元素吗? 使用示例
[JsonIgnore]
是 是 隐藏列表或 Book 内的字段
[JsonPropertyName]
是 是 把 Books 重命名为 items,或把 Title 重命名为 name
[JsonInclude]
是 是 将私有属性包含到序列化中
[JsonConverter]
是 是 为列表指定自定义转换器

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,以及对集合内容的认真处理。

2
任务
C# SELF, 第 46 级, 课程 4
已锁定
重命名集合属性
重命名集合属性
1
调查/小测验
集合的序列化第 46 级,课程 4
不可用
集合的序列化
嵌套和层级对象的序列化
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION