CodeGym /Các khóa học /C# SELF /Đắm chìm sâu vào System.Text.Json

Đắm chìm sâu vào System.Text.Json

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

1. Введение

Trong bài này chúng ta sẽ đi từ "chỉ cần lưu danh sách cho tôi" đến serialization linh hoạt, chính xác và hiệu năng tốt với System.Text.Json. Trong các dự án .NET hiện đại JSON là tiêu chuẩn trao đổi dữ liệu. Các gọi cơ bản Serialize/Deserialize thì đơn giản, nhưng thực tế thường cần tinh chỉnh: bỏ/đổi tên trường, quản lý định dạng ngày, phòng vòng lặp, xử lý dữ liệu lớn, converter tùy chỉnh, v.v.

Chúng ta sẽ xem không chỉ các method của JsonSerializer, mà còn cấu hình qua JsonSerializerOptions, attribute, làm việc với Stream, quản lý bộ nhớ và chèn quy tắc serialization riêng thông qua JsonConverter.

Lịch sử ngắn và vị trí của System.Text.Json

Trong thời gian dài trên .NET thư viện thống trị là Newtonsoft.Json (Json.NET) — linh hoạt và chín muồi, nhưng không phải lúc nào cũng nhanh nhất hoặc nhẹ về dependency. Từ .NET Core 3.0 có bộ System.Text.Json tích hợp: hiệu năng cao, ít phụ thuộc (thuộc nền tảng), tích hợp chặt với ASP.NET Core và tiếp tục được cải tiến qua các bản .NET.

2. Основные классы и методы

Thành phần cơ bản — lớp static JsonSerializer, cung cấp hai hướng:

  • Сериализация: объект → JSON‑строка (Serialize)
  • Десериализация: JSON‑строка → объект нужного типа (Deserialize)

Пример сериализации простого объекта

using System.Text.Json;

var person = new Person { Name = "Ivan", Age = 30 };
string jsonString = JsonSerializer.Serialize(person);
Console.WriteLine(jsonString); // {"Name":"Ivan","Age":30}

Пример десериализации

var json = "{\"Name\":\"Anna\",\"Age\":22}";
var anna = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine(anna.Name); // Anna

Ghi chú: kiểu Person đã được triển khai trong các bài trước — dùng luôn ở đây.

3. Управление сериализацией: JsonSerializerOptions

Trong dự án thực tế gần như luôn cần cấu hình: tên thuộc tính ở camelCase, định dạng ngày, xử lý vòng lặp, giá trị mặc định, v.v. Tất cả điều đó điều khiển qua JsonSerializerOptions.

Пример настройки

var options = new JsonSerializerOptions
{
    WriteIndented = true,    // Định dạng JSON cho đẹp (thêm khoảng trắng và xuống dòng)
    PropertyNameCaseInsensitive = true, // Không phân biệt hoa thường khi десериализации
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase // dùng camelCase cho thuộc tính (thay vì PascalCase)
};

string json = JsonSerializer.Serialize(person, options);
/*
{
  "name": "Ivan",
  "age": 30
}
*/

Tại sao quan trọng? Hầu hết framework frontend mong đợi camelCase, không phải PascalCase kiểu .NET.

4. Атрибуты: System.Text.Json.Serialization

Đôi khi tiện hơn khi điều khiển serialization ngay trong model bằng attribute. Gắn vào field/prop để ảnh hưởng tên, include/exclude và xử lý giá trị.

Основные атрибуты

Атрибут Что делает
[JsonIgnore]
Loại thuộc tính khỏi serialization/deserialization
[JsonPropertyName("tên")]
Dùng tên khác trong JSON
[JsonInclude]
Include thuộc tính/field không-public vào serialization
[JsonNumberHandling]
Điều khiển xử lý giá trị số

Пример: управление свойствами через атрибуты

using System.Text.Json.Serialization;

public class Person
{
    [JsonPropertyName("full_name")]
    public string Name { get; set; }

    [JsonIgnore]
    public int SecretCode { get; set; }

    public int Age { get; set; }
}
var person = new Person { Name = "Petr", Age = 45, SecretCode = 123 };
string json = JsonSerializer.Serialize(person);
// {"full_name":"Petr","Age":45}

Lưu ý: SecretCode đã không vào JSON, còn Name được serialized như "full_name".

5. Сериализация коллекций и вложенных объектов

Коллекции — это просто

var numbers = new List<int> { 1, 2, 3 };
string json = JsonSerializer.Serialize(numbers); // [1,2,3]

var people = new List<Person> {
    new Person { Name = "Anna", Age = 20 },
    new Person { Name = "Maksim", Age = 40 }
};
string jsonList = JsonSerializer.Serialize(people);
// [{"Name":"Anna","Age":20},{"Name":"Maksim","Age":40}]

Вложенные структуры

public class Group
{
    public string Name { get; set; }
    public List<Person> Members { get; set; }
}

var group = new Group
{
    Name = "Nhóm lập trình",
    Members = new List<Person>
    {
        new Person { Name = "Sasha", Age = 23 },
        new Person { Name = "Masha", Age = 28 }
    }
};

string jsonGroup = JsonSerializer.Serialize(group, options);
/*
{
  "name": "Nhóm lập trình",
  "members": [
    { "name": "Sasha", "age": 23 },
    { "name": "Masha", "age": 28 }
  ]
}
*/

6. Десериализация: что важно знать?

var json = "[{\"Name\":\"Ivan\",\"Age\":21}]";
var list = JsonSerializer.Deserialize<List<Person>>(json);
Console.WriteLine(list[0].Name); // Ivan

Kịch bản thường gặp: nếu trong JSON thiếu một trường, property tương ứng sẽ nhận giá trị mặc định. Các trường thừa trong JSON mà model không có sẽ bị bỏ qua. Nhưng nếu kiểu không khớp (ví dụ thay vì số lại là chuỗi) — khi deserialization sẽ ném exception.

7. Обработка дат, времени, форматов и числовых значений

public class Meeting
{
    public string Topic { get; set; }
    public DateTime Time { get; set; }
}

var meeting = new Meeting { Topic = "Cuộc họp", Time = DateTime.Now };
string json = JsonSerializer.Serialize(meeting);
// {"Topic":"Cuộc họp","Time":"2024-06-06T20:30:00.0000000+03:00"}

Mặc định DateTime được serialized theo ISO 8601. Cần định dạng khác (ví dụ chỉ ngày)? Dùng property riêng hoặc viết converter tuỳ chỉnh (xem bên dưới).

FAQ: Để số được serialize như chuỗi (ví dụ số điện thoại hoặc ID lớn), dùng attribute [JsonNumberHandling(JsonNumberHandling.WriteAsString)].

8. Потоки и работа с файлами

Bạn có thể làm việc không chỉ với chuỗi mà còn với Stream — quan trọng cho dữ liệu lớn (file, mạng).

Пример записи в файл

using var fs = File.Create("person.json");
JsonSerializer.Serialize(fs, person);
// Đừng quên gọi fs.Flush() hoặc dùng using!

Пример чтения из файла

using var fs = File.OpenRead("person.json");
var restored = JsonSerializer.Deserialize<Person>(fs);

Với stream có các phương thức bất đồng bộ SerializeAsync/DeserializeAsync — hữu dụng cho service tải cao.

9. Кастомные конвертеры

Nếu quy tắc chuẩn không phù hợp (định dạng ngày/số không chuẩn, giá trị phức tạp, cấu trúc riêng) — viết JsonConverter.

Пример: дата только как "dd.MM.yyyy"

public class CustomDateConverter : JsonConverter<DateTime>
{
    public override DateTime Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.ParseExact(reader.GetString(), "dd.MM.yyyy", null);
    }

    public override void Write(
        Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("dd.MM.yyyy"));
    }
}

var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateConverter());

var dt = new DateTime(2024, 6, 1);
string json = JsonSerializer.Serialize(dt, options); // "01.06.2024"

Converter tuỳ chỉnh hữu ích để serialize tọa độ, vector, màu sắc, ngày/tiền tệ không chuẩn, các định dạng ID khác nhau, v.v.

10. Полезные нюансы

Обработка циклических ссылок и глубоких иерархий

var options = new JsonSerializerOptions
{
    ReferenceHandler = ReferenceHandler.Preserve, // Giữ lại tất cả object bằng $id/$ref
    WriteIndented = true
};

Quan trọng: JSON sẽ chứa các thuộc tính hệ thống $id và $ref. Khi trao đổi với hệ thống ngoài không hiểu chúng, có thể không phù hợp.

Различия между System.Text.Json и Newtonsoft.Json

System.Text.Json giờ đã rất mạnh, nhưng chưa che phủ mọi kịch bản của Newtonsoft.Json (constructor private, object dynamic phức tạp, v.v.). Với hầu hết tác vụ chuẩn nên ưu tiên serializer tích hợp — nó nhanh hơn và không có dependency thừa.

Интерактивная работа с JSON: DOM API

Khi cần "duyệt" JSON mà không muốn có model đầy đủ, dùng JsonDocument và JsonElement.

using var doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;

if (root.TryGetProperty("Name", out var nameProperty))
{
    Console.WriteLine(nameProperty.GetString());
}

11. Полезные опции и их эффекты

Свойство Значение/Назначение
WriteIndented
true — форматирование с отступами
PropertyNameCaseInsensitive
true — игнорировать регистр имён свойств при десериализации
PropertyNamingPolicy
JsonNamingPolicy.CamelCase
DefaultIgnoreCondition
Правила игнорирования null/значений по умолчанию
ReferenceHandler
Preserve
,
IgnoreCycles
AllowTrailingCommas
true — разрешить запятую в конце массива
NumberHandling
Преобразование чисел в строки/обратно (и др.)
Converters
Список кастомных конвертеров

12. Частые ошибки и практические советы

Ошибка №1: неправильный тип при десериализации. Если сериализовали список, десериализуйте именно в список: List<T>, а не одиночный объект.

Ошибка №2: неверный регистр имён свойств. Без настройки регистрозависимости свойства могут «не находиться». Используйте PropertyNameCaseInsensitive или настройте PropertyNamingPolicy.

Ошибка №3: некорректная обработка дат. По умолчанию формат — ISO 8601. Нужен другой — создайте и примените конвертер (JsonConverter<DateTime>).

Ошибка №4: ожидание сериализации приватных/статических полей. По умолчанию берутся публичные свойства. Для нестандартных случаев используйте соответствующие атрибуты (например, [JsonInclude]).

Ошибка №5: непонимание значений по умолчанию. Отсутствующее поле в JSON → значение по умолчанию для свойства. Учитывайте это в логике.

Ошибка №6: неправильное управление потоками. Закрывайте ресурсы с using или await using, чтобы избежать утечек и блокировок.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Serialization đối tượng với JsonSerializerOptions
Serialization đối tượng với JsonSerializerOptions
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION