Явная реализация интерфейса и System.Text.Json

<< к списку постов

Явная реализация интерфейса и System.Text.Json

14.03.2022

Переписывал вчера структуру классов в krvnotes и наткнулся на странный баг при сериализации и десериализации класса Folder, описывающего папку с заметками.

Приложение устроено так, что после любого действия оно сохраняет состояние в JSON, а при открытии загружает все папки и находящиеся в них заметки из этого JSONа.

В какой-то момент я очередной раз нажимаю F5 и вместо гуя вижу исключение десериализации.

Exception
Чево случилось-то

Судя по сообщению, проблема в том, что десериализатор не смог сопоставить каждый из параметров конструктора класса Folder с его свойством. Однако дальше ничего не ясно , потому что параметры конструктора называются name и notes, а свойства класса -- Name и Notes. Вроде всё должно работать.

Folder class
Класс Folder

IFolder interface
Интерфейс IFolder

PropertyNameCaseInsensitive выставлен в true, оба свойства публичные - в чём проблема?

Первая мысль, пришедшая в голову -- наверно, дело в том, что сериализатор не смог сериализовать явно реализованное свойство Notes, записал в JSON только свойство Name, а теперь на десериализации не понимает, что передавать во второй параметр конструктора.

Чтобы убедиться в этом, я открыл JSON.

JSON

Как ни странно, в JSONе всё в порядке. Всё отлично сериализовалось, вот они Notes слева направо -- в чём тогда проблема всё это десериализовать обратно?

Решение

После многочасового мучения я наконец заметил, что мой код для сериализации и для десериализации немного отличается.

Serialization code
Deserialization code
Найди System.OverflowException отличий

Конкретнее говоря, сериализую я IEnumerable<IFolder>, а десериализую в IEnumerable<Folder>. Я обратил на это внимание и решил попробовать привести всё к одному типу. Десериализовывать в интерфейсы нельзя, поэтому вместо этого я решил сериализовать класс.

Cast
Я добавил каст к Folder перед сериализацией...

Broken JSON
... и вот что я увидел в JSON.

Здесь и стало ясно, что сериализатор STJ игнорирует явно реализованные свойства интерфейсов при работе с классами. Всё это время сериализация работала только потому, что на вход отправлялись объекты IFolder, которые для сериализатора содержали просто два публичных свойства. Но при попытке десериализации в класс сериализатор не находил публичное свойство для параметра notes и кидал исключение.

Всё, что мне надо было сделать -- превратить поле Notes в свойство ([JsonInclude], по идее, тоже бы сработал, но мне не хотелось тащить ещё один атрибут), и всё заработало как надо.

Happy end

SIKE!

Нажимаем F5 и получаем ровно то же исключение. Тратим ещё час и выясняем: помимо того, что у каждого параметра и соответствующего свойства должны совпадать имена, также в точности должны совпадать их типы. Если тип параметра -- IEnumerable<Note>, а тип свойства -- List<Note>, работать ничего не будет, даже если для десериализации используется конструктор.
К сожалению, в exception message про точное совпадение типов не сказано, поэтому на выяснение этой детали ушло ещё достаточно времени. Oh well.

Final Folder class
Финальный вид класса Folder

<< к списку постов^^ наверх