1. 為什麼在序列化時需要屬性?
當你把物件序列化成 JSON 或 XML 時,發生的情況有點像一台掃地機器人在打掃你的房間。所有明顯擺在外面的東西(public 屬性/欄位)都會被「裝進袋子」(寫進檔案或字串),其他的則被忽略。但有時你不想讓機器人露出你包包裡的內容,或想把東西改個更好理解的名字——例如,不用俄文而用英文。
這時屬性就登場了!屬性讓你能控制序列化流程:隱藏不需要的資料、修改名稱、標註順序、忽略物件的某些部分,或告訴序列化器你有特殊規則。就像貼在東西上的貼紙:不要動、很重要、在 JSON 中改名。
常見的序列化控制任務
- 在輸出檔案中改變屬性或欄位的名稱(例如,把 FirstName 變成 JSON 裡的 "first_name")
- 在序列化或反序列化時隱藏某些欄位/屬性(比如密碼、內部計數器)
- 控制預設值或 null 值的處理方式
- 指定元素的順序(對 XML 特別重要)
- 為 XML 元素描述額外參數(attributes)
每個序列化平台——不論是 System.Text.Json、Newtonsoft.Json 還是 XmlSerializer——都有自己的一組屬性來完成這些任務。
2. System.Text.Json 的屬性:現代又流行
.NET 的內建 JSON 序列化器支援相當實用的一系列屬性,這些屬性位於命名空間 System.Text.Json.Serialization。
最有用的屬性:
| 屬性 | 用途 | 使用範例 |
|---|---|---|
|
將屬性名稱轉換成 JSON 裡的名稱 | |
|
完全排除該屬性不參與序列化 | |
|
包含 public 欄位的序列化(不只針對屬性) | |
|
在特定條件下忽略該屬性 | |
使用範例:
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("first_name")]
public string FirstName { get; set; } // 在 JSON 裡的名稱會是 'first_name'
[JsonIgnore]
public string Password { get; set; } // 不會出現在 JSON
[JsonPropertyName("born_year")]
public int? YearOfBirth { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Nickname { get; set; } // 如果是 null 就不會出現在 JSON
}
我們來序列化這個 person:
var person = new Person
{
FirstName = "Ivan",
Password = "123456",
YearOfBirth = 2000,
Nickname = null
};
string json = JsonSerializer.Serialize(person);
// json: {"first_name":"Ivan","born_year":2000}
注意:密碼和暱稱都沒有出現在 JSON!(密碼是因為永遠被忽略,暱稱則是在它是 null 時被忽略)。
為什麼這很重要? 實務上你通常不希望前端(或壞人)看到像密碼、token、計數器、內部時間戳等敏感資料。用 [JsonIgnore] 這類屬性可以一行搞定。
3. Newtonsoft.Json 的屬性:靈活的旗艦
如果你使用 Newtonsoft.Json(官方文件在這裡),可用的功能更多,對於舊專案也比較友善。
主要的屬性:
| 屬性 | 用途 |
|---|---|
|
設定 JSON 裡的屬性名稱,還能設定順序、是否必要等 |
|
將欄位/屬性排除在序列化之外 |
|
反序列化時要求該欄位一定存在 |
|
允許指定自訂 converter 處理複雜情況 |
|
控制預設值的序列化方式 |
實作範例:
using Newtonsoft.Json;
public class UserProfile
{
[JsonProperty("login")]
public string Username { get; set; }
[JsonIgnore]
public string InternalNotes { get; set; }
[JsonProperty(Required = Required.Always)]
public string Email { get; set; }
}
JsonProperty 還有更多選項——例如 Required(必要性)、Order(順序)等等。例如:
[JsonProperty("id", Order = 1, Required = Required.Always)]
public int Id { get; set; }
4. XML 的屬性:經典風格
XmlSerializer 有自己一套屬性,位於命名空間 System.Xml.Serialization。
常見屬性:
| 屬性 | 用途 |
|---|---|
|
在 XML 中作為一個元素,並使用不同的名稱 |
|
把屬性轉成 XML 元素的 attribute |
|
排除該屬性或欄位不做 XML 序列化 |
|
對集合指定 XML 陣列的名稱 |
|
對集合指定陣列中單一項目的名稱 |
|
改變 XML 根標籤的名稱 |
實作範例:
using System.Xml.Serialization;
[XmlRoot("human")]
public class Person
{
[XmlElement("firstname")]
public string Name { get; set; }
[XmlAttribute("years")]
public int Age { get; set; }
[XmlIgnore]
public string Secret { get; set; }
}
var person = new Person { Name = "Anna", Age = 32, Secret = "42" };
序列化後的 XML 大致會像這樣:
<human years="32"><firstname>Anna</firstname></human>
注意 Secret 沒有出現在 XML 裡,而 Age 被序列化為 attribute,而不是內嵌的 tag。
5. 有用的細節
底層運作:屬性如何生效
當序列化器遇到你的類別時,它會透過反射去「讀」你的型別(.NET 的 magic,詳見 System.Reflection)。序列化器會讀取元資料:例如屬性上有沒有 JsonIgnore 或 XmlElement。根據這些資訊,它會決定把哪些資料加入輸出文件,或者跳過哪些欄位。
這是把「資料結構描述」和業務邏輯分離的一個方便方式。你的類別是業務邏輯,而屬性本質上就是序列化的說明文件。
關鍵屬性對照表
| 功能 | System.Text.Json | Newtonsoft.Json | XmlSerializer |
|---|---|---|---|
| 改名 | |
|
|
| 忽略 | |
|
|
| 自訂格式 | |
|
—(透過 IXmlSerializable,比較麻煩) |
| 物件根 | — | — | |
| 集合 | — | — | |
6. 進階情境
有時你需要的序列化行為不是標準序列化器預設的。例如你想把日期以 UNIX timestamp 存,而不是一般的 ISO 字串。這時你可以用屬性掛上自訂 converter。
在 System.Text.Json 中:
public class UnixDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).UtcDateTime;
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteNumberValue(new DateTimeOffset(value).ToUnixTimeSeconds());
}
public class LogEntry
{
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime EventTime { get; set; }
}
現在序列化時 EventTime 會變成一個數字,而不是日期字串。
在 Newtonsoft.Json 中:
public class BoolToYesNoConverter : JsonConverter<bool>
{
public override void WriteJson(JsonWriter writer, bool value, JsonSerializer serializer)
=> writer.WriteValue(value ? "yes" : "no");
public override bool ReadJson(JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer)
=> (string)reader.Value == "yes";
}
public class Answer
{
[JsonConverter(typeof(BoolToYesNoConverter))]
public bool IsCorrect { get; set; }
}
現在布林值會被序列化成 "yes" 或 "no",反序列化回來則變成 true 或 false。
7. 特點與坑
當你開始使用屬性時,記得:不同的序列化器使用不同的屬性。如果你同時處理 JSON 與 XML(或同時用到 Newtonsoft.Json 與 System.Text.Json),不要忘了同一個欄位可能需要標上兩個不同的屬性,否則會有驚喜。
如果你沒有用屬性覆寫,序列化器會採用預設的屬性名稱。
繼承要小心:子類別會繼承父類別的 public 欄位/屬性,如果父類別上有屬性,這些元資料也會套用到子類。這經常讓人覺得奇怪,但這是預期行為。
常見錯誤: 很多人不小心把本該要序列化的重要欄位標成 JsonIgnore(或反過來忘了忽略敏感資料)。或是把 XmlElement 標到 private 欄位上,然後納悶為什麼序列化器看不到它(XmlSerializer 只處理 public 成員!)。
GO TO FULL VERSION