将其他数据传递给JsonConverter

时间:2018-11-07 16:17:00

标签: vb.net json.net

我反序列化对象,其中属性之一是外键(例如,数据库表中的标识值)。在反序列化期间,我想使用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方法来检索它,但是我不想这样做。

2 个答案:

答案 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

我尝试过,它似乎起作用了。

因此在坚果壳中

  1. 将DefaultContractResolver子类化,并包含所需的所有其他数据。
  2. 通过JsonSerializerSettings中的其他数据传递自定义合同解析器的实例。
  3. 在您的JsonConverter中,将传递的合同解析器试播回您的自定义合同解析器,在那里您将拥有其他数据。

如果您对我可能会错过的任何收获发表评论,我会很高兴,但是我认为这应该是我可以接受的解决方案。

感谢您的评论和帮助。 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