将数据从JSON字符串覆盖到现有对象实例

时间:2011-03-01 16:10:13

标签: c# .net json serialization

我想反序列化一个JSON字符串,该字符串不一定包含每个成员的数据,例如:

public class MyStructure
{
   public string Field1;
   public string Field2;
}

假设我有一个实例:

Field1: "data1"
Field2: "data2"

我反序列化一个字符串:

{ "Field1": "newdata1" }

结果应为

Field1: "newdata1"
Field2: "data2"

框架JavascriptSerializerJSON.NET都在其反序列化方法中返回新对象,因此我能想到直接执行此操作的唯一方法是将反序列化对象与使用反射的现有对象进行比较喜欢很多不必要的开销。理想情况下,某些软件会有一个方法,我传递一个现有的对象实例,只有那些存在于字符串中的成员才会得到更新。这里的要点是,我希望能够只传递已更改为服务器的数据,并更新现有对象。

这是否可以使用这些工具之一,如果没有,是否有任何关于如何解决问题的建议?

4 个答案:

答案 0 :(得分:81)

在浏览源代码之后(比阅读文档容易得多,嗯?)JSON.NET完全符合我的要求:

JsonConvert.PopulateObject(string, object)

请参阅Json.NET: Populate an Object

答案 1 :(得分:9)

Realize - JsonConvert.PopulateObject(string,object)不适用于集合。

即使使用PreserveReferencesHandling = Objects / Arrays / All和IReferenceResolver。 JSON.NET不会更新集合中的项目。相反,它会复制您的收藏品。

JSON.NET仅使用其(“ref”)保留引用标识符来重用在序列化JSON中读取的引用。 JSON.NET不会在现有嵌套对象图中重用实例。我们尝试通过向所有对象添加ID属性,但JSON.NET IReferenceResolver不提供查找&匹配集合中的现有引用。

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

答案 2 :(得分:6)

注意JsonConvert.PopulateObject

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

只需调用jsonSerializer.Populate(see here

        string json = "{ 'someJson':true }";

        var jsonSerializer = new JsonSerializer();

        jsonSerializer.Populate(new StringReader(json), item);

因此,如果你需要重复转换一千个对象,你可以在这条路线上获得更好的性能,这样每次都不会实例化新的JsonSerializer。

答案 3 :(得分:2)

我遇到过这篇文章,并认为我会分享我处理数组的解决方案,因为我无法在任何地方找到一个完整的例子。为了使此示例有效,目标数组必须实现IEnumerable和IList,目标数组对象必须实现IEquatable(Of JToken)。 IEquatable(Of JToken)的实现是您放置逻辑以确定反序列化器是应该对现有项目进行操作还是创建新项目的地方。该示例还会从目标中删除不在json中的任何项目。我还没有对删除的项目进行处理检查,但是很容易做到。

新的PopulateObject调用:

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

转换器:

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

IEquatable(JToken)的示例实现,键入一个整数' Id'字段:

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