2

I try to deserialize a custom collection using 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

This is my implementation. While deserializing I get en Exception. "Cannot create and populate list type". I've tried a ObservableCollection instead, without any exceptions.

 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})

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
hilrol
  • 57
  • 6

1 Answers1

3

Your problem is that your ObservableCollectionAdvanced(Of T As INotifyPropertyChanged) only implements IEnumerable(Of T) not ICollection(Of T), and thus Json.NET sees it as a read-only or immutable collection. It will only pick up on the Public Sub Add(Item As T) method if it Implements ICollection(Of T).Add - which yours does not, as the type itself does not implement ICollection(Of T). Whenever you attempt to deserialize an (apparently) read-only collection with Json.NET that it does not otherwise know how to populate, you will get the error message

Cannot create and populate list type TCollection`1[TItem]

So what are your options?

Firstly, As of Json.NET 6.0 Release 3 Json.NET supports deserialization of read-only collections as long as they have a public constructor taking a single Items As IEnumerable(Of T) argument. So, you could add one:

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

Now Json.NET can deserialize your collection successfully. Demo fiddle #1 here.

Secondly, you could implement 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

Demo fiddle #2 here.

Notes:

  • Your question does not include a definition for INotifyCollectionItemPropertyChanged so I could not guess what events to raise in Clear() and Remove(). Thus I raised exceptions in both.

  • Json.NET is written in c#. Stylistically, c# tends to use static compile-time binding via type inheritance and interface implementation while VB had late binding from the start. This may explain why discovery of the Add(item As T) in the absence of Implements ICollection(Of T).Add was not implemented by Json.NET.

  • As an aside, I notice you are serializing using TypeNameHandling.All. This may leave you open to security risks such as those described in TypeNameHandling caution in Newtonsoft Json. To mitigate those risks, consider writing a custom Serializationbinder to validate incoming types.

dbc
  • 104,963
  • 20
  • 228
  • 340