如何反序列化对某些容器类型具有只读反向引用的类型的实例,同时也要反序列化该类型的实例? [英] How can I deserialize instances of a type that has read-only back-references to some container type also being deserialized?

查看:134
本文介绍了如何反序列化对某些容器类型具有只读反向引用的类型的实例,同时也要反序列化该类型的实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Let's say I have two types, a Document and a Child. The Child is nested fairly deeply within the Document, and contains a back-reference to the parent that must needs be passed into its constructor. How can I deserialize such an object graph with Json.NET and pass the parent into the child's constructor?

这是一个具体示例,受 反序列化为List(Of T)时传递构造函数参数 Ama :

Here's a concrete example, inspired by Pass constructor arguments when deserializing into a List(Of T) by 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中的父引用是只读的,必须传递到构造函数中.

  • The parent reference in Child is read-only and must be passed into the constructor.

我无法修改DocumentChild的类定义.

I cannot modify the class definitions for Document and Child.

DocumentChild此处显示的更为复杂,因此不建议先加载到JToken层次结构中然后手动进行构建.

Document and Child are more complicated that shown here, so loading into a JToken hierarchy then constructing manually is not preferred.

如何将JSON反序列化为这样的数据模型,以正确初始化父级的方式构造子级列表?

How can I deserialize JSON to such a data model, constructing the list of children with the parent properly initialized?

推荐答案

由于无法修改DocumentChild的定义,因此一种方法是使用

Since the definitions for Document and Child cannot be modified, one way to do this would be with a custom contract resolver that returns contracts that track the current document being deserialized in some ThreadLocal(Of Stack(Of Document)) stack, and allocate instances of MyObject using the topmost document.

以下合同解析器可以完成这项工作:

The following contract resolver does the job:

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;
        }
    }
}

注意:

Newtonsoft的性能提示中所述,

为避免每次您使用JsonSerializer都需要重新创建合同的开销,您应该创建一次合同解析器并重新使用它.

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.

  • ThreadLocal<T> is disposable, so if you don't plan to cache your WordContractResolver you should probably make it disposable also, and dispose of the threadlocal in the dispose method.

    演示小提琴此处(vb.net) 查看全文

  • 登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆