将其他数据传递给JsonConverter [英] Pass additional data to JsonConverter

查看:97
本文介绍了将其他数据传递给JsonConverter的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我反序列化对象,其中属性之一是外键(例如,数据库表中的标识值).在反序列化期间,我想使用JsonConverter从集合中检索相应的对象.

I deserialize object where one of the properties is a foreign key (eg an identity value from a database table). During deserialization I would like to use a JsonConverter to retrieve the corresponding object from a collection.

我知道如何编写使用自定义JsonConverters的工具.我不知道如何将集合传递给JsonConverter,因为转换器是在设计时指定的(如下所示),但是该集合显然仅在运行时存在:

I know how to write a use custom JsonConverters. I don't know how to pass the collection to the JsonConverter, because the converter is specified at design time (like below), but the collection obviously only exists at runtime:

  <JsonConverter(GetType(JSonCustomConverter))>
  Public Property SomeProperty As SomePropertyClass

因此JSonCustomConverter的ReadJson应该看起来像这样:

So the JSonCustomConverter's ReadJson should look this:

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return False
  Dim value As String = reader.Value.ToString().Trim()
  retun MagicallyGetMyCollectionValue(value)
End Function

所以愚蠢的函数名称MagicallyGetMyCollectionValue只是一个占位符,用于向您显示我被卡住的位置.我不想通过全局变量访问集合,但是我也不知道如何将集合传递给ReadJson.

So the silly function name MagicallyGetMyCollectionValue is just a placeholder to show you where I am stuck. I don't want to access the collection through a global variable, but I don't know how to pass the collection to the ReadJson either.

如果有人能指出我正确的方向,我会很高兴.

I would be happy, if someone could point me in the right direction.

让我尝试给出一个更好的例子.

Let me try to give a better example.

假设我有以下课程:

class ParentObject
  <JssonConverter(GetType(JsonCustomConverter))>
  Property SomeProperty As SomePropertyClass
end class

我会反序列化我的json数据:

I would deserialize my json data like this:

 dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))

现在假设json数据不包含SomePropertyClass实例的完整表示,而仅包含键值,例如键作为字符串.假设我有一个这样的集合:

Now assume, that the json data doesn't contain the complete representation of an instance of the SomePropertyClass, but only a key value e.g. an key as string. Suppose I have a collection like this:

dim cache as Dictionary(of string, SomePropertyClass)

该缓存应包含我需要的所有实例.因此,我的JSonCustomConverter应该具有这样的ReadJson函数:

That cache shall contain all the instances that I need. So my JSonCustomConverter should have a ReadJson Function like this:

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return nothing
  Dim value As String = reader.Value.ToString().Trim()
  Dim cachedObject as SomePropertyClass = nothing
  if cache.TryGetValue(value, cachedObject) then return cachedObject
  retun Nothing ' or new SomePropertyClass(value)
End Function

因此,我希望ReadJson基于键值查找实例.

So I want the ReadJson to lookup the instance based on the key value.

我如何将cache-Dictionary传递给ReadJson函数?我可以使用包含高速缓存的singelton类和som getInstance方法来检索它,但是我不想这样做.

How would I pass the cache-Dictionary into the ReadJson function? I could use a singelton class that contains the cache an som getInstance-method to retrieve it, but I wouldn't want to do this.

推荐答案

您可以使用

You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.

首先,定义以下接口和通用转换器:

First, define the following interfaces and generic converter:

Public Interface ISerializationContext
    Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface

Public Interface INameTable(Of T)
    Function TryGetName(value As T, ByRef name as String) As Boolean    
    Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) :  Implements INameTable(Of T)
    Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

    Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

    Public Function Add(value as T, name as String) as T
        Dictionary.Add(name, value)
        ReverseDictionary.Add(value, name)              
        Return value
    End Function

    Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
        Return ReverseDictionary.TryGetValue(value, name)
    End Function

    Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
        Return Dictionary.TryGetValue(name, value)
    End Function
End Class

Public Class ObjectToNameConverter(Of T)
    Inherits JsonConverter

    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return GetType(T) = objectType
    End Function

    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Dim tValue = CType(value, T)
        Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
        If context Is Nothing
            Throw New JsonSerializationException("No ISerializationContext.")
        End If

        Dim nameTable as INameTable(Of T) = Nothing
        If (Not context.TryGetNameTable(Of T)(nameTable))
            Throw New JsonSerializationException("No NameTable.")
        End If

        Dim name as String = Nothing
        if (Not nameTable.TryGetName(tValue, name))
            Throw New JsonSerializationException("No Name.")
        End If

        writer.WriteValue(name)
    End Sub

    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
        If context Is Nothing
            Throw New JsonSerializationException("No ISerializationContext.")
        End If

        Dim nameTable as INameTable(Of T) = Nothing
        If (Not context.TryGetNameTable(Of T)(nameTable))
            Throw New JsonSerializationException("No NameTable.")
        End If

        Dim name As String = serializer.Deserialize(Of String)(reader)
        If name Is Nothing Then
            Return Nothing
        End If

        dim tValue as T = Nothing
        nameTable.TryGetValue(name, tValue)
        return tValue
    End Function
End Class

接下来,定义以下具体实现:

Next, define the following concrete implementations:

Public Class RootObject
    <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
    Public Property SomeProperty As SomePropertyClass   
End Class

Public Class SomePropertyClass
End Class

Public Class MySerializationContext : Implements ISerializationContext
    Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
        Return SomePropertyNameTable.Add(value, name)
    End Function

    Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)

    Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
        if (GetType(T) Is GetType(SomePropertyClass))
            table = SomePropertyNameTable
            return True
        End If

        table = Nothing
        return False
    End Function
End Class   

现在,您可以在反序列化过程中用其名称替换SomePropertyClass的实例,如下所示:

Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:

Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
    .Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty) 
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))

注意:

  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T))是通用的,因此可以同时为多种类型的对象支持对象到名称的替换,而转换器之间不会互相干扰.

  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.

但是,具体的实现不必如此通用.在这里MySerializationContext仅支持SomePropertyClass实例的名称替换.其他可以根据需要添加.

The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.

中所述,Json.NET缓存类型的序列化信息吗? ,Newtonsoft建议缓存DefaultContractResolver及其子类型的实例,以获得最佳性能.因此,最好是通过StreamingContext.Context传递附加数据,而不是通过DefaultContractResolver子类的新分配实例传递数据.

As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.

.net小提琴#1的示例工作此处.

Sample working .Net fiddle #1 here.

作为替代方案,尽管上面的设计有效,但我认为将<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))>SomeProperty中删除,而传递经过适当初始化的包含本地引用的ObjectToNameConverter(Of SomePropertyClass)会更简单.某些INameTable(Of SomePropertyClass)中,在 JsonSerializerSettings.Converters 中.

As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.

像这样定义转换器和接口.请注意,ObjectToNameConverter(Of T)现在具有参数化的构造函数,并且不再需要ISerializationContext:

Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:

Public Interface INameTable(Of T)
    Function TryGetName(value As T, ByRef name as String) As Boolean    
    Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) :  Implements INameTable(Of T)
    Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

    Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

    Public Function Add(value as T, name as String) as T
        Dictionary.Add(name, value)
        ReverseDictionary.Add(value, name)              
        Return value
    End Function

    Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
        Return ReverseDictionary.TryGetValue(value, name)
    End Function

    Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
        Return Dictionary.TryGetValue(name, value)
    End Function
End Class

Public Class ObjectToNameConverter(Of T)
    Inherits JsonConverter

    Private Property NameTable as INameTable(Of T)

    Public Sub New(nameTable as INameTable(Of T))
        If nameTable Is Nothing 
            Throw new ArgumentNullException("nameTable")
        End If
        Me.NameTable = nameTable
    End Sub

    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return GetType(T) = objectType
    End Function

    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Dim tValue = CType(value, T)

        Dim name as String = Nothing
        if (Not NameTable.TryGetName(tValue, name))
            Throw New JsonSerializationException("No Name.")
        End If

        writer.WriteValue(name)
    End Sub

    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim name As String = serializer.Deserialize(Of String)(reader)
        If name Is Nothing Then
            Return Nothing
        End If

        dim tValue as T = Nothing
        NameTable.TryGetValue(name, tValue)
        return tValue
    End Function
End Class

然后序列化如下:

dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }

Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))

Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty) 
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))

通过这种方式处理,消除了静态序列化方法对第一个解决方案中存在的运行时代码的依赖.现在,所有名称替换逻辑都在运行时在一个位置处理.

Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.

样本小提琴#2 此处.

这篇关于将其他数据传递给JsonConverter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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