I came across this post, and thought I would share my solution for dealing with arrays, as I couldn't find a fully worked up example anywhere. In order for this sample to work, the target array must implement IEnumerable and IList, and the target array objects must implement IEquatable(Of JToken). The implementation of IEquatable(Of JToken) is where you put your logic to determine whether the deserializer should act on an existing item or create a new one. The example also removes any items from the target that are not in the json. I haven't added a disposal check on the removed items, but trivial to do.
The new PopulateObject Call:
Private Sub PopulateObject(value As String, target As Object)
'set up default converter
Dim converter As ReconcileEnumerationConverter = New ReconcileEnumerationConverter
JsonConvert.DefaultSettings = Function()
Return New JsonSerializerSettings With {.Converters = {converter}}
End Function
'for some reason populate object won't call converter on root
'so force the issue if our root is an array
If converter.CanConvert(target.GetType) Then
Dim array As JArray = JArray.Parse(value)
converter.ReadJson(array.CreateReader, target.GetType, target, Nothing)
Else
JsonConvert.PopulateObject(value, target)
End If
End Sub
The converter:
Public Class ReconcileEnumerationConverter : Inherits JsonConverter
Public Overrides Function CanConvert(objectType As Type) As Boolean
'check to ensure our target type has the necessary interfaces
Return GetType(IList).IsAssignableFrom(objectType) AndAlso GetType(IEnumerable(Of IEquatable(Of JToken))).IsAssignableFrom(objectType)
End Function
Public Overrides ReadOnly Property CanWrite As Boolean
Get
Return False
End Get
End Property
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim array As JArray = JArray.ReadFrom(reader)
'cast the existing items
Dim existingItems As IEnumerable(Of IEquatable(Of JToken)) = CType(existingValue, IEnumerable(Of IEquatable(Of JToken)))
'copy the existing items for reconcilliation (removal) purposes
Dim unvisitedItems As IList = existingItems.ToList 'start with full list, and remove as we go
'iterate each item in the json array
For Each j As JToken In array.Children
'look for existing
Dim existingitem As Object = existingItems.FirstOrDefault(Function(x) x.Equals(j))
If existingitem IsNot Nothing Then 'found an existing item, update it
JsonSerializer.CreateDefault.Populate(j.CreateReader, existingitem)
unvisitedItems.Remove(existingitem)
Else 'create a new one
Dim newItem As Object = JsonSerializer.CreateDefault.Deserialize(j.CreateReader)
CType(existingItems, IList).Add(newItem)
End If
Next
'remove any items not visited
For Each item As Object In unvisitedItems
CType(existingItems, IList).Remove(item)
Next
Return existingItems
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Throw New NotImplementedException
End Sub
End Class
And a sample implementation of IEquatable(of JToken), keyed on an integer 'Id' field:
Public Shadows Function Equals(other As JToken) As Boolean Implements IEquatable(Of JToken).Equals
Dim idProperty As JProperty = other.Children.FirstOrDefault(Function(x) CType(x, JProperty).Name = "Id")
If idProperty IsNot Nothing AndAlso CType(idProperty.Value, JValue).Value = Id Then
Return True
Else
Return False
End If
End Function
UpdateModel
method? You provide an object, and the framework sets whatever values it finds in the input string? – Bixby