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. 表格:主要屬性在哪些情況下可用
| 屬性 | 可以用在集合上嗎? | 可以用在集合元素上嗎? | 使用範例 |
|---|---|---|---|
|
可以 | 可以 | 隱藏整個列表或 Book 裡的欄位 |
|
可以 | 可以 | 把 Books 改名為 items,或把 Title 改名為 name |
|
可以 | 可以 | 把 private 屬性包含到序列化中 |
|
可以 | 可以 | 為列表指定自訂 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 及對集合內容的謹慎處理。
GO TO FULL VERSION