JavaRush /Курсы /C# SELF /Сериализация словарей

Сериализация словарей

C# SELF
46 уровень , 1 лекция
Открыта

1. Введение

Словарь (или Dictionary<TKey, TValue> в C#) — это коллекция пар "ключ-значение". Такой тип данных незаменим, когда нужно быстро находить значение по уникальному идентификатору (например, искать телефон по имени в телефонной книге).

В отличие от списков (List<T>), где порядок элементов имеет значение, словарь фокусируется на быстром доступе к данным по ключу. Но если при сериализации списка всё просто (JSON-массив), то при сериализации словаря возникает ряд нюансов:

  • Ключ должен быть сериализуемым типом (чаще всего строка, но иногда это может быть число или даже другой объект).
  • В JSON нет отдельных словарей — только объекты или массивы.

Давайте подробнее разберём, как .NET сериализует словари, с какими трудностями можно столкнуться, и как правильно "научить" наш код работать с такими структурами.

2. Сериализация словаря с ключами-строками

Начнём с классики — словарь, где и ключ, и значение строковые.


// Пример словаря: книга и её автор
var books = new Dictionary<string, string>
{
    ["Мастер и Маргарита"] = "Михаил Булгаков",
    ["Гарри Поттер"] = "Джоан Роулинг",
    ["Повелитель мух"] = "Вильям Голдинг"
};

// Сериализация в строку JSON
string json = JsonSerializer.Serialize(books, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine("Словарь сериализован в JSON:\n" + json);

// Сохраним в файл (синхронно)
File.WriteAllText("books.json", json);
Console.WriteLine("JSON записан в файл books.json.");

// Прочитаем обратно из файла (синхронно)
string jsonFromFile = File.ReadAllText("books.json");

// Десериализация обратно в словарь
var restoredBooks = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonFromFile);
Console.WriteLine("Результат десериализации:");
foreach (var pair in restoredBooks)
    Console.WriteLine($"{pair.Key} -> {pair.Value}");

Что получится в файле books.json?

{
  "Мастер и Маргарита": "Михаил Булгаков",
  "Гарри Поттер": "Джоан Роулинг",
  "Война и мир": "Вильям Голдинг"
}

Как это работает?

Сериализатор превращает наш Dictionary<string, string> в JSON-объект, где каждый ключ становится именем свойства, а значение — значением свойства. Это удобно, если ключи являются строками и они уникальны.

3. Словарь с нестандартным типом ключа

Всё просто, пока ключ — строка. А если это, например, число?


var bookIds = new Dictionary<int, string>
{
    [1001] = "Мастер и Маргарита",
    [1002] = "Гарри Поттер",
    [1003] = "Повелитель мух"
};

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

Результат:

{
  "1001": "Мастер и Маргарита",
  "1002": "Гарри Поттер",
  "1003": "Повелитель мух"
}

Что произошло?

  • C# преобразовал числовые ключи в строки, потому что в JSON имена свойств могут быть только строками.
  • При десериализации обратно в Dictionary<int, string> сериализатор обратно попытается привести строку к числу.

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


var restoredBookIds = JsonSerializer.Deserialize<Dictionary<int, string>>(jsonIntKeys);
// Все работает! Ключи опять стали числами.
Что если ключ — сложный тип, например, объект?

var dict = new Dictionary<Author, string>
{
    [new Author { Name = "Голдинг", BirthYear = 1911 }] = "Повелитель мух"
};

Попытка сериализовать такой словарь приведёт к исключению:

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

Почему так?

JSON-объект не может использовать что-либо, кроме строки, в качестве имени свойства. Поэтому ключами в словаре при сериализации должны быть простые типы, однозначно переводимые в строку (чаще всего строка или число). Сложные объекты нельзя использовать в качестве ключей при сериализации в JSON стандартными средствами.

4. Словарь с вложенными объектами в качестве значения

С ключами разобрались — теперь посмотрим, что происходит, если значение — сложный объект (например, Book или Author).

Пример


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

var authorDirectory = new Dictionary<string, Author>
{
    ["bulgakov"] = new Author { Name = "Михаил Булгаков", BirthYear = 1891 },
    ["golding"] = new Author { Name = "Вильям Голдинг", BirthYear = 1911 }
};

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

Результат:

{
  "bulgakov": {
    "Name": "Михаил Булгаков",
    "BirthYear": 1891
  },
  "golding": {
    "Name": "Вильям Голдинг",
    "BirthYear": 1911
  }
}
  • Всё, что вложено, сериализуется согласно структуре объектов.
  • Десериализация обратно в Dictionary<string, Author> также работает без проблем.

5. Словарь в составе другого объекта

Очень часто словари используются как поле внутри более сложного объекта. Например, у библиотеки есть каталог книг, где каждый ключ — название жанра, а значение — список книг этого жанра.


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>>
    {
        ["Фантастика"] = new List<Book>
        {
            new Book { Title = "Солярис", Author = "Станислав Лем" }
        },
        ["Классика"] = new List<Book>
        {
            new Book { Title = "Повелитель мух", Author = "Вильям Голдинг" },
            new Book { Title = "Зримая тьма", Author = "Вильям Голдинг" }
        }
    }
};

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

Фрагмент выходного JSON:

{
  "CatalogByGenre": {
    "Фантастика": [
      {
        "Title": "Солярис",
        "Author": "Станислав Лем"
      }
    ],
    "Классика": [
      {
        "Title": "Повелитель мух",
        "Author": "Вильям Голдинг"
      },
      {
        "Title": "Зримая тьма",
        "Author": "Вильям Голдинг"
      }
    ]
  }
}

Всё работает — вложенные словари и коллекции сериализуются и десериализуются рекурсивно.

6. Особенности и "подводные камни" сериализации словарей

Повторяющиеся ключи

В словаре ключи всегда уникальны. А вот если руками попытаться подложить JSON с повторяющимися ключами:

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

Результат: последнее значение ("second") перезапишет первое, ошибки не будет. Так работает большинство парсеров JSON.

Порядок элементов

Словарь — неупорядоченная коллекция. При сериализации порядок ключей в JSON может отличаться от исходного. Если порядок критичен — используйте список пар (List<KeyValuePair<string, T>>), но обычно для словаря порядок значения не имеет.

JSON и вложенные словари

Уровни вложенности не ограничены, но для корректной работы каждый уровень должен укладываться в ограничения JSON (ключи — строки, значения — валидные JSON-объекты/массивы).

Использование JsonSerializerOptions

Иногда нужно, чтобы имена свойств были не в PascalCase, а в camelCase. Это особенно важно, если вы интегрируетесь с фронтендом на JavaScript, где camelCase считается стандартом для названий полей.


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

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

Важно: в словарях этот параметр влияет только на сериализацию вложенных объектов (их свойства), а не на ключи словаря. Ключи в словаре всегда сериализуются в той строке, которая была указана в C#.

7. Проблемы со сложными и нестроковыми ключами

С сериализацией словарей с ключами-строками и числовыми ключами (например, int, long, Guid) всё работает "из коробки". А вот если вы попытаетесь использовать в качестве ключа пользовательский класс или структуру — получите ошибку NotSupportedException.

Для сериализации таких случаев существуют обходные подходы:

  • Использовать другой формат хранения, например, сериализовать словарь как массив объектов с полями "Key" и "Value".
  • Написать конвертер (JsonConverter), который преобразует ваш сложный ключ в строку и обратно.
  • Если структура реально сложная — иногда стоит пересмотреть архитектуру и не использовать сложные объекты как ключ словаря.

Пример обхода через сериализацию как списка пар


public class AuthorInfo
{
    public Author Author { get; set; }
    public string Book { get; set; }
}
// вместо Dictionary<Author, string>
var list = new List<AuthorInfo>
{
    new AuthorInfo { Author = new Author { Name = "Вильям Голдинг", BirthYear = 1911 }, Book = "Повелитель мух" }
};
// такой список сериализуется без проблем

Сравнение: словарь vs. список пар при сериализации

Тип коллекции JSON-структура Когда использовать
Dictionary<string, Book>
{ "key1": {...}, "key2": {...} }
Ключи — простые строки, нужны быстрые поиски и уникальность
List<KeyValuePair<string, Book>>
[{"Key": "key1", "Value": {...}}, ... ]
Ключ — сложный тип, нужен контроль порядка, возможны дубликаты

8. Типовые вопросы на собеседованиях

1. Можно ли сериализовать Dictionary<DateTime, string>?
Да, но ключи преобразуются в строковое представление (обычно ISO формат серии "yyyy-MM-ddTHH:mm:ss"). Иногда при десериализации могут возникнуть проблемы с локалями и форматами дат.

2. Что произойдет, если сериализовать Dictionary<int, string>?
Ключи будут сериализованы как строки, даже если в исходном словаре были числа. Обратно десериализуется нормально.

3. Почему нельзя сериализовать словарь с объектами в качестве ключа?
Только строки могут быть именами свойств JSON-объекта, а объекты — нет.

4. А если хочется сериализовать словарь с комплексным ключом?
Лучше пересмотреть структуру или сериализовать как список пар "key-value", где ключ сериализуется целиком как объект-поле, а не как имя свойства.

2
Задача
C# SELF, 46 уровень, 1 лекция
Недоступна
Сериализация и десериализация простого словаря
Сериализация и десериализация простого словаря
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ