JavaRush /Курсы /C# SELF /Управление сериализацией коллекций

Управление сериализацией коллекций

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

1. Введение

Как бы ни хотелось думать, что коллекции сериализуются и десериализуются идеально “из коробки”, в реальных проектах это часто не так. Иногда нужно скрыть определённые коллекции от сериализации — например, внутренние кэшированные данные. Бывает, требуется переименовать коллекционные свойства, чтобы они соответствовали контракту API. В некоторых случаях важно управлять тем, какие элементы сохраняются или игнорируются, или даже преобразовать коллекцию специальным образом, чтобы итоговый JSON выглядел понятным и “читаемым” для других сервисов.

К счастью, System.Text.Json предлагает простой и прозрачный способ управления сериализацией с помощью атрибутов, которые можно применять как к коллекциям, так и к отдельным элементам. В этом разделе мы продолжим развивать нашу библиотечную модель, чтобы разобраться, как это работает на практике.

2. Исключение коллекционных свойств: [JsonIgnore]

Начнем с простого. Иногда в вашем классе есть коллекция, которую сериализовать нельзя — например, это временные, кэшированные или чувствительные данные. Что делать? Конечно же, [JsonIgnore]!

Представим, что у нас есть класс Library, в который мы добавили свойство List<Book> Cache, используемое только для быстрого доступа:

using System.Text.Json.Serialization;

public class Library
{
    public string Name { get; set; }

    public List<Book> Books { get; set; }

    [JsonIgnore]
    public List<Book> Cache { get; set; } // Не сериализуется!
}
// Пример использования:
var library = new Library
{
    Name = "Главная библиотека",
    Books = new List<Book>
    {
        new Book { Title = "Волшебная долина", Author = new Author { Name = "Туве Янссен", BirthYear = 1914 } }
    },
    Cache = new List<Book>
    {
        new Book { Title = "Повелитель мух", Author = new Author { Name = "Вильям Голдинг", BirthYear = 1911 } }
    }
};

string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json); // В JSON-е нет свойства Cache!

Результат сериализации будет примерно такой:

{
  "Name": "Главная библиотека",
  "Books": [
    {
      "Title": "Волшебная долина",
      "Author": {
        "Name": "Туве Янссен",
        "BirthYear": 1914
      }
    }
  ]
}

Видите? Никаких “кэшей” во внешнем мире. Всё, что под [JsonIgnore], — скрыто и надёжно, как Wi-Fi пароль в вашей голове.

3. Переименование коллекций с [JsonPropertyName]

Часто сталкиваетесь с такими API, где ожидают, например, "items" вместо "Books"? Или, наоборот, вы не хотите переименовывать поле в C# (чтобы не запутаться), но в JSON оно должно “звучать” иначе?

Вот как это делается:

using System.Text.Json.Serialization;

public class Library
{
    public string Name { get; set; }

    [JsonPropertyName("items")]
    public List<Book> Books { get; set; }

    [JsonIgnore]
    public List<Book> Cache { get; set; }
}
// Сериализация:
var library = new Library { Name = "Филиал №1", Books = new List<Book>() };
string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

Вывод:

{
  "Name": "Филиал №1",
  "items": []
}

Обратите внимание, что десериализация тоже корректно сопоставит поле items из JSON с Books в C# — магия работает в обе стороны.

4. Управление сериализацией коллекций и их элементов

Для этого вам понадобится JsonIgnoreCondition.WhenWritingNull и/или nullable.

Бывает, что коллекция — это просто необязательное поле. Например, у только что созданной библиотеки пока ещё нет книг. Если вы не хотите, чтобы в JSON появлялось свойство books: null, вы можете управлять этим через опции:

var library = new Library { Name = "Пустая библиотека" };
// Books не инициализирован = null

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    WriteIndented = true
};

string json = JsonSerializer.Serialize(library, options);
Console.WriteLine(json);

Результат:

{
  "Name": "Пустая библиотека"
}

А если у вас есть пустой список (но не null), то сериализатор выдаст "books": []. Это важное различие, потому что иногда нужно намеренно скрыть поле, если оно null, но не пустой список.

5. Атрибут [JsonIgnore] на свойствах элементов

Атрибуты сериализации работают и внутри элементов коллекции. Вы можете скрыть отдельные свойства у каждого объекта в списке.

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

    [JsonIgnore]
    public string InternalCode { get; set; }
}

Теперь при сериализации книги из коллекции Books, поля InternalCode не будет в JSON.

6. Адресация коллекций через “индексы” или вложенные структуры

Иногда приходится сериализовать коллекции не просто как массивы, а, например, как “карты” (dictionary) — если каждая книга имеет уникальный идентификатор. В этом случае — без хитрых атрибутов для элементов, но с помощью стандартных средств — можно объявить свойство-словарь:

public class Library
{
    [JsonPropertyName("catalog")]
    public Dictionary<string, Book> BookCatalog { get; set; }
}

При сериализации словарь превратится в объект с парами ключ-значение:

var library = new Library
{
    BookCatalog = new Dictionary<string, Book>
    {
        ["978-5-699-12345-6"] = new Book { Title = "Словарь", Author = new Author { Name = "Неизвестный", BirthYear = 2000 } }
    }
};

JSON:

{
  "catalog": {
    "978-5-699-12345-6": {
      "Title": "Словарь",
      "Author": {
        "Name": "Неизвестный",
        "BirthYear": 2000
      }
    }
  }
}

Такое представление удобно для передачи по API, где важно сохранять связь между ключом и объектом.

7. Ошибки и трудности при управлении сериализацией коллекций

Если попытаться сериализовать коллекцию, у которой не все элементы корректно инициализированы (например, в списке есть null), то по умолчанию System.Text.Json запишет такие элементы как null в массиве.

Даже если установлено DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, элементы-null внутри массива останутся — правило относится к свойствам объекта, а не к содержимому коллекций. Чтобы этого избежать, предварительно очистите коллекцию: RemoveAll(b => b == null).

Частая путаница при десериализации — несоответствие имён. Если забыть указать [JsonPropertyName], класс будет ожидать свойство Books, а вы отправите JSON с items: в результате коллекция не заполнится и останется пустой. Всегда проверяйте корректность имён!

8. Таблица: где применимы основные атрибуты

Атрибут Можно ли применять к коллекциям? Можно ли применять к элементам коллекций? Примеры использования
[JsonIgnore]
да да Скрыть список или поле внутри Book
[JsonPropertyName]
да да Переименовать Books → items или Title → name
[JsonInclude]
да да Включить приватные свойства в сериализацию
[JsonConverter]
да да Назначить специальный конвертер для списка

9. Схема сериализации коллекций с атрибутами


+-------------+
|   Library   |
+-------------+
  | Name           -- сериализуется как "Name"
  | Books          -- [JsonPropertyName("items")], сериализуется как "items": [...]
  | Cache          -- [JsonIgnore], не сериализуется
  | BookCatalog    -- [JsonPropertyName("catalog")], сериализуется как "catalog": {...}
JSON-результат примерно такой:
{
  "Name": "Городская библиотека",
  "items": [
    {
      "Title": "1984",
      "Author": {
        "Name": "Джордж Оруэлл",
        "BirthYear": 1903
      }
    },
    {
      "Title": "Большие надежды",
      "Author": {
        "Name": "Чарльз Диккенс",
        "BirthYear": 1812
      }
    }
  ],
  "catalog": {
    "978-1234567890": {
      "Title": "Зов Ктулху",
      "Author": {
        "Name": "Говард Филлипс Лавкрафт",
        "BirthYear": 1890
      }
    }
  }
}

10. Практическое значение и особенности на собеседованиях и в реальных проектах

В “боевых” условиях всегда приходится учитывать контракт внешнего API и требования к сериализации. Нужно уметь “прятать” внутренние коллекции, соответствовать регистру и стилю имён, иногда даже динамически менять схему сериализации в зависимости от версии клиента.

Типичные вопросы на собеседованиях:

  • Как сериализовать только часть данных?
  • Как сделать, чтобы свойство коллекции не попало в JSON?
  • Как сопоставить имена свойств в C# и JSON, если они различаются?
  • Можно ли скрыть отдельные элементы внутри коллекции от сериализации (например, конфиденциальную информацию)?

Ответы крутятся вокруг грамотного использования атрибутов и параметров сериализации: [JsonIgnore], [JsonPropertyName], опций JsonSerializerOptions и продуманной работы с содержимым коллекций.

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