CodeGym /课程 /C# SELF /字典序列化

字典序列化

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

1. Введение

字典(在 C# 中通常是 Dictionary<TKey, TValue>)是键值对的集合。这种数据类型在你需要根据唯一标识快速查找值时非常有用(比如电话簿里按姓名查电话)。

和列表(List<T>)不同,列表关注元素顺序,而字典关注按键快速访问数据。但在序列化时,列表很简单(JSON 数组),而字典会有一些细节需要注意:

  • 键必须是可序列化的类型(通常是字符串,但有时也可能是数字,甚至其他对象)。
  • JSON 本身没有“字典”类型——只有对象或数组。

下面我们详细看 .NET 是如何序列化字典的,会遇到哪些问题,以及怎样正确地让代码处理这些结构。

2. Сериализация словаря с ключами-строками

先从经典情况开始——键和值都是字符串的字典。


// 示例字典:书名和作者
var books = new Dictionary<string, string>
{
    ["大师与玛格丽塔"] = "米哈伊尔·布尔加科夫",
    ["哈利·波特"] = "乔安·罗琳",
    ["蝇王"] = "威廉·戈尔丁"
};

// 序列化为 JSON 字符串
string json = JsonSerializer.Serialize(books, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine("字典已序列化为 JSON:\n" + json);

// 同步写入文件
File.WriteAllText("books.json", json);
Console.WriteLine("JSON 已写入文件 books.json。");

// 同步从文件读取
string jsonFromFile = File.ReadAllText("books.json");

// 反序列化回字典
var restoredBooks = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonFromFile);
Console.WriteLine("反序列化结果:");
foreach (var pair in restoredBooks)
    Console.WriteLine($"{pair.Key} -> {pair.Value}");

Что получится в файле books.json?

{
  "大师与玛格丽塔": "米哈伊尔·布尔加科夫",
  "哈利·波特": "乔安·罗琳",
  "战争与和平": "威廉·戈尔丁"
}

Как это работает?

序列化器会把我们的 Dictionary<string, string> 变成一个 JSON 对象,每个键成为属性名,值成为属性值。如果键是字符串且唯一,这是很方便的表示方式。

3. Словарь с нестандартным типом ключа

当键是字符串一切简单。如果键是数字怎么办?


var bookIds = new Dictionary<int, string>
{
    [1001] = "大师与玛格丽塔",
    [1002] = "哈利·波特",
    [1003] = "蝇王"
};

string jsonIntKeys = JsonSerializer.Serialize(bookIds, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonIntKeys);

结果:

{
  "1001": "大师与玛格丽塔",
  "1002": "哈利·波特",
  "1003": "蝇王"
}

Что произошло?

  • C# 把数字键转换成了字符串,因为在 JSON 中属性名只能是字符串。
  • 反序列化回 Dictionary<int, string> 时,序列化器会尝试把这些字符串再转换成数字。

Пример десериализации:


var restoredBookIds = JsonSerializer.Deserialize<Dictionary<int, string>>(jsonIntKeys);
// 一切正常!键又变回数字了。
Если ключ — сложный тип, например, объект?

var dict = new Dictionary<Author, string>
{
    [new Author { Name = "戈尔丁", BirthYear = 1911 }] = "蝇王"
};

尝试序列化这样的字典会抛出一个 异常

System.NotSupportedException: Serialization and deserialization of 'Dictionary<Author, string>' instances are not supported.

Почему так?

JSON 对象的属性名只能是字符串。因此在序列化时,字典的键必须是能够唯一映射为字符串的简单类型(通常是 string 或 number)。复杂对象不能用作键(至少用默认的序列化方式不能)。

4. Словарь с вложенными объектами в качестве значения

键已经讲清楚——现在看如果值是复杂对象(比如 BookAuthor)会怎样。

Пример


public class Author
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
}

var authorDirectory = new Dictionary<string, Author>
{
    ["bulgakov"] = new Author { Name = "米哈伊尔·布尔加科夫", BirthYear = 1891 },
    ["golding"] = new Author { Name = "威廉·戈尔丁", BirthYear = 1911 }
};

string jsonAuthors = JsonSerializer.Serialize(authorDirectory, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonAuthors);

结果:

{
  "bulgakov": {
    "Name": "米哈伊尔·布尔加科夫",
    "BirthYear": 1891
  },
  "golding": {
    "Name": "威廉·戈尔丁",
    "BirthYear": 1911
  }
}
  • 嵌套的对象会按照对象结构被序列化。
  • 反序列化回 Dictionary<string, Author> 也能正常工作。

5. Словарь в составе другого объекта

字典经常作为更复杂对象的字段使用。例如,图书馆有按流派分类的目录,每个键是流派名称,值是该流派的书单。


public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
}

public class Library
{
    public Dictionary<string, List<Book>> CatalogByGenre { get; set; }
}

var library = new Library
{
    CatalogByGenre = new Dictionary<string, List<Book>>
    {
        ["科幻"] = new List<Book>
        {
            new Book { Title = "索拉里斯", Author = "斯坦尼斯瓦夫·莱姆" }
        },
        ["经典"] = new List<Book>
        {
            new Book { Title = "蝇王", Author = "威廉·戈尔丁" },
            new Book { Title = "可见的黑暗", Author = "威廉·戈尔丁" }
        }
    }
};

string jsonLibrary = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonLibrary);

输出 JSON 片段:

{
  "CatalogByGenre": {
    "科幻": [
      {
        "Title": "索拉里斯",
        "Author": "斯坦尼斯瓦夫·莱姆"
      }
    ],
    "经典": [
      {
        "Title": "蝇王",
        "Author": "威廉·戈尔丁"
      },
      {
        "Title": "可见的黑暗",
        "Author": "威廉·戈尔丁"
      }
    ]
  }
}

一切正常——嵌套的字典和集合会递归序列化和反序列化。

6. Особенности и "подводные камни" сериализации словарей

Повторяющиеся ключи

字典中的键始终是唯一的。但如果你手动提供一个带重复键的 JSON:

{
  "foo": "first",
  "foo": "second"
}

结果:最后一个值("second")会覆盖前面的,不会抛错。这是大多数 JSON 解析器的行为。

Порядок элементов

字典是无序集合。序列化时 JSON 中键的顺序可能和原来不一样。如果顺序很关键——使用键值对列表(List<KeyValuePair<string, T>>),但通常字典的顺序无关紧要。

JSON и вложенные словари

嵌套层级没有硬性限制,但每层都必须满足 JSON 的规则(键为字符串,值为合法的 JSON 对象/数组)。

Использование JsonSerializerOptions

有时候你希望属性名不是 PascalCase 而是 camelCase,这在和前端 JavaScript 集成时很常见,因为前端通常用 camelCase。


var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

string camelJson = JsonSerializer.Serialize(authorDirectory, options);

重要: 对字典来说,这个选项只影响嵌套对象的属性名(它们会被转成 camelCase),但不会影响字典的键。字典的键会按 C# 中指定的字符串原样序列化。

7. Проблемы со сложными и нестроковыми ключами

对于带字符串键和数值键(比如 int, long, Guid)的字典,序列化通常是“开箱即用”的。但如果你尝试用自定义类或结构体作为键——会遇到 NotSupportedException

针对这种情况,有几个解决办法:

  • 使用不同的存储格式,比如把字典序列化成一组包含 "Key""Value" 字段的对象数组。
  • 写一个自定义转换器(JsonConverter),把复杂键转换成字符串并在反序列化时还原。
  • 如果键的结构真的很复杂,可能需要重新考虑架构,避免用复杂对象作为字典键。

Пример обхода через сериализацию как списка пар


public class AuthorInfo
{
    public Author Author { get; set; }
    public string Book { get; set; }
}
// 用 List<AuthorInfo> 代替 Dictionary<Author, string>
var list = new List<AuthorInfo>
{
    new AuthorInfo { Author = new Author { Name = "威廉·戈尔丁", BirthYear = 1911 }, Book = "蝇王" }
};
// 这样的列表可以直接序列化

Сравнение: словарь vs. список пар при сериализации

Тип коллекции JSON-структура Когда использовать
Dictionary<string, Book>
{ "key1": {...}, "key2": {...} }
键是简单字符串,需要快速查找和唯一性
List<KeyValuePair<string, Book>>
[{"Key": "key1", "Value": {...}}, ... ]
键是复杂类型、需要控制顺序、允许重复

8. Типовые вопросы на собеседованиях

1. Можно ли сериализовать Dictionary<DateTime, string>?
可以,但键会被转换为字符串表示(通常是 ISO 格式,类似 "yyyy-MM-ddTHH:mm:ss")。反序列化时可能会遇到地区设置和日期格式的问题。

2. Что произойдет, если сериализовать Dictionary<int, string>?
键会被序列化为字符串,即使原来是数字。反序列化回去通常没有问题。

3. Почему нельзя сериализовать словарь с объектами в качестве ключа?
因为只有字符串才能作为 JSON 对象的属性名,而对象不能。

4. А если хочется сериализовать словарь с комплексным ключом?
最好重构数据结构,或者把字典序列化为键值对列表,让键作为对象字段来序列化,而不是作为属性名。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION