我反序列化对象,其中属性之一是外键(例如,数据库表中的标识值)。在反序列化期间,我想使用JsonConverter从集合中检索相应的对象。
我知道如何编写使用自定义的JsonConverters。我不知道如何将集合传递给JsonConverter,因为转换器是在设计时指定的(如下所示),但是该集合显然仅在运行时存在:
<JsonConverter(GetType(JSonCustomConverter))>
Public Property SomeProperty As SomePropertyClass
因此JSonCustomConverter的ReadJson应该看起来像这样:
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。
如果有人能指出我正确的方向,我会很高兴。
编辑:让我尝试给出一个更好的例子。
假设我有以下课程:
class ParentObject
<JssonConverter(GetType(JsonCustomConverter))>
Property SomeProperty As SomePropertyClass
end class
我会反序列化我的json数据:
dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))
现在假设json数据不包含SomePropertyClass实例的完整表示,而仅包含键值,例如键作为字符串。假设我有一个这样的集合:
dim cache as Dictionary(of string, SomePropertyClass)
该缓存应包含我需要的所有实例。因此,我的JSonCustomConverter应该具有如下的ReadJson函数:
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根据键值查找实例。
如何将cache-Dictionary传递给ReadJson函数?我可以使用包含高速缓存的singelton类和som getInstance方法来检索它,但是我不想这样做。
答案 0 :(得分:1)
按照@ doom87er的要求,我将分享对我有用的代码。该解决方案基于@dbc的注释,并进行了一些更改。请在下面的代码中将其更像是概念性代码:我不得不更改一些名称,并省略一些逻辑运算符,这对于本概念证明不是必需的。所以里面可能有错别字。
主要解决方案是将DefaultContractResolver子类化,然后将缓存字典添加到该类中。像这样:
Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)
Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class
然后您使用JsonSerializerSettings这样传递自定义合同解析器:
Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)
其中prefilledCache是包含SomePropertyClass对象的字典的实例。
最后一步是在我的JsonConverter的ReadJson函数中检索缓存(我将其附加到SomeProperty上,如原始文章的示例代码所示):
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function
我尝试过,它似乎起作用了。
因此在坚果壳中
如果您对我可能会错过的任何收获发表评论,我会很高兴,但是我认为这应该是我可以接受的解决方案。
感谢您的评论和帮助。 Sascha
答案 1 :(得分:1)
您可以通过StreamingContext.Context
使用JsonSerializer.Context
将其他数据传递到自定义JsonConverter
。使用这种机制,可以将类实例以通用方式映射到名称。
首先,定义以下接口和通用转换器:
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
接下来,定义以下具体实现:
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
的实例,如下所示:
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))
是通用的,因此可以同时为多种类型的对象支持对象到名称的替换,而转换器之间不会互相干扰。
但是,具体的实现不必如此通用。这里MySerializationContext
仅支持SomePropertyClass
实例的名称替换。其他可以根据需要添加。
如 Does Json.NET cache types' serialization information? 中所述,Newtonsoft建议缓存DefaultContractResolver
及其子类型的实例,以获得最佳性能。因此,最好是通过StreamingContext.Context
传递附加数据,而不是通过DefaultContractResolver
子类的新分配实例传递附加数据。
.net小提琴#1 here的工作示例。
作为替代方案,尽管上述设计有效,但我认为将<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))>
中的SomeProperty
删除,而传递经过适当初始化的{{1} },在JsonSerializerSettings.Converters
中包含对某些ObjectToNameConverter(Of SomePropertyClass)
的本地引用。
这样定义转换器和接口。请注意,INameTable(Of SomePropertyClass)
现在具有参数化构造函数,并且不再需要ObjectToNameConverter(Of T)
:
ISerializationContext
然后序列化如下:
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
通过这种方式处理,消除了静态序列化方法对第一个解决方案中存在的运行时代码的依赖。现在,所有名称替换逻辑都在运行时在一个位置处理。
样本小提琴#2 here。