如何反序列化对某些容器类型具有只读反向引用的类型实例,这些实例也被反序列化?



假设我有两种类型,一种Document,一种ChildChild嵌套在Document中相当深,并且包含对父级的反向引用,该引用必须传递给其构造函数。 如何使用 Json.NET 反序列化此类对象图并将父项传递到子构造函数中?

下面是一个具体的例子,灵感来自Ama 反序列化为 List(Of T) 时的 Pass 构造函数参数

Class Document
Public Property MyObjects as List(Of Child) = new List(Of Child)()
End Class
Class Child
Private ReadOnly _Parent As Document
Sub New(Parent As Document)
_Parent = Parent
End Sub
Property Foo As String
Property Bar As String
Function GetParent() As Document
Return _Parent
End Function
End Class

使用相应的 JSON:

{
"MyObjects": [
{
"Foo": "foo",
"Bar": "bar"
}
]
}

笔记:

  • Child中的父引用是只读的,必须传递到构造函数中。

  • 我无法修改DocumentChild的类定义。

  • DocumentChild比此处显示的更复杂,因此不建议加载到JToken层次结构中然后手动构造。

如何将 JSON 反序列化为此类数据模型,并在正确初始化父级的情况下构建子项列表?

由于无法修改DocumentChild的定义,因此执行此操作的一种方法是使用自定义协定解析程序,该解析程序返回跟踪某些ThreadLocal(Of Stack(Of Document))堆栈中正在反序列化的当前文档的协定,并使用最顶层的文档分配MyObject实例。

以下协定解析器完成这项工作:

Public Class DocumentContractResolver
Inherits DefaultContractResolver
Private ActiveDocuments As ThreadLocal(Of Stack(Of Document)) = New ThreadLocal(Of Stack(Of Document))(Function() New Stack(Of Document))
Protected Overrides Function CreateContract(ByVal objectType As Type) As JsonContract
Dim contract = MyBase.CreateContract(objectType)
Me.CustomizeDocumentContract(contract)
Me.CustomizeMyObjectContract(contract)
Return contract
End Function
Private Sub CustomizeDocumentContract(ByVal contract As JsonContract)
If GetType(Document).IsAssignableFrom(contract.UnderlyingType) Then
contract.OnDeserializingCallbacks.Add(Sub(o, c) ActiveDocuments.Value.Push(CType(o, Document)))
contract.OnDeserializedCallbacks.Add(Sub(o, c) ActiveDocuments.Value.Pop())
End If
End Sub
Private Sub CustomizeMyObjectContract(ByVal contract As JsonContract)
If (GetType(Child) = contract.UnderlyingType) Then
contract.DefaultCreator = Function() New Child(ActiveDocuments.Value.Peek())
contract.DefaultCreatorNonPublic = false
End If
End Sub
End Class

然后像这样使用它:

Dim contractResolver = New DocumentContractResolver() ' Cache this statically somewhere
Dim settings = New JsonSerializerSettings() With { .ContractResolver = contractResolver }
Dim doc2 = JsonConvert.DeserializeObject(Of Document)(jsonString, settings)

在 c# 中:

public class DocumentContractResolver : DefaultContractResolver
{
ThreadLocal<Stack<Document>> ActiveDocuments = new ThreadLocal<Stack<Document>>(() => new Stack<Document>());
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
CustomizeDocumentContract(contract);
CustomizeMyObjectContract(contract);
return contract;
}
void CustomizeDocumentContract(JsonContract contract)
{
if (typeof(Document).IsAssignableFrom(contract.UnderlyingType))
{
contract.OnDeserializingCallbacks.Add((o, c) => ActiveDocuments.Value.Push((Document)o));
contract.OnDeserializedCallbacks.Add((o, c) => ActiveDocuments.Value.Pop());
}
}
void CustomizeMyObjectContract(JsonContract contract)
{
if (typeof(Child) == contract.UnderlyingType)
{
contract.DefaultCreator = () => new Child(ActiveDocuments.Value.Peek());
contract.DefaultCreatorNonPublic = false;
}
}
}

笔记:

  • 如果在反序列化期间发生异常,则可能无法正确清除ActiveDocuments。 您可能需要添加序列化错误处理程序来执行此操作。

  • 正如Newtonsoft的性能提示中所解释的那样,

    若要避免每次使用 JsonSerializer 时重新创建协定的开销,应创建一次协定解析程序并重复使用它。

  • ThreadLocal<T>是一次性的,所以如果你打算缓存你的WordContractResolver你可能也应该让它一次性,并在dispose方法中释放threadlocal。

演示小提琴在这里 (vb.net) 和这里 (c#)。

相关内容

  • 没有找到相关文章

最新更新