CodeGym /Các khóa học /C# SELF /Serializaton với System.Te...

Serializaton với System.Text.Json

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

1. Giới thiệu

Trong thời gian dài trong hệ sinh thái .NET, công cụ chính để làm việc với JSON là package bên thứ ba rất hay Newtonsoft.Json (còn gọi là Json.NET). Nó mạnh mẽ, linh hoạt và vẫn được dùng rộng rãi. Nhưng với các phiên bản mới của .NET 9 và C# 14, Microsoft quyết định đã đến lúc có serializer JSON tích hợp của riêng họ, hiệu năng cao. Vậy là xuất hiện System.Text.Json.

Tại sao cần cách tiếp cận mới? System.Text.Json được thiết kế theo thực tế hiện đại và giải quyết các vấn đề tích tụ qua nhiều năm dùng thư viện ngoài. Nó được tối ưu cho tốc độ và an toàn, phù hợp cho các kịch bản bất đồng bộ và web-API, và — điều thích nhất — không cần cài qua NuGet: mọi thứ đã có trong nền tảng.

Dĩ nhiên, Newtonsoft.Json vẫn tồn tại, và chúng ta sẽ xem xét nó sau. Nhưng với đa số dự án mới, System.Text.Json là lựa chọn mặc định. Chuẩn bị nhé, bây giờ mình sẽ dạy object của bạn nói chuyện bằng JSON!

2. Những điều cơ bản về System.Text.Json

Serialize một object đơn giản

Ok, bắt đầu với phép "ma thuật" cơ bản. Serialize object của chúng ta thành chuỗi JSON.

using System;
using System.Text.Json; // Đừng quên thêm!

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    public bool IsAlive { get; set; }
}

// Ở đâu đó trong chương trình của bạn:
Player player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };

// Serialize:
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // Sẽ in: {"Name":"Aragorn","Health":100,"IsAlive":true}

Ghi chú: Nếu bạn mới bắt đầu học serialization, ví dụ này cho thấy mọi thứ đơn giản thế nào: JsonSerializer.Serialize — xong.

Deserialize object

Hồi sinh object từ chuỗi JSON:

string incomingJson = "{\"Name\":\"Legolas\",\"Health\":88,\"IsAlive\":true}";

Player player2 = JsonSerializer.Deserialize<Player>(incomingJson);
Console.WriteLine(player2.Name);   // Legolas
Console.WriteLine(player2.Health); // 88
Console.WriteLine(player2.IsAlive); // true

Ghi chú: Nếu structure JSON khớp với class của bạn — mọi thứ chạy ngon lành. Nếu không — có thể ném exception hoặc trả về giá trị mặc định.

3. Nguyên tắc hoạt động và cơ chế serialization

Cách mapping diễn ra

System.Text.Json theo mặc định dùng cùng tên thuộc tính như trong class. Phân biệt chữ hoa chữ thường! Nếu trong JSON viết health thay vì Health, deserialization sẽ không khớp — thuộc tính sẽ giữ giá trị mặc định (0, false hoặc null).

Ví dụ:

// JSON với khóa chữ thường:
string badJson = "{\"name\":\"Gimli\",\"health\":120,\"isAlive\":true}";
Player player3 = JsonSerializer.Deserialize<Player>(badJson);
Console.WriteLine(player3.Name);    // rỗng
Console.WriteLine(player3.Health);  // 0
Console.WriteLine(player3.IsAlive); // false

Thú vị: Nhiều API viết khóa theo camelCase (health), còn trong C# thường dùng PascalCase (Health). Vấn đề này giải được bằng cấu hình (xem dưới).

4. Điều khiển serialization — options và cấu hình

Định dạng JSON: output dễ đọc cho con người

Đôi khi muốn JSON không gọn, mà đẹp để đọc — cho config hoặc log.

var options = new JsonSerializerOptions
{
    WriteIndented = true // Thêm indent
};

string prettyJson = JsonSerializer.Serialize(player, options);
Console.WriteLine(prettyJson);
/*
{
  "Name": "Frodo",
  "Health": 50,
  "IsAlive": true,
  "Inventory": [
    "Ring",
    "Bread",
    "Torch"
  ],
  "Position": {
    "X": 5,
    "Y": 15
  }
}
*/

Thuộc tính WriteIndented được bật, serializer sẽ thêm indent và xuống dòng.

Quản lý style tên (CamelCase vs PascalCase)

Nếu bạn làm việc với web-API mà tất cả khóa là camelCase, bật policy tên:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

string camelCaseJson = JsonSerializer.Serialize(player, options);
// {"name":"Frodo","health":50,"isAlive":true,"inventory":["Ring","Bread","Torch"],"position":{"x":5,"y":15}}

Và khi deserialize, những khóa như vậy sẽ được mapping đúng:

string apiJson = "{\"name\":\"Bilbo\",\"health\":40,\"isAlive\":true,\"inventory\":[\"Mug\"],\"position\":{\"x\":10,\"y\":5}}";
Player bilbo = JsonSerializer.Deserialize<Player>(apiJson, options);
Console.WriteLine(bilbo.Name); // Bilbo

5. Những điểm hữu ích cần biết

Dùng attribute [JsonIgnore]

Đôi khi không phải thuộc tính nào cũng nên serialize — ví dụ dữ liệu nhạy cảm hoặc giá trị tạm tính.

using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }

    [JsonIgnore] // Thuộc tính này sẽ không xuất vào JSON
    public bool IsSecretCharacter { get; set; }
}

Bây giờ khi serialize:

var player = new Player { Name = "Boromir", Health = 80, IsSecretCharacter = true };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Boromir","Health":80}

Khi deserialize ngược lại, IsSecretCharacter sẽ nhận giá trị mặc định (false).

Dùng [JsonPropertyName("...")]

Giả sử trong code thuộc tính tên là IsAlive, nhưng trong JSON bạn muốn nó là "status":

using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }

    [JsonPropertyName("status")]
    public bool IsAlive { get; set; }
}

Serialize sẽ cho:

var player = new Player { Name = "Pippin", Health = 60, IsAlive = false };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Pippin","Health":60,"status":false}

Và khi deserialize, khóa "status" sẽ map đúng vào thuộc tính IsAlive.

Hạn chế tích hợp và đặc điểm an toàn

  • Theo mặc định chỉ serialize các thuộc tính public có getter/setter; field/private property bị bỏ qua.
  • Khi có vòng tham chiếu (cyclic references) sẽ ném exception: "A possible object cycle was detected".
  • Khác với Newtonsoft.Json, serializer chuẩn ít dựa vào "mánh" với types — đổi lại an toàn hơn và nhanh hơn cho các kịch bản điển hình.

6. Lỗi thường gặp và bẫy

Bạn vô tình đổi tên thuộc tính trong JSON và quên cập nhật code — kết quả thuộc tính nhận giá trị mặc định (null, 0, false).

Trong JSON thiếu trường cần thiết — thuộc tính tương ứng trong object sẽ có giá trị mặc định (xem tài liệu).

Thuộc tính không có setter public — khi deserialize nó sẽ không được gán giá trị.

Thay đổi cấu trúc lớp lồng nhau hoặc collection — deserialize có thể hỏng hoặc cho kết quả bất ngờ.

Các tên trùng nhau ở nhiều cấp lồng (ở parent và child) có thể gây nhầm lẫn và khó debug.

Đôi khi nguyên nhân là phiên bản nền tảng: các phiên bản cũ của System.Text.Json xử lý kém một số kiểu (ví dụ Dictionary, DateTime, enum), nhưng trong .NET 7/8/9 nhiều thứ đã được sửa.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION