Почему JsonConvert по-разному обрабатывает конструкторы по умолчанию и параметризованные конструкторы? - PullRequest
1 голос
/ 29 мая 2020

Я пробовал Json. net сериализовать и десериализовать словари, и благодаря этому сообщению я смог найти хорошее решение для сериализации словарей простым способом.

Он работал отлично, но в определенных обстоятельствах он сломался (для меня) бессмысленным образом, так что я не мог не потратить следующие три часа на отладку.

Вот проблема. При сериализации этого класса

public class ReferenceTesting
{
    public List<Scenario> scenarios = new List<Scenario>();
    private Dictionary<Scenario, float> _Dict = new Dictionary<Scenario, float>();
    [JsonProperty]
    public List<KeyValuePair<Scenario, float>> SerializedDict
    {
        get { return _Dict.ToList(); }
        set { _Dict = value.ToDictionary(x => x.Key, x => x.Value); }
    }
    public ReferenceTesting(int number = 0)
    {
        for (int i = 0; i < number; i++)
        {
            Scenario s1 = new Scenario();
            scenarios.Add(s1);
            _Dict.Add(s1, i);
        }
    }
    public override string ToString()
    {
        string s = "";
        for (int i = 0; i < scenarios.Count(); i++)
        {
            Scenario scenario = scenarios[i];
            s += $"scenario{i} \n";
        }
        foreach (KeyValuePair<Scenario, float> scenario in SerializedDict)
        {
            s += $"Key: {scenario.Key}, Value: {scenario.Value} \n";
        }
        return s;
    }
}

Все работает, как ожидалось, то есть, когда я создаю экземпляр

new Reference(3);

, а затем сериализую и десериализуйте, я получаю объект с ожидаемыми 3 элементами в list и 3 элемента в словаре.

Вывод:

scenario0 
scenario1 
scenario2 
Key: Scenario, Value: 0 
Key: Scenario, Value: 1 
Key: Scenario, Value: 2 

Однако, добавив конструктор по умолчанию

public ReferenceTesting() { }

, сериализация работает, выписывая 3 элемента в списке и словаре, но десериализация не работает со свойством. Это означает, что я получаю

scenario0 
scenario1 
scenario2 

в качестве вывода.

Большой сюрприз заключается в том, что два конструктора делают одно и то же - что равно ничего , когда число = 0 (это когда Json. net создает его, я дважды проверял). Это означает, что сериализатор должен делать что-то под капотом, чтобы обрабатывать свойство по-разному, если есть или нет конструктор по умолчанию.

1 Ответ

1 голос
/ 02 июня 2020

Каким образом Json. NET десериализует объект по-разному, когда у него есть параметризованный конструктор по сравнению с конструктором по умолчанию?

Json. NET - это потоковая передача десериализатор . По возможности он десериализуется по мере прохождения потока через JSON вместо предварительной загрузки полного JSON в промежуточное представление перед окончательной десериализацией.

Таким образом, при десериализации объекта JSON с помощью конструктора по умолчанию он сначала создает соответствующий объект. Net. Затем он рекурсивно заполняет члены. Net объекта путем потоковой передачи через пары ключ / значение в JSON до конца объекта JSON. Для каждой обнаруженной пары он находит соответствующий член. Net. Если значение является примитивным типом, он десериализует примитив и устанавливает значение. Но если значение является сложным типом (JSON объект или массив), оно при необходимости создает дочерний объект, устанавливает значение обратно в родительский объект, а затем заполняет его рекурсивно, продолжая поток.

Однако , при десериализации объекта с помощью параметризованного конструктора , Json. NET не может использовать этот алгоритм потоковой передачи и вместо этого должен сначала полностью десериализовать объект JSON в промежуточную таблицу десериализации. Net name / значение, сопоставляя каждое значение JSON с соответствующим ему аргументом или свойством конструктора. Net по имени, а затем десериализуя его до типа, объявленного в. Net. Только после этого можно создать объект, передав десериализованные параметры конструктора в конструктор и установив остаток в качестве значений свойств.

Подробнее об этом процессе см.

(Существует третий алгоритм для ISerializable объектов, которые не применимы в вашем случае.)

Почему мое суррогатное свойство public List<KeyValuePair<Scenario, float>> SerializedDict не десериализуется правильно при десериализации с помощью конструктора по умолчанию?

Причина объяснена в этот ответ от до Почему все коллекции в моем POCO являются нулевыми при десериализации некоторых действительных json с. NET Newtonsoft. Json компонентом , и возникает из-за особенностей алгоритма Json. NET Populate():

  1. Он вызывает метод получения в родительском классе, чтобы получить текущий значение десериализуемого свойства.

  2. Если значение null и если не используется настраиваемый конструктор , i t выделяет экземпляр возвращаемого типа свойства (используя метод JsonContract.DefaultCreator для типа).

  3. Он вызывает метод установки в родительском элементе для установки выделенного экземпляр обратно в родительский.

  4. Продолжается заполнение экземпляра типа.

  5. Он не возвращает экземпляр во второй раз, после того, как он был заполнен.

Таким образом, установщик для SerializedDict не вызывается после заполнения списка.

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

Как создать суррогатную коллекцию свойство, которое работает в обоих сценариях ios?

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

public class ReferenceTesting
{
    public KeyValuePair<Scenario, float> [] SerializedDict
    {
        get { return _Dict.ToArray(); }
        set { _Dict = value.ToDictionary(x => x.Key, x => x.Value);  }
    }
    // Remainder unchanged

Вы можете сделать свойство массива private, если хотите, отметив это с помощью [JsonProperty].

Между прочим, ваша текущая десериализация создает дублирующиеся объекты Scenario в коллекциях scenarios и _Dict, как показано в демонстрационной скрипте # 1 здесь .

Один из способов исправить это - сериализовать только _Dict (при условии, что все сценарии ios находятся в словаре). Другой вариант - использовать PreserveReferencesHandling, например, добавив [JsonObject(IsReference = true)] к Scenario:

[JsonObject(IsReference = true)]
public class Scenario
{
    // Remainder unchanged
}

Примечания:

  • Там не является стандартом для сериализации ссылок в JSON. Реализация Json. NET может не совпадать с реализацией других сериализаторов.

  • PreserveReferencesHandling не работает для объектов с параметризованными конструкторами (см. здесь для подробностей), поэтому убедитесь, что у Scenario его нет.

Демо-скрипт # 2 здесь показывает, что все работает правильно с конструктором по умолчанию, и # 3 здесь с параметризованным конструктором.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...