gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 12:02:59 24 4
gpt4 key购买 nike

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

这是一个具体的例子,灵感来自 Pass constructor arguments when deserializing into a List(Of T) 来自 Ama :

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 反序列化为这样的数据模型,从而构建父级正确初始化的子级列表?

最佳答案

由于 Document 的定义和 Child无法修改,一种方法是使用 custom contract resolver返回跟踪当前文档反序列化的契约(Contract) ThreadLocal(Of Stack(Of Document)) 堆栈,并分配 MyObject 的实例使用最顶层的文档。

以下契约(Contract)解析器完成这项工作:

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可能无法正确清除。您可能想添加 serialization error handler这样做。

  • Newtonsoft's performance tips 中所述,

    To avoid the overhead of recreating contracts every time you use JsonSerializer you should create the contract resolver once and reuse it.

  • ThreadLocal<T> 是一次性的,所以如果您打算缓存您的 WordContractResolver您可能也应该将其设为一次性,并在 dispose 方法中处理 threadlocal。

演示 fiddle here (vb.net)here (c#) .

关于c# - 如何反序列化具有对某些容器类型的只读反向引用的类型的实例也被反序列化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56316631/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com