1. 为什么序列化时需要属性?
当你把对象序列化成 JSON 或 XML 时,发生的事情有点像用清洁机器人“扫描”你的房间。所有明显放在外面的东西(public 属性/字段)会被放进“袋子”(文件或字符串),其他的会被忽略。但有时候你不想让机器人展示你包里的东西,或者你想把东西换个更合适的名字——比如用英文而不是俄文。
这时就用到属性了!它们让你能控制序列化过程:隐藏不想暴露的内容、改名、设定顺序、忽略对象的某些部分,或者告诉序列化器有特殊规则。就像给物品贴标签:“别动”、“重要”、“在 JSON 中重命名”。
常见的序列化控制需求
- 在输出文档中改变属性或字段的名字(比如在 JSON 中把 FirstName 变成 "first_name")
- 从序列化或反序列化中隐藏某些字段/属性(比如密码、内部计数器)
- 控制默认值或 null 值的处理方式
- 指定元素顺序(对 XML 很重要)
- 为 XML 元素描述额外参数(attributes)
每个平台的序列化器——比如 System.Text.Json、Newtonsoft.Json 或 XmlSerializer——都有自己的属性来完成这些事。
2. System.Text.Json 的属性:现代又流行
.NET 的经典 JSON 序列化器支持一组不错的属性,这些属性在命名空间 System.Text.Json.Serialization 下。
最有用的属性:
| 属性 | 用途 | 示例 |
|---|---|---|
|
把属性名转换为 JSON 中的名字 | |
|
完全从序列化中排除该属性 | |
|
序列化公共字段(不仅仅是属性) | |
|
在特定条件下忽略属性 | |
使用示例:
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
}
我们来序列化这样一个人:
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 中的属性名,也能设置顺序、是否必需等 |
|
把字段/属性从序列化中排除 |
|
反序列化时要求必须存在 |
|
可以指定自定义转换器来处理复杂情况 |
|
控制默认值的序列化行为 |
实际示例:
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 被序列化成了属性,而不是嵌套的标签。
5. 有用的小细节
内部原理:属性是怎么工作的
当序列化器遇到你的类时,它会通过反射“读”这个类(.NET 的魔法,详见 System.Reflection)。序列化器会读取元数据:比如属性上有没有 JsonIgnore 或 XmlElement。根据这些信息,它决定把哪些数据加入最终文档,或者跳过哪些数据。
这是把“数据模式”与业务逻辑分离的便捷方式。你的类是业务逻辑,属性其实就是给序列化的“护照”。
关键属性对照表
| 功能 | System.Text.Json | Newtonsoft.Json | XmlSerializer |
|---|---|---|---|
| 改名 | |
|
|
| 忽略 | |
|
|
| 自定义格式 | |
|
—(通过 IXmlSerializable,比较麻烦) |
| 根对象 | — | — | |
| 集合 | — | — | |
6. 高级场景
有时候你需要的序列化方式超出标准序列化器能做的范围。比如你想把日期存成 UNIX timestamp 而不是 ISO 字符串。这种情况可以用属性指定自定义转换器。
在 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