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

管理集合的序列化

C# SELF
等級 46 , 課堂 4
開放

1. 介紹

儘管你可能想像集合能「開箱即用」地被序列化/反序列化,但在實際專案裡情況常常沒那麼簡單。有時你需要把某些集合從序列化中隱藏——例如內部的快取資料。有時要把集合屬性重新命名以符合 API contract。還有時需要控制哪些元素被保留或忽略,甚至特別轉換集合,讓最終的 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 = "Glavnaya biblioteka",
    Books = new List<Book>
    {
        new Book { Title = "Volshebnaya dolina", Author = new Author { Name = "Tuve Yanssen", BirthYear = 1914 } }
    },
    Cache = new List<Book>
    {
        new Book { Title = "Povelitel mukh", Author = new Author { Name = "Vil'yam Golding", BirthYear = 1911 } }
    }
};

string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json); // JSON 裡沒有 Cache 屬性!

序列化結果大致如下:

{
  "Name": "Glavnaya biblioteka",
  "Books": [
    {
      "Title": "Volshebnaya dolina",
      "Author": {
        "Name": "Tuve Yanssen",
        "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 = "Filial №1", Books = new List<Book>() };
string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

輸出:

{
  "Name": "Filial №1",
  "items": []
}

注意:反序列化同樣會把 JSON 的 items 對應回 C# 的 Books——雙向對應會正常工作。

4. 管理集合與其元素的序列化

這裡會用到 JsonIgnoreCondition.WhenWritingNull 與/或 nullable 型別的概念。

有時集合只是個可選欄位。比如新建立的圖書館暫時沒有書。如果你不想在 JSON 裡看到 books: null,可以透過選項控制:

var library = new Library { Name = "Pustaya biblioteka" };
// Books 沒有初始化 = null

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    WriteIndented = true
};

string json = JsonSerializer.Serialize(library, options);
Console.WriteLine(json);

結果:

{
  "Name": "Pustaya biblioteka"
}

如果集合是空的 list(但不是 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. 用索引或巢狀結構來表示集合

有時你需要把集合序列化成不是純陣列的形式,例如作為 map(dictionary)——每本書有唯一識別碼時很常見。這種情況不需要對元素使用特別屬性,只要用 dictionary:

public class Library
{
    [JsonPropertyName("catalog")]
    public Dictionary<string, Book> BookCatalog { get; set; }
}

序列化後 dictionary 會變成鍵值對的物件:

var library = new Library
{
    BookCatalog = new Dictionary<string, Book>
    {
        ["978-5-699-12345-6"] = new Book { Title = "Slovar'", Author = new Author { Name = "Neizvestnyy", BirthYear = 2000 } }
    }
};

JSON:

{
  "catalog": {
    "978-5-699-12345-6": {
      "Title": "Slovar'",
      "Author": {
        "Name": "Neizvestnyy",
        "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]
可以 可以 把 private 屬性包含到序列化中
[JsonConverter]
可以 可以 為列表指定自訂 converter

9. 含屬性的集合序列化示意圖


+-------------+
|   Library   |
+-------------+
  | Name           -- 會序列化為 "Name"
  | Books          -- [JsonPropertyName("items")], 會序列化為 "items": [...]
  | Cache          -- [JsonIgnore], 不會序列化
  | BookCatalog    -- [JsonPropertyName("catalog")], 會序列化為 "catalog": {...}
JSON 範例結果大致如下:
{
  "Name": "Gorodskaya biblioteka",
  "items": [
    {
      "Title": "1984",
      "Author": {
        "Name": "Dzhordzh Oruell",
        "BirthYear": 1903
      }
    },
    {
      "Title": "Bol'shie nadezhdy",
      "Author": {
        "Name": "Char'lz Dikkens",
        "BirthYear": 1812
      }
    }
  ],
  "catalog": {
    "978-1234567890": {
      "Title": "Zov Ktulhu",
      "Author": {
        "Name": "Govard Filips Lavkraft",
        "BirthYear": 1890
      }
    }
  }
}

10. 實務意義與面試/專案上的重點

在生產環境中你必須考慮外部 API 的 contract 與序列化需求。要會把內部集合「藏起來」、遵守命名風格,有時還需根據客戶端版本動態改變序列化方案。

常在面試中被問到的題目:

  • 如何只序列化部分資料?
  • 怎麼讓集合屬性不出現在 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