首页 > 解决方案 > Newtonsoft JSON - 如何反序列化自定义集合?

问题描述

我尝试使用 Newtonsoft.net 反序列化自定义集合。

 Public Class ObservableCollectionAdvanced(Of T As INotifyPropertyChanged)
    Implements IEnumerable(Of T)
    Implements INotifyPropertyChanged
    Implements INotifyCollectionChanged
    Implements INotifyCollectionItemPropertyChanged

    Private ReadOnly _collection As New ObservableCollection(Of T)

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Public Event CollectionChanged As NotifyCollectionChangedEventHandler Implements INotifyCollectionChanged.CollectionChanged
    Public Event CollectionItemPropertyChanged As PropertyChangedEventHandler Implements INotifyCollectionItemPropertyChanged.CollectionItemPropertyChanged

    Public Sub New()
        AddHandler _collection.CollectionChanged, Sub(sender As Object, e As NotifyCollectionChangedEventArgs)
                                                      RaiseEvent CollectionChanged(Me, e)
                                                  End Sub
    End Sub

    Default Public ReadOnly Property Item(Index As Integer) As T
        Get
            If Index >= 0 AndAlso Index < _collection.Count Then
                Return _collection(Index)
            Else
                Return Nothing
            End If
        End Get
    End Property

    Public Sub Add(Item As T)
        If Item IsNot Nothing Then
            Me.AddHandlerToItem(Item)
            _collection.Add(Item)
        End If
    End Sub

    Private Sub AddHandlerToItem(Item As T)
        If Not TypeOf Item Is INotifyPropertyChanged Then Exit Sub
        AddHandler DirectCast(Item, INotifyPropertyChanged).PropertyChanged, AddressOf Item_PropertyChanged
    End Sub

    Private Sub Item_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
        RaiseEvent CollectionItemPropertyChanged(sender, e)
    End Sub

    Public Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
        Return _collection.GetEnumerator()
    End Function            

    Private Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
        Return Me.GetEnumerator()
    End Function
End Class

这是我的实现。在反序列化时,我得到了异常。“无法创建和填充列表类型”。我已经尝试了 ObservableCollection,没有任何例外。

 Dim devices As New ObservableCollectionAdvanced(Of IDevice)
 devices.add(new TorsionArmDevice())
 Dim JsonString as string = JsonConvert.SerializeObject(devices, Newtonsoft.Json.Formatting.Indented, New JsonSerializerSettings With {.TypeNameHandling = TypeNameHandling.All})


 Newtonsoft.Json.JsonConvert.DeserializeObject(of ObservableCollectionAdvanced(Of IDevice))(JsonString, New JsonSerializerSettings With {.TypeNameHandling = TypeNameHandling.All})

演示小提琴在这里

标签: jsonvb.netcollectionsjson.net

解决方案


您的问题是您ObservableCollectionAdvanced(Of T As INotifyPropertyChanged)唯一的实现IEnumerable(Of T)not ICollection(Of T),因此 Json.NET 将其视为只读或不可变集合。它只会拾取该Public Sub Add(Item As T)方法,如果它Implements ICollection(Of T).Add- 你没有,因为类型本身没有实现ICollection(Of T)。每当您尝试使用 Json.NET 反序列化(显然)只读集合时,它不知道如何填充,您将收到错误消息

无法创建和填充列表类型TCollection`1[TItem]

那么你有什么选择呢?

首先,从Json.NET 6.0 第 3 版开始,只要Items As IEnumerable(Of T)只读集合具有采用单个参数的公共构造函数,Json.NET 就支持反序列化。因此,您可以添加一个:

Public Sub New()
    Me.New(Enumerable.Empty(Of T)())
End Sub

Public Sub New(Items As IEnumerable(Of T))
    ' TODO: Decide whether to add the items before or after binding the event
    For Each i in Items
        If i IsNot Nothing Then
            _collection.Add(i)
        End If
    Next
    AddHandler _collection.CollectionChanged, Sub(sender As Object, e As NotifyCollectionChangedEventArgs)
                                                  RaiseEvent CollectionChanged(Me, e)
                                              End Sub
End Sub

现在 Json.NET 可以成功反序列化您的集合。演示小提琴#1在这里

其次,您可以实施ICollection(Of T)

Public Class ObservableCollectionAdvanced(Of T As INotifyPropertyChanged)
    Implements ICollection(Of T) ' changed from IEnumerable(Of T)
    Implements INotifyPropertyChanged
    Implements INotifyCollectionChanged
    Implements INotifyCollectionItemPropertyChanged 

    ' Implement ICollection(Of T)
    
    Public Sub Add(Item As T) Implements ICollection(Of T).Add
        If Item IsNot Nothing Then
            Me.AddHandlerToItem(Item)
            _collection.Add(Item)
        End If
    End Sub

    Public Function Contains(ByVal item As T) As Boolean Implements ICollection(Of T).Contains
        If item IsNot Nothing Then
            Return _collection.COntains(item)
        End If
        Return False
    End Function
    
    Public ReadOnly Property Count() As Integer Implements ICollection(Of T).Count
        Get
            Return _collection.Count
        End Get
    End Property

    Public ReadOnly Property IsReadOnly() As Boolean Implements ICollection(Of T).IsReadOnly
        Get
            Return False
        End Get
    End Property

    Public Sub CopyTo(array As T(), arrayIndex As Integer) Implements ICollection(Of T).CopyTo
        _collection.CopyTo(array, arrayIndex)
    End Sub

    Public Sub Clear() Implements ICollection(Of T).Clear
        Throw New NotImplementedException()
        ' Or, add correct notifications and uncomment
        ' _collection.Clear()
    End Sub

    Public Function Remove(ByVal item As T) As Boolean Implements ICollection(Of T).Remove
        Throw New NotImplementedException()
        ' Or, add correct notifications and uncomment
        ' If item Is Nothing Then
        '   Return False
        ' End If
        ' Return _collection.Remove(item)
    End Function
    
    ' End ICollection(Of T)
    ' Remainder unchanged

演示小提琴#2在这里

笔记:

  • 您的问题不包括定义,INotifyCollectionItemPropertyChanged因此我无法猜测要在Clear()和中提出什么事件Remove()。因此,我在两者中都提出了例外。

  • Json.NET 是用 c# 编写的。在风格上,c# 倾向于通过类型继承和接口实现使用静态编译时绑定,而 VB 从一开始就有后期绑定。这可以解释为什么Json.NET 没有实现Add(item As T)在不存在的情况下的发现。Implements ICollection(Of T).Add

  • 顺便说一句,我注意到您正在使用TypeNameHandling.All. 这可能会使您面临安全风险,例如Newtonsoft Json 中的 TypeNameHandling 警告中所述的安全风险。为了减轻这些风险,请考虑编写一个自定义Serializationbinder来验证传入类型。


推荐阅读