Десериализация циркулярных ссылок двухфазной десериализацией - PullRequest
0 голосов
/ 29 сентября 2018

У меня есть сериализатор / десериализатор, который использует ссылки PreserveReferencesHandling = PreserveReferencesHandling.All.

Проблема в том, что у меня есть циклические ссылки.

Вот очень простой пример.

class Node
{
    public Node(object value)
    {
        Value = value;
    }
    public object Value { get; set; }
    public Node Left { get; set; }
    public Node Right { get; set; }
}

Мой тестовый сценарий:

var obj = new Node("o")
{
    Left = new Node("oL"),
    Right = new Node("oR")
};
obj.Right.Right = obj; // Circular reference!

При десериализации у меня появляется следующее IReferenceResolver

private class InternalReferenceResolver : IReferenceResolver
{
    private readonly Deserializer _par;

    public InternalReferenceResolver(Deserializer par)
    {
        _par = par;
    }

    public object ResolveReference(object context, string reference)
    {
        object refObject;
        if (!_par._processed.TryGetValue(reference, out refObject))
        {
            refObject = _par.DeserializeObject(reference);
        }
        return refObject;
    }

    public string GetReference(object context, object value)
    {
        throw new NotSupportedException("Only for Serialization");
    }

    public bool IsReferenced(object context, object value)
    {
        return false;
    }

    public void AddReference(object context, string reference, object value)
    {
        _par._processed.Add(reference, value);
    }
}    

Как вы можете видеть, когда JSON.NET сообщает мненового ref-> объекта (через AddReference()) я добавляю его в словарь.

Когда JSON.NET запрашивает объект для конкретной ссылки (через ResolveReference()), я рекурсивную и десериализовываю эту ссылку.

Проблема заключается в том, что JSON.NET вызывает ResolveReference() для каждой ссылки на объект, прежде чем он вызовет AddReference().

Я ожидаю, что поток десериализации будет:

  1. Определить тип объекта
  2. Построить объект
  3. AddReference (id, newObj)
  4. Разрешить ссылки + Заполнить свойства

Я вижу следующее:

  1. Определение типа объекта
  2. Разрешение ссылок
  3. Построениеobject
  4. AddReference (id, newObj)
  5. Заполнить свойства

Мои вопросы:

  1. Почему это сделаново-вторых, я что-то упускаю с моим предложенным потоком?

  2. как я могу преодолеть эту проблему, имея «голый» объект только для ссылок и только потом фактически разрешать ссылки?

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Ну, я нашел решение для своей проблемы:

При первой десериализации, которую я выполняю, я использую пользовательский IContractResolver, который исключает все свойства, которые не имеют отношения к конструктору ...

на втором проходе, я использую Заполнить и использую значение по умолчанию IContractResolver

    private class InternalOnlyCtorContractResolver : IContractResolver
    {
        private readonly IContractResolver _base;

        public InternalOnlyCtorContractResolver(IContractResolver _base)
        {
            this._base = _base;
        }

        public JsonContract ResolveContract(Type type)
        {
            var contract = _base.ResolveContract(type);
            var objectContract = contract as JsonObjectContract;
            if (objectContract != null)
            {
                var creatorParameters = new HashSet<string>(objectContract.CreatorParameters.Select(p => p.PropertyName));
                var irrelevantProperties = objectContract.Properties
                    .Where(p => !creatorParameters.Contains(p.PropertyName))
                    .ToArray();
                foreach (var irrelevantProperty in irrelevantProperties)
                {
                    objectContract.Properties.Remove(irrelevantProperty);
                }
                //TODO Can be optimized better
            }
            return contract;
        }
    }

Если по какой-то причине Конструктору нужна Циркулярная ссылка,
Это все еще вызывает цикл, нов любом случае это невозможно создать без второго конструктора.

0 голосов
/ 29 сентября 2018

Двухэтапная десериализация , которую вы видите, возникает потому, что ваш класс Node имеет только параметризованный конструктор.Как объяснено в Проблема с сериализацией / десериализацией графа объектов с самоссылающимися объектами в сочетании с конструктором JSON.# 715 :

JamesNK прокомментировано 28 ноября 2015 г.

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

Таким образом, поскольку Value являетсяВ любом случае, изменяемое свойство, вы должны добавить конструктор без параметров к Node:

class Node
{
    public Node() : this(null) { }

    public Node(object value)
    {
        Value = value;
    }
    // Remainder unchanged.
}

. Он может быть закрытым, если вы пометите его как [JsonConstructor] или десериализовать, используя параметр ConstructorHandling.AllowNonPublicDefaultConstructor.И если бы Value были неизменяемыми, вам нужно было бы установить его в частном порядке и пометить его как [JsonProperty]

[JsonProperty]
public object Value { get; private set; }

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

Примечания:

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