CodeGym /Các khóa học /C# SELF /Serialization Dictionary

Serialization Dictionary

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

1. Giới thiệu

Dictionary (hoặc Dictionary<TKey, TValue> trong C#) là một bộ sưu tập các cặp "khóa-giá trị". Loại dữ liệu này cực kỳ hữu ích khi cần tìm nhanh giá trị theo một định danh duy nhất (ví dụ, tìm số điện thoại theo tên trong danh bạ).

Khác với danh sách (List<T>), nơi thứ tự các phần tử quan trọng, dictionary tập trung vào truy cập nhanh dữ liệu qua khóa. Nhưng khi serialize danh sách thì đơn giản (mảng JSON), còn serialize dictionary có một số điểm cần lưu ý:

  • Khóa phải là kiểu serialize được (thường là string, nhưng đôi khi có thể là number hoặc thậm chí là object khác).
  • Trong JSON không có kiểu dictionary riêng biệt — chỉ có object hoặc mảng.

Hãy cùng xem kỹ hơn cách .NET serialize dictionaries, những khó khăn có thể gặp và cách "dạy" cho code của chúng ta làm việc đúng với các cấu trúc này.

2. Serialize dictionary với khóa là string

Bắt đầu với ví dụ đơn giản — dictionary nơi cả khóa và giá trị đều là string.

// Ví dụ dictionary: sách và tác giả của nó
var books = new Dictionary<string, string>
{
    ["Master and Margarita"] = "Mikhail Bulgakov",
    ["Harry Potter"] = "J.K. Rowling",
    ["War and Peace"] = "Leo Tolstoy"
};

// Serialize thành chuỗi JSON
string json = JsonSerializer.Serialize(books, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine("Dictionary serialize thành JSON:\n" + json);

// Lưu vào file (bằng cách đồng bộ)
File.WriteAllText("books.json", json);
Console.WriteLine("JSON đã ghi vào file books.json.");

// Đọc lại từ file (bằng cách đồng bộ)
string jsonFromFile = File.ReadAllText("books.json");

// Deserialize về dictionary
var restoredBooks = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonFromFile);
Console.WriteLine("Kết quả deserialize:");
foreach (var pair in restoredBooks)
    Console.WriteLine($"{pair.Key} -> {pair.Value}");

File books.json sẽ chứa gì?

{
  "Master and Margarita": "Mikhail Bulgakov",
  "Harry Potter": "J.K. Rowling",
  "War and Peace": "Leo Tolstoy"
}

Chức năng hoạt động thế nào?

Serializer biến dictionary Dictionary<string, string> thành JSON object, trong đó mỗi key trở thành tên thuộc tính, còn value là giá trị của thuộc tính đó. Rất tiện lợi khi khóa là string và duy nhất.

3. Dictionary với kiểu khóa không phải string

Chỉ đơn giản khi khóa là string. Còn nếu là number chẳng hạn?

var bookIds = new Dictionary<int, string>
{
    [1001] = "Master and Margarita",
    [1002] = "Harry Potter",
    [1003] = "War and Peace"
};

string jsonIntKeys = JsonSerializer.Serialize(bookIds, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonIntKeys);

Kết quả:

{
  "1001": "Master and Margarita",
  "1002": "Harry Potter",
  "1003": "War and Peace"
}

Chuyện gì đã xảy ra?

  • C# chuyển các key kiểu number thành string vì trong JSON tên thuộc tính chỉ có thể là string.
  • Khi deserialize ngược lại từ string về Dictionary<int, string>, serializer sẽ cố gắng convert chuỗi thành number.

Ví dụ deserialize:

var restoredBookIds = JsonSerializer.Deserialize<Dictionary<int, string>>(jsonIntKeys);
// Mọi thứ vẫn ổn! Các key lại thành số.
Nếu khóa là kiểu phức tạp, ví dụ như object?
var dict = new Dictionary<Author, string>
{
    [new Author { Name = "Tolstoy", BirthYear = 1828 }] = "War and Peace"
};

Thử serialize dictionary kiểu này sẽ gây ra ngoại lệ:

System.NotSupportedException: Serialization and deserialization of 'Dictionary<Author, string>' instances are not supported.

Tại sao vậy?

Trong JSON, object chỉ có thể dùng string làm tên thuộc tính. Không thể dùng object làm key. Vì vậy, keys trong dictionary khi serialize thành JSON phải là kiểu đơn giản, dễ chuyển thành string (thường là string hoặc number). Các object phức tạp không thể dùng làm key theo chuẩn JSON.

4. Dictionary với giá trị là object phức tạp

Chúng ta đã hiểu về khóa, giờ xem giá trị là object phức tạp (ví dụ, Book hoặc Author).

Ví dụ

public class Author
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
}

var authorDirectory = new Dictionary<string, Author>
{
    ["bulgakov"] = new Author { Name = "Mikhail Bulgakov", BirthYear = 1891 },
    ["tolstoy"] = new Author { Name = "Leo Tolstoy", BirthYear = 1828 }
};

string jsonAuthors = JsonSerializer.Serialize(authorDirectory, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonAuthors);

Kết quả:

{
  "bulgakov": {
    "Name": "Mikhail Bulgakov",
    "BirthYear": 1891
  },
  "tolstoy": {
    "Name": "Leo Tolstoy",
    "BirthYear": 1828
  }
}
  • Mọi thứ lồng nhau đều serialize theo cấu trúc object.
  • Deserialize ngược lại về Dictionary<string, Author> cũng không vấn đề gì.

5. Dictionary trong một object khác

Rất phổ biến là dùng dictionary như một field trong object phức tạp hơn. Ví dụ, thư viện có danh mục sách theo thể loại, trong đó key là tên thể loại, còn value là danh sách sách.

public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
}

public class Library
{
    public Dictionary<string, List<Book>> CatalogByGenre { get; set; }
}

var library = new Library
{
    CatalogByGenre = new Dictionary<string, List<Book>>
    {
        ["Science Fiction"] = new List<Book>
        {
            new Book { Title = "Piknik na oboche", Author = "Brothers Strugatsky" }
        },
        ["Classics"] = new List<Book>
        {
            new Book { Title = "War and Peace", Author = "Leo Tolstoy" },
            new Book { Title = "Anna Karenina", Author = "Leo Tolstoy" }
        }
    }
};

string jsonLibrary = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonLibrary);

Phần JSON xuất ra:

{
  "CatalogByGenre": {
    "Science Fiction": [
      {
        "Title": "Piknik na oboche",
        "Author": "Brothers Strugatsky"
      }
    ],
    "Classics": [
      {
        "Title": "War and Peace",
        "Author": "Leo Tolstoy"
      },
      {
        "Title": "Anna Karenina",
        "Author": "Leo Tolstoy"
      }
    ]
  }
}

Mọi thứ hoạt động tốt — dictionary lồng nhau và collection đều serialize và deserialize đươc theo đệ quy.

6. Những đặc điểm và "cạm bẫy" của serialization dictionaries

Khóa trùng lặp

Trong dictionary, khóa luôn là duy nhất. Còn nếu cố tình đưa JSON có các khóa trùng nhau:

{
  "foo": "first",
  "foo": "second"
}

Kết quả: giá trị của khóa "foo" sẽ là "second" (giá trị cuối cùng), không có lỗi. Đây là cách các parser JSON hoạt động phổ biến.

Thứ tự phần tử

Dictionary là collection không theo thứ tự. Khi serialize, thứ tự các key trong JSON có thể khác so với ban đầu. Nếu thứ tự quan trọng, nên dùng List<KeyValuePair<string, T>> nhưng thường thứ tự không quan trọng với dictionary.

JSON và dictionary lồng nhau

Các cấp độ lồng nhau không giới hạn, nhưng mỗi cấp phải phù hợp với giới hạn JSON (key là string, value là object hoặc mảng hợp lệ).

Sử dụng JsonSerializerOptions

Thỉnh thoảng cần đổi tên thuộc tính từ PascalCase sang camelCase (ví dụ để phù hợp với JavaScript frontend).

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

string camelJson = JsonSerializer.Serialize(authorDirectory, options);

Quan trọng: Trong dictionary, tùy chọn này chỉ ảnh hưởng đến serialize các object lồng nhau, còn key trong dictionary luôn được serialize theo chuỗi đã định trong C#.

7. Vấn đề với key phức tạp và không phải string

Với dictionary có key là string hoặc number (như int, long, Guid) thì không vấn đề gì. Nhưng nếu dùng class hoặc struct phức tạp làm key, sẽ gặp lỗi NotSupportedException.

Để xử lý các trường hợp này, có thể:

  • Sử dụng định dạng khác, ví dụ serialize dictionary thành mảng object có fields "Key" và "Value".
  • Viết custom JsonConverter để chuyển đổi key phức tạp thành string và ngược lại.
  • Nếu cấu trúc quá phức tạp, nên xem xét lại kiến trúc, không dùng object phức tạp làm key.

Ví dụ workaround qua serialize thành danh sách cặp

public class AuthorInfo
{
    public Author Author { get; set; }
    public string Book { get; set; }
}
// Thay vì Dictionary<Author, string>
var list = new List<AuthorInfo>
{
    new AuthorInfo { Author = new Author { Name = "Leo Tolstoy", BirthYear = 1828 }, Book = "War and Peace" }
};
// Danh sách này serialize dễ dàng hơn

So sánh: dictionary vs. list cặp khi serialize

Loại collection Cấu trúc JSON Nên dùng khi nào
Dictionary<string, Book> { "key1": {...}, "key2": {...} } Khóa là string đơn giản, cần tìm kiếm nhanh, duy nhất
List<KeyValuePair<string, Book>> [{"Key": "key1", "Value": {...}}, ... ] Khóa là kiểu phức tạp, cần kiểm soát thứ tự, có thể trùng lặp

8. Các lỗi thường gặp và câu hỏi phỏng vấn

  1. Có thể serialize Dictionary<DateTime, string> không?
    Có, nhưng các key sẽ chuyển thành chuỗi (thường theo định dạng ISO như "yyyy-MM-ddTHH:mm:ss"). Đôi khi khi deserialize có thể gặp vấn đề về định dạng ngày tháng hoặc locale.
  2. Điều gì xảy ra nếu serialize Dictionary<int, string>?
    Các key sẽ được serialize thành chuỗi, dù ban đầu là number. Khi deserialize, vẫn hoạt động bình thường.
  3. Tại sao không thể serialize dictionary có object làm key?
    Vì JSON chỉ cho phép string làm tên thuộc tính, còn object thì không thể.
  4. Nếu muốn serialize dictionary với key phức tạp?
    Nên xem xét lại cấu trúc hoặc serialize thành danh sách các cặp "key-value", trong đó key là object toàn phần, không phải tên thuộc tính.
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Serialization và deserialization một dictionary đơn giản
Serialization và deserialization một dictionary đơn giản
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION