我有一个使用引用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()
。
我希望反序列化的流程为:
我看到的是:
我的问题:
为什么要选择后者,我的建议流程中是否缺少某些东西?
我如何克服这个问题,只有一个“裸”对象用于引用,然后才能真正解析引用?
答案 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属性。)
注意:
由于您的问题缺少complete and verifiable example,因此您的代码可能还会遇到其他由于添加无参数构造函数而无法解决的问题。
有关相关问题,请参见Usage of non-default constructor breaks order of deserialization in Json.net。
有关相关问题,请参见PreserveReferencesHandling.Objects deserialize does not work with non-default constructor #678。
另一件事,我可以告诉Json.NET仅处理构造函数参数,而保留其余的...吗?
不符合问题715。由于JSON对象是名称/值对的无序集合,因此Json.NET需要解析整个对象以确保它已加载所有构造函数参数。由于它是单遍序列化程序,因此在构造对象之前,非构造函数参数将被加载到某些东西中。 Json.NET已选择一步将其反序列化为最终目标成员类型,而不是中间JToken
以及后来的最终成员类型。可以在JsonSerializerInternalReader.ResolvePropertyAndCreatorValues()
中看到。
答案 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;
}
}
如果出于某种原因,构造函数需要循环引用,
它仍然会导致循环,但是如果没有第二个构造函数,就无法创建。