将其他数据传递给JsonConverter [英] Pass additional data to 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.
推荐答案
您可以使用 JsonSerializer.Context
.使用这种机制,可以将类实例以通用方式映射到名称.
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屋!