JsonPolymorphismDemo

Минимальный пример на C# (.NET 9): полиморфизм + JSON через System.Text.Json. Одна точка приёма строки — один сериализатор и один десериализатор на всё приложение. Клиент не задумывается, какую строку он передаёт, — но получает валидный объект.

Дерево классов

Message (abstract)
├── GameMessage (abstract)      — принимает Game
│   ├── Data
│   ├── Command
│   ├── SetSettings
│   └── InitChannels
└── KinesisMessage (abstract)   — принимает Kinesis
    └── RequestLimitOffset

Два промежуточных абстрактных класса задают направление движения JSON (Game / Kinesis), у каждого — свои потомки. Клиент кода работает только с корнем Message.

Формат JSON

Тип сообщения различается полем-дискриминатором $type (имя класса):

{"$type":"Command","action":"move","args":["north","3"]}
{"$type":"RequestLimitOffset","limit":50,"offset":100}

Сериализация и десериализация дают один и тот же формат (round-trip).

Единственная точка приёма

Message msg = MessageJson.Deserialize(json); // любая строка -> валидный Message
string json = MessageJson.Serialize(msg);    // Message -> та же строка

Внутри — один JsonSerializerOptions и один JsonConverter<Message> (MessageJson.cs). Вся настройка локальна, глобальное состояние не трогается.

Направление выбирается обычным pattern matching по абстрактным уровням:

string route = msg switch
{
    GameMessage => "Game",
    KinesisMessage => "Kinesis",
    _ => "???",
};

Ошибка на неизвестном/отсутствующем типе — JsonException, без «молчаливых» значений по умолчанию.

Пути полиморфизма в JSON и выбор

Путь Как работает Плюсы Минусы
System.Text.Json JsonPolymorphic/JsonDerivedType-атрибуты (A) атрибуты на корне перечисляют потомков встроено в STJ, минимум кода список потомков размазан атрибутами по классу; иерархия Game/Kinesis остаётся скрытой; добавление класса = правка атрибутов
Свой конвертер JsonConverter<Message> (B) вручную читаем/пишем $type, дальше обычный STJ полный контроль формата; явная таблица «имя → тип» в одном месте; различия корректно ловятся ~50 строк кода конвертера
Newtonsoft TypeNameHandling (C) сериализатор сам вставляет $type с именем типа ноль своего кода небезопасно при приёме чужих строк: имя типа разрешается в любой тип сборки (уязвимость из документации Newtonsoft); отдельная зависимость

Выбран путь B (свой конвертер): формат задан явно (короткий $type), таблица типов собрана в одном месте (MessageJson.ByType), произвольных TypeNameHandling-подстановок нет — вход доверяет только пяти известным именам.

Как расширить

Новый тип сообщения = два шага, без правки конвертера:

  1. объявить класс-потомок GameMessage или KinesisMessage;
  2. добавить одну строку в MessageJson.ByType.

Структура

  • Messages/Messages.cs — дерево классов (только данные, без атрибутов);
  • MessageJson.cs — единственный сериализатор/десериализатор;
  • Program.cs — пример использования: приём строк, round-trip, обработка ошибки, маршрутизация по направлению.

Запуск

dotnet run

Пример вывода:

{"$type":"Data","payload":"hello"}                          -> Data
...
JsonException: Unknown message type: "Nope".
Data               -> Game
RequestLimitOffset -> Kinesis
S
Description
C# пример: полиморфизм + System.Text.Json, единая точка сериализации
Readme
107 KiB
0 Stars 1 Watchers 0 Forks
Languages
C# 100%