1. Attributes để làm gì khi serialization?
Khi bạn serialize objects thành JSON hoặc XML, có cái gì đó giống như robot dọn phòng quét xung quanh. Mọi thứ nằm rõ ràng (public properties/fields) sẽ được bỏ vào "túi" (vào file hoặc string), còn phần còn lại bị bỏ qua. Nhưng đôi khi bạn không muốn robot vô tình lộ nội dung chiếc túi của bạn, hoặc muốn đổi tên mấy thứ cho đẹp — ví dụ không phải tiếng Nga mà là tiếng Anh.
Đó là lúc attributes xuất hiện! Chúng cho phép điều khiển quá trình serialization: ẩn những thứ thừa, đổi tên, gắn thứ tự, bỏ qua một số phần của object hoặc thông báo cho serializer rằng bạn có rules đặc biệt. Giống như dán nhãn lên đồ: "đừng đụng", "quan trọng", "đổi tên trong JSON".
Các tác vụ điển hình để kiểm soát serialization
- Đổi tên property hoặc field trong tài liệu xuất ra (ví dụ trong JSON thay FirstName bằng "first_name")
- Ẩn một số fields/properties khỏi serialization hoặc deserialization (ví dụ password, counters nội bộ)
- Quản lý cách xử lý giá trị mặc định hoặc null-values
- Đặt thứ tự các phần tử (cần thiết cho XML)
- Mô tả các tham số bổ sung (attributes) cho XML-elements
Mỗi platform serialization — dù là System.Text.Json, Newtonsoft.Json hay XmlSerializer — đều có bộ attributes riêng để làm mấy việc này.
2. Attributes cho System.Text.Json: hiện đại và được ưa thích
Serializer JSON tiêu chuẩn của .NET hỗ trợ một danh sách attributes khá tốt, nằm trong namespace System.Text.Json.Serialization.
Các attributes hữu ích nhất:
| Attribute | Để làm gì | Ví dụ sử dụng |
|---|---|---|
|
Chuyển tên property trong JSON | |
|
Loại bỏ hoàn toàn property khỏi serialization | |
|
Serialize public field (không chỉ property) | |
|
Bỏ qua property trong một số điều kiện | |
Ví dụ sử dụng:
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("first_name")]
public string FirstName { get; set; } // Tên trong JSON sẽ là 'first_name'
[JsonIgnore]
public string Password { get; set; } // Sẽ không xuất hiện trong JSON
[JsonPropertyName("born_year")]
public int? YearOfBirth { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Nickname { get; set; } // Nếu null — sẽ không xuất hiện trong JSON
}
Giờ ta serialize một người như vậy:
var person = new Person
{
FirstName = "Ivan",
Password = "123456",
YearOfBirth = 2000,
Nickname = null
};
string json = JsonSerializer.Serialize(person);
// json: {"first_name":"Ivan","born_year":2000}
Lưu ý: cả password lẫn nickname đều biến mất khỏi JSON! (Password — vì luôn bị ignore, nickname — chỉ khi nó là null).
Tại sao quan trọng? Thực tế bạn thường không muốn client (hoặc kẻ xấu!) thấy dữ liệu nhạy cảm như passwords, tokens, counters nội bộ, timestamps. Với [JsonIgnore] và mấy attribute tương tự bạn làm việc này chỉ với một dòng.
3. Attributes cho Newtonsoft.Json: champion của sự linh hoạt
Nếu bạn dùng Newtonsoft.Json (tài liệu ở đây), bạn có nhiều khả năng hơn nữa (và đôi khi dễ dùng hơn nếu bạn quen với các project cũ).
Các attribute chính:
| Attribute | Mục đích |
|---|---|
|
Đặt tên property trong JSON, cũng như order, required, v.v. |
|
Loại bỏ field/property khỏi serialization |
|
Yêu cầu bắt buộc khi deserialization |
|
Cho phép chỉ định converter tùy chỉnh cho các trường hợp phức tạp |
|
Quản lý serialization cho giá trị mặc định |
Ví dụ thực tế:
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; }
}
Các khả năng bổ sung của JsonProperty — như Required, Order, v.v. Ví dụ:
[JsonProperty("id", Order = 1, Required = Required.Always)]
public int Id { get; set; }
4. Attributes cho XML: phong cách "classic"
XmlSerializer dùng một bộ attributes riêng trong namespace System.Xml.Serialization.
Thường gặp nhất:
| Attribute | Để làm gì |
|---|---|
|
Element trong XML với tên khác |
|
Chuyển property thành attribute của XML-element |
|
Loại bỏ property hoặc field khỏi XML-serialization |
|
Cho collection — đặt tên XML-array |
|
Cho collection — đặt tên item của array |
|
Thay đổi tên tag root của XML |
Ví dụ thực tế:
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" };
Sau khi serialize, XML sẽ khoảng như sau:
<human years="32"><firstname>Anna</firstname></human>
Chú ý rằng field Secret không được đưa vào XML, còn Age được serialize dưới dạng attribute, không phải tag con.
5. Những nuance hữu ích
Bên dưới nắp: attributes hoạt động như thế nào
Khi serializer gặp class của bạn, nó thực sự "đọc" bằng reflection (ma thuật .NET, xem thêm System.Reflection). Serializer đọc metadata: ví dụ property có attribute JsonIgnore hay XmlElement không. Tùy theo đó nó thêm dữ liệu vào tài liệu đầu ra (hoặc bỏ qua).
Đây là cách tiện để tách "schema dữ liệu" khỏi business logic. Class của bạn là business logic, còn attributes thực chất là hộ chiếu cho serialization.
Bảng đối chiếu các attributes chính
| Chức năng | System.Text.Json | Newtonsoft.Json | XmlSerializer |
|---|---|---|---|
| Đổi tên | |
|
|
| Bỏ qua | |
|
|
| Custom format | |
|
— (qua IXmlSerializable, đau đầu) |
| Root của object | — | — | |
| Collection | — | — | |
6. Scenarios nâng cao
Đôi khi bạn cần serialize object khác với cách serializer chuẩn làm. Ví dụ bạn muốn lưu date ở dạng UNIX timestamp thay vì ISO. Trong trường hợp đó có attributes để gắn custom converters.
Trong 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; }
}
Bây giờ khi serialize, field EventTime sẽ thành số chứ không phải string chứa ngày.
Trong 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; }
}
Bây giờ boolean sẽ được serialize thành "yes" hoặc "no", và khi đọc lại sẽ thành true hoặc false.
7. Đặc điểm và bẫy
Khi bạn thêm attributes, nhớ rằng: các serializer khác nhau dùng các attributes khác nhau. Nếu bạn làm việc với cả JSON và XML (hoặc, trong trường hợp xấu, cùng lúc với Newtonsoft.Json và System.Text.Json), đừng quên gắn cả hai attributes cần thiết, nếu không sẽ có bất ngờ.
Tên property mặc định sẽ được serializer lấy nếu bạn không override bằng attribute.
Cẩn thận với inheritance: subclass inherit public fields/properties và nếu parent có attribute thì nó sẽ áp dụng cho subclass. Điều này hay gây ngạc nhiên, nhưng đó là thiết kế.
Lỗi điển hình: Thường thấy dev vô tình gắn attribute JsonIgnore lên field mà thực ra cần phải được serialize (hoặc ngược lại — quên ignore dữ liệu nhạy cảm). Hoặc, ví dụ, gắn XmlElement cho field private — rồi thắc mắc tại sao serializer không thấy nó (XmlSerializer chỉ xử lý members public!).
GO TO FULL VERSION