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

Jam*_*rgy 45 .net c# serialization json

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

public class MyStructure
{
   public string Field1;
   public string Field2;
}
Run Code Online (Sandbox Code Playgroud)

假设我有一个实例:

Field1: "data1"
Field2: "data2"
Run Code Online (Sandbox Code Playgroud)

我反序列化一个字符串:

{ "Field1": "newdata1" }

结果应该是

Field1: "newdata1"
Field2: "data2"
Run Code Online (Sandbox Code Playgroud)

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

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

Jam*_*rgy 83

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

JsonConvert.PopulateObject(string, object)

请参阅Json.NET:填充对象

  • 对我来说,“new JsonSerializerSettings{NullValueHandling = NullValueHandling.Ignore}”作为第三个参数确实很有帮助。 (4认同)

小智 9

实现 - JsonConvert.PopulateObject(字符串,对象)不适用于集合.

即使使用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.


Nic*_*sen 7

注意JsonConvert.PopulateObject

JsonConvert.PopulateObject(json, item, new JsonSerializerSettings());
Run Code Online (Sandbox Code Playgroud)

只需调用jsonSerializer.Populate(参见此处)

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

        var jsonSerializer = new JsonSerializer();

        jsonSerializer.Populate(new StringReader(json), item);
Run Code Online (Sandbox Code Playgroud)

因此,如果您需要重复转换一千个对象,则可以在此路径中获得更好的性能,以便每次都不会实例化新的JsonSerializer.


2st*_*oke 5

我遇到了这篇文章,并认为我会分享我处理数组的解决方案,因为我在任何地方都找不到完整的示例。为了使此示例工作,目标数组必须实现 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
Run Code Online (Sandbox Code Playgroud)

转换器:

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
Run Code Online (Sandbox Code Playgroud)

以及 IEquatable(of 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
Run Code Online (Sandbox Code Playgroud)