通过两阶段反序列化对循环引用进行反序列化

时间:2018-09-28 22:17:54

标签: c# json serialization json.net

我有一个使用引用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. 构造对象
  4. AddReference(id,newObj)
  5. 填充属性

我的问题:

  1. 为什么要选择后者,我的建议流程中是否缺少某些东西?

  2. 我如何克服这个问题,只有一个“裸”对象用于引用,然后才能真正解析引用?

2 个答案:

答案 0 :(得分:2)

您看到的两阶段反序列化的出现是因为您的Node类仅具有参数化构造函数。如Issue with serializing/deserializing object graph with self-referencing objects in combination with JSON constructor. #715中所述:

  

JamesNK 于2015年11月28日发表评论

     
    

非默认构造函数和保留引用不能很好地协同工作,因为类型的子值必须在创建父类之前先反序列化,因此引用解析为空。

  

因此,由于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; }

(如果愿意,可以使用{Data contract attributes代替Json.NET属性。)

注意:

答案 1 :(得分:0)

好吧,我找到了解决问题的方法:

我执行的第一个反序列化,我使用自定义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;
        }
    }

如果出于某种原因,构造函数需要循环引用,
它仍然会导致循环,但是如果没有第二个构造函数,就无法创建。