1. Giới thiệu
Ổn nếu nhu cầu của bạn trùng với hành vi mặc định. Nhưng thường khách hàng (hoặc backend của bên thứ ba) yêu cầu format riêng: mọi nơi camelCase, ngày — ISO 8601, bỏ qua null, custom converters, và những "vui vẻ" của integrator.
Lúc này người bạn mới xuất hiện — lớp JsonSerializerOptions.
Nếu serialization là một nhà bếp, thì lớp này là gia vị, nồi niêu và thành phần bí mật để bạn nêm nếm serialization theo ý mình.
Cách dùng JsonSerializerOptions
Để tùy chỉnh quá trình serialization hoặc deserialization, ta chỉ cần truyền đối tượng settings cho các method của serializer:
using System.Text.Json;
var options = new JsonSerializerOptions
{
// Ở đây sẽ là các thiết lập của bạn
};
string json = JsonSerializer.Serialize(myObject, options);
var obj = JsonSerializer.Deserialize<MyType>(json, options);
Mẫu này xuất hiện khắp nơi: bạn tạo JsonSerializerOptions, cấu hình theo ý (xem bên dưới), rồi truyền vào các method của serializer Serialize/Deserialize.
Quan trọng: Cùng một đối tượng JsonSerializerOptions hoàn toàn có thể (và thậm chí nên) tái sử dụng cho nhiều lần serialization.
2. Các thiết lập chính JsonSerializerOptions
Hãy xem qua những "núm" và "công tắc" phổ biến của lớp này.
Định dạng JSON — WriteIndented
Có thấy khó chịu với JSON một dòng không? Không sao — bật định dạng đẹp với indent!
var options = new JsonSerializerOptions
{
WriteIndented = true // JSON đẹp, "dễ đọc" cho con người
};
var person = new Person { Name = "Anna", Age = 30 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);
Kết quả:
{
"Name": "Anna",
"Age": 30
}
Nếu không đặt WriteIndented = true bạn sẽ có JSON gọn một dòng {"Name":"Anna","Age":30}. Trong production thường dùng JSON "minified" (nhỏ hơn), còn cho debug/log thì format cho dễ đọc.
Quy tắc đặt tên — PropertyNamingPolicy
Thường API yêu cầu tên thuộc tính theo kiểu camelCase (ví dụ firstName thay vì FirstName). Trong .NET properties hay dùng PascalCase, nên thuộc tính PropertyNamingPolicy rất hữu dụng:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var person = new Person { Name = "Oleg", Age = 25 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"name":"Oleg","age":25}
Nếu cần style riêng (ví dụ toàn VIẾT IN HOA), bạn có thể tự implement JsonNamingPolicy. Thông thường CamelCase là đủ.
3. Bỏ qua thuộc tính
Bỏ qua hay không bỏ qua giá trị null — DefaultIgnoreCondition
Đôi khi bạn muốn các trường có giá trị rỗng (null) không xuất hiện trong JSON. Lý do: JSON gọn hơn, hoặc hệ thống bên ngoài không chịu được null.
Dùng thuộc tính DefaultIgnoreCondition:
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var person = new Person { Name = null, Age = 22 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"Age":22}
Nếu muốn bỏ qua không chỉ null mà cả giá trị mặc định (ví dụ int=0), dùng WhenWritingDefault:
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;
Serialize fields và private members — IncludeFields và [JsonInclude]
Mặc định chỉ serialize các public properties có public setter. Nếu muốn serialize cả fields, bật:
var options = new JsonSerializerOptions
{
IncludeFields = true
};
Ví dụ lớp:
public class Item
{
public string Name;
public int Id { get; set; }
}
var item = new Item { Name = "Book", Id = 12 };
string json = JsonSerializer.Serialize(item, options);
// {"Name":"Book","Id":12}
Để serialize private fields/properties có thể dùng attribute [JsonInclude], nhưng đó là chủ đề cho một bài khác.
4. Chuyển đổi giá trị
Định dạng ngày — Converters
Mặc định System.Text.Json serialize ngày theo chuẩn ISO 8601 (ví dụ "2023-12-27T15:30:45.123Z"), điều này thường hợp lý. Nhưng có lúc bạn cần format ngày/giờ khác.
Lúc đó ta thêm custom converters. Ví dụ cơ bản:
using System.Text.Json;
using System.Text.Json.Serialization;
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
private string _format = "yyyyMMdd";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var date = reader.GetString();
return DateTime.ParseExact(date, _format, null);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}
// Dùng converter
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateTimeConverter());
var dateObj = new { Date = new DateTime(2022, 1, 5) };
string json = JsonSerializer.Serialize(dateObj, options); // {"Date":"20220105"}
5. Các chi tiết hữu ích
Không phân biệt chữ hoa/chữ thường tên thuộc tính khi deserialization — PropertyNameCaseInsensitive
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var json = "{\"name\":\"Vasya\",\"age\":33}";
var person = JsonSerializer.Deserialize<Person>(json, options);
// person.Name == "Vasya"
Quản lý nesting và độ sâu tối đa của object — MaxDepth
var options = new JsonSerializerOptions
{
MaxDepth = 32 // Mặc định 64
};
Nhưng nếu object của bạn có thể sâu vô tận — ví dụ cây với tham chiếu parent/child — tốt hơn là thiết kế model khác.
Bình luận trong JSON — ReadCommentHandling (deserialization)
JSON chính thức không hỗ trợ comment, nhưng đôi khi gặp các file "sáng tạo" với comment kiểu //.
var options = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip
};
string json = "{\n \"Name\": \"Ivan\", // Tên người dùng\n \"Age\": 30\n}";
var person = JsonSerializer.Deserialize<Person>(json, options);
Hỗ trợ ký tự đặc biệt — Encoder
Đôi khi cần kiểm soát cách serializer escape ký tự (ví dụ không chuyển chữ Kiril sang \uXXXX). Bạn có thể chỉ định encoder:
using System.Text.Encodings.Web;
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
Dùng cẩn thận, nếu không emoji/kanji của bạn có thể bất ngờ "chạy" vào chuỗi Unicode!
6. Lỗi thường gặp khi cấu hình serialization
Lỗi #1: quên public setter. Field hoặc property không được serialize vì nó không có public setter, hoặc nó là private.
Lỗi #2: bỏ qua không đúng cách giá trị null. Khi bật DefaultIgnoreCondition, các thuộc tính có null sẽ không xuất hiện trong JSON, dù bạn không mong muốn điều đó.
Lỗi #3: định dạng ngày sai. Ngày bị serialize không đúng định dạng — thêm custom JsonConverter.
Lỗi #4: mong chờ thứ tự cố định của thuộc tính. Chuẩn JSON không đảm bảo thứ tự trường. Nếu API yêu cầu thứ tự cố định, bạn phải đổi model hoặc dùng thư viện bên thứ ba.
Lỗi #5: cài đặt khác nhau khi serialize và deserialize. Nếu bạn không dùng cùng JsonSerializerOptions để đọc và ghi JSON, có thể gặp lỗi với case tên hoặc format.
GO TO FULL VERSION