Наложение данных из строки JSON на существующий экземпляр объекта - PullRequest
44 голосов
/ 01 марта 2011

Я хочу десериализовать строку JSON, которая не обязательно содержит данные для каждого члена, например:

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

Предположим, у меня есть экземпляр:

Field1: "data1"
Field2: "data2"

и я десериализирую строку:

{ "Field1": "newdata1" }

Результат должен быть

Field1: "newdata1"
Field2: "data2"

Framework JavascriptSerializer и JSON.NET оба возвращают новые объекты в своих методах десериализации, поэтому я могу думать только о том, чтобы сделать это напрямую, - сравнить десериализованный объект с существующим, используя отражение, которое кажется большим ненужные накладные расходы. В идеале, в некоторых программах должен быть метод, в котором я передаю существующий экземпляр объекта, и обновляются только те члены, которые существовали в строке. Дело в том, что я хотел бы иметь возможность передавать на сервер только те данные, которые были изменены, и обновлять существующий объект.

Возможно ли это с помощью какого-либо из этих инструментов, а если нет, каких-либо предложений о том, как решить проблему?

Ответы [ 4 ]

82 голосов
/ 01 марта 2011

После просмотра исходного кода (намного проще, чем чтение документации, а?) JSON.NET делает именно то, что я хочу:

JsonConvert.PopulateObject(string, object)

См. Json.NET: заполнить объект

9 голосов
/ 15 августа 2011

Реализация - 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.

7 голосов
/ 17 октября 2015

Обратите внимание, что JsonConvert.PopulateObject

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

Просто вызывает jsonSerializer.Populate ( см. Здесь )

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

        var jsonSerializer = new JsonSerializer();

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

Так что если вам нужно повторно преобразовать тысячуобъекты, вы можете получить лучшую производительность этого маршрута, так что новый JsonSerializer не создается каждый раз.

3 голосов
/ 21 октября 2017

Я наткнулся на этот пост и подумал, что поделюсь своим решением для работы с массивами, поскольку нигде не смог найти полностью проработанный пример.Чтобы этот пример работал, целевой массив должен реализовывать 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...