Overlay data from JSON string to existing object instance

后端 未结 4 1487
余生分开走
余生分开走 2020-12-03 02:32

I want to deserialize a JSON string which does not necessarily contain data for every member, e.g:

public class MyStructure
{
   public string Field1;
   pub         


        
相关标签:
4条回答
  • 2020-12-03 02:54

    After poking around the source code (so much easier than reading the documentation, eh?) JSON.NET does exactly what I want already:

    JsonConvert.PopulateObject(string, object)

    See Json.NET: Populate an Object

    0 讨论(0)
  • 2020-12-03 03:02

    Note that JsonConvert.PopulateObject

    JsonConvert.PopulateObject(json, item, new JsonSerializerSettings());
    

    Simply calls jsonSerializer.Populate (see here)

            string json = "{ 'someJson':true }";
    
            var jsonSerializer = new JsonSerializer();
    
            jsonSerializer.Populate(new StringReader(json), item);
    

    So if you need to repeatedly convert a thousand objects, you may get better performance this route, so that a new JsonSerializer is not instantiated every time.

    0 讨论(0)
  • 2020-12-03 03:07

    Realize - JsonConvert.PopulateObject(string,object) will NOT work for collections.

    Even with PreserveReferencesHandling = Objects/Arrays/All and an IReferenceResolver. JSON.NET will not update items in collections. Instead, it will duplicate your collection items.

    JSON.NET only uses its ("ref") Preserve Reference identifiers to reuse references read within the serialized JSON. JSON.NET will not reuse instances in existing nested object graphs. We attempted by adding an ID property to all our objects, but JSON.NET IReferenceResolver does not provide the facilities to find & match existing references within collections.

    Our solution will be to deserialize JSON into a new object instance and map properties across the 2 instances using either Fasterflect or AutoMapper.

    0 讨论(0)
  • 2020-12-03 03:15

    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
    
    0 讨论(0)
提交回复
热议问题