CodeGym /Các khóa học /C# SELF /Kiểm soát quá trình bằng attributes

Kiểm soát quá trình bằng attributes

C# SELF
Mức độ , Bài học
Có sẵn

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
JsonPropertyName("...")
Chuyển tên property trong JSON
[JsonPropertyName("id")]
JsonIgnore
Loại bỏ hoàn toàn property khỏi serialization
[JsonIgnore]
JsonInclude
Serialize public field (không chỉ property)
[JsonInclude]
JsonIgnore(Condition = ...)
Bỏ qua property trong một số điều kiện
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

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
JsonProperty("...")
Đặt tên property trong JSON, cũng như order, required, v.v.
JsonIgnore
Loại bỏ field/property khỏi serialization
JsonRequired
Yêu cầu bắt buộc khi deserialization
JsonConverter
Cho phép chỉ định converter tùy chỉnh cho các trường hợp phức tạp
DefaultValueHandling
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ì
[XmlElement("...")]
Element trong XML với tên khác
[XmlAttribute("...")]
Chuyển property thành attribute của XML-element
[XmlIgnore]
Loại bỏ property hoặc field khỏi XML-serialization
[XmlArray("...")]
Cho collection — đặt tên XML-array
[XmlArrayItem("...")]
Cho collection — đặt tên item của array
[XmlRoot("...")]
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
[JsonPropertyName]
[JsonProperty]
[XmlElement]
[XmlAttribute]
Bỏ qua
[JsonIgnore]
[JsonIgnore]
[XmlIgnore]
Custom format
[JsonConverter]
[JsonConverter]
— (qua IXmlSerializable, đau đầu)
Root của object
[XmlRoot]
Collection
[XmlArray]
[XmlArrayItem]

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.JsonSystem.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!).

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Bỏ qua giá trị null
Bỏ qua giá trị null
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION