1. Giới thiệu
Nhắc lại: Serialization — là cách lưu trạng thái của một đối tượng dưới dạng chuỗi, file hoặc stream, để sau này có thể khôi phục lại đối tượng đó (deserialization) — và bạn lại có một đối tượng với cùng dữ liệu.
Tình huống điển hình:
1) Bạn có một đối tượng, ví dụ, một người dùng với tên và tuổi.
2) Cần lưu nó — gửi qua mạng, ghi vào file hoặc, giả sử, sang định dạng JSON.
3) Sau đó dữ liệu từ chuỗi/file lấy ra lại — và bạn lại có đối tượng!
Trong C# việc này làm trong vài dòng thôi. Cùng xem cụ thể.
Ứng dụng của chúng ta
Chúng ta đang phát triển một ứng dụng nhỏ "Contact Manager". Giả sử ta có lớp Person mà ta muốn serialize.
public class Person
{
// Các thuộc tính public cần thiết cho serialization!
public string Name { get; set; }
public int Age { get; set; }
// Constructor không tham số cần cho XmlSerializer
public Person() { }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Nếu bạn chưa quen với cú pháp này thì đừng lo — trong khóa học sau này chúng ta sẽ đi sâu vào class và properties. Hiện tại coi đây như model dữ liệu để thử nghiệm. Lưu ý: các thuộc tính public Name và Age — quan trọng cho serialization, và với XmlSerializer cần constructor public không tham số.
2. Serialization sang JSON bằng System.Text.Json
Ví dụ đơn giản nhất
Như đã hứa — đây là cách serialize sang JSON:
using System;
using System.Text.Json;
Person person = new Person("Alisa", 28);
// Serialization: biến đối tượng thành chuỗi JSON
string json = JsonSerializer.Serialize(person);
Console.WriteLine("JSON:");
Console.WriteLine(json);
Trên màn hình sẽ thấy gì?
JSON:
{"Name":"Alisa","Age":28}
Rất đơn giản: đối tượng → JSON. Đó, phép màu!
Deserialization — trả lại đối tượng
Bây giờ làm thao tác ngược lại — từ chuỗi JSON khôi phục đối tượng:
string json = "{\"Name\":\"Bob\",\"Age\":35}";
// Deserialization: khôi phục đối tượng kiểu Person từ chuỗi JSON
Person restored = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine("Đối tượng được khôi phục:");
Console.WriteLine($"Tên: {restored.Name}, tuổi: {restored.Age}");
Kết quả:
Đối tượng được khôi phục:
Tên: Bob, tuổi: 35
Lưu ý: nếu cấu trúc dữ liệu không khớp (ví dụ không có thuộc tính đó), trường tương ứng sẽ giữ giá trị mặc định (null cho kiểu tham chiếu, 0 cho số, v.v.).
3. Serialization/deserialization sang XML bằng XmlSerializer
Serialization sang XML — cái nhìn đầu tiên
using System;
using System.IO;
using System.Xml.Serialization;
Person person = new Person("Katya", 22);
// Tạo serializer cho kiểu Person
var serializer = new XmlSerializer(typeof(Person));
// Ghi XML vào file
using (var stream = new FileStream("person.xml", FileMode.Create))
{
serializer.Serialize(stream, person);
// Stream sẽ tự đóng khi kết thúc using-block
}
Console.WriteLine("Đã tạo file XML!");
Trong file person.xml sẽ xuất hiện thứ gì đó như sau:
<?xml version="1.0"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Katya</Name>
<Age>22</Age>
</Person>
Điểm quan trọng:
Để serialize vào file dùng stream (Stream, ví dụ FileStream). Nếu muốn serialize thành chuỗi (ví dụ để gửi qua mạng), dùng StringWriter:
var serializer = new XmlSerializer(typeof(Person));
using (var sw = new StringWriter())
{
serializer.Serialize(sw, person);
string xmlString = sw.ToString();
Console.WriteLine(xmlString);
}
Deserialization từ XML
using (var stream = new FileStream("person.xml", FileMode.Open))
{
// Đừng quên ép về kiểu cần thiết!
Person restored = (Person)serializer.Deserialize(stream);
Console.WriteLine($"Tên: {restored.Name}, tuổi: {restored.Age}");
}
Một lần nữa: XmlSerializer yêu cầu class được serialize phải có constructor public mặc định (không tham số).
4. Làm việc với file và chuỗi: chọn gì?
Trong thực tế serialization có thể đi thẳng vào chuỗi (để truyền qua mạng/lưu DB) hoặc vào file (để lưu trữ lâu dài).
Serialization vào chuỗi (ví dụ để truyền qua API):
- JSON: dùng JsonSerializer.Serialize(obj).
- XML: qua StringWriter.
Serialization vào file (ví dụ để export/backup):
- JSON: lưu chuỗi vào file bằng các cơ chế ghi file thông thường.
- XML: ghi trực tiếp vào stream bằng XmlSerializer.
Ví dụ: serialize vào file và ngược lại (JSON)
using System.IO;
using System.Text.Json;
Person person = new Person("Tom", 42);
// Serialize vào chuỗi
string json = JsonSerializer.Serialize(person);
// Ghi vào file
File.WriteAllText("contact.json", json);
// Đọc từ file
string readJson = File.ReadAllText("contact.json");
Person restored = JsonSerializer.Deserialize<Person>(readJson);
Console.WriteLine($"{restored.Name}, tuổi: {restored.Age}");
5. So sánh JSON vs XML
Minh họa quá trình
Để khỏi rối, đây là sơ đồ quá trình serialization và deserialization:
flowchart LR
A[Đối tượng] -- serialization --> B[Chuỗi/File/Stream]
B -- deserialization --> C[Đối tượng]
A -. JSON hoặc XML .-> B
B -. JSON hoặc XML .-> C
Bạn có thể tưởng tượng serialization là "đóng va li", còn deserialization là "mở va li" khi về nhà.
Bảng so sánh
| Tính năng | JSON (System.Text.Json) | XML (XmlSerializer) |
|---|---|---|
| Đọc/ghi vào chuỗi | ++ | + |
| Đọc/ghi vào file | ++ | ++ |
| Cần constructor không tham số | Không | Có |
| Đọc được bởi con người | ++ | + |
| Sơ đồ dữ liệu nghiêm ngặt (validation) | - | ++ |
| Độ linh hoạt (thuộc tính tùy chỉnh) | + | ++ |
| Hiệu năng | ++ | + |
| Tương thích với internet | ++ | + |
++ — rất tốt, + — bình thường, - — không được hỗ trợ
6. Làm thế nào thêm serialization vào ứng dụng của chúng ta
Giả sử trong "Contact Manager" có tính năng lưu danh sách contacts vào file và khôi phục lại.
Ngữ cảnh: ta có danh sách contacts (List<Person>).
List<Person> contacts = new List<Person>
{
new Person("Vasya", 30),
new Person("Petya", 25),
new Person("Masha", 27)
};
// Lưu tất cả contacts — serialize sang JSON
string jsonContacts = JsonSerializer.Serialize(contacts);
File.WriteAllText("contacts.json", jsonContacts);
// Khôi phục danh sách contacts
string readContactsJson = File.ReadAllText("contacts.json");
List<Person> restored = JsonSerializer.Deserialize<List<Person>>(readContactsJson);
foreach (var c in restored)
{
Console.WriteLine($"{c.Name}, tuổi: {c.Age}");
}
Tương tự có thể làm với XML, nhưng có vài khác biệt. Khi serialize collection bằng XmlSerializer thường đóng gói list vào một class container (root element) — làm vậy dễ kiểm soát format và tên element hơn.
7. Lỗi thường gặp khi serialization và deserialization
Lỗi #1: mismatch kiểu khi serialize và deserialize.
Serialize List<Person> — thì deserialize cũng phải vào List<Person>, không phải List<object> hoặc IEnumerable<Person>. Nếu không sẽ gặp lỗi hoặc kết quả không mong muốn.
Lỗi #2: cố serialize field hoặc property không public.
Các serializer phổ biến (JsonSerializer, XmlSerializer) làm việc với thành viên public. Nếu property không có getter/setter public thì giá trị có thể không vào JSON/XML, và bạn sẽ đi tìm lý do vì sao nó null.
Lỗi #3: thiếu constructor không tham số.
Với XmlSerializer constructor public mặc định là bắt buộc. Nếu không có — sẽ ném exception khi deserialize.
Lỗi #4: hiểu sai về tham chiếu đối tượng.
Nếu một đối tượng chứa tham chiếu tới đối tượng khác, serialization diễn ra "theo giá trị" (như nested object). Các tham chiếu không được giữ nguyên, điều này có thể dẫn tới trùng lặp dữ liệu hoặc vấn đề với vòng tham chiếu.
Lỗi #5: cố trộn lẫn các định dạng serialization.
XML và JSON — là các định dạng khác nhau với serializer khác nhau. Không thể đưa chuỗi JSON vào XmlSerializer và ngược lại — sẽ bị lỗi.
GO TO FULL VERSION