Ответ 1
После просмотра исходного кода (намного проще, чем чтение документации, а?) JSON.NET
делает именно то, что я уже хочу:
JsonConvert.PopulateObject(string, object)
Я хочу десериализовать строку JSON, которая не обязательно содержит данные для каждого элемента, например:
public class MyStructure
{
public string Field1;
public string Field2;
}
Предположим, что у меня есть экземпляр:
Field1: "data1"
Field2: "data2"
и я десериализую строку:
{ "Field1": "newdata1" }
Результат должен быть
Field1: "newdata1"
Field2: "data2"
Framework JavascriptSerializer
и JSON.NET
оба возвращают новые объекты в свои методы десериализации, поэтому единственный способ, которым я могу думать об этом непосредственно, - это сравнить десериализованный объект с существующим, используя отражение, которое кажется большим ненужные накладные расходы. В идеале, какое-то программное обеспечение будет иметь метод, в котором я передал существующий экземпляр объекта, и только те члены, которые существовали в строке, будут обновляться. Дело в том, что я хотел бы иметь возможность передавать только данные, которые были изменены на сервер, и обновить существующий объект.
Возможно ли это использовать любой из этих инструментов, а если нет, то какие-либо предложения о том, как подойти к проблеме?
После просмотра исходного кода (намного проще, чем чтение документации, а?) JSON.NET
делает именно то, что я уже хочу:
JsonConvert.PopulateObject(string, object)
Реализовать - 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.
Обратите внимание, что JsonConvert.PopulateObject
JsonConvert.PopulateObject(json, item, new JsonSerializerSettings());
Там также перегрузка, позволяющая отправлять в настройках (см.):
JsonConvert.PopulateObject(string value, object target, JsonSerializerSettings settings)
Просто вызывает jsonSerializer.Populate(см. Здесь)
string json = "{ 'someJson':true }";
var jsonSerializer = new JsonSerializer();
jsonSerializer.Populate(new StringReader(json), item);
Поэтому, если вам нужно повторно преобразовать тысячу объектов, вы можете получить лучшую производительность на этом маршруте, чтобы новый JsonSerializer не создавался каждый раз.
Я столкнулся с этим сообщением и подумал, что поделюсь своим решением для работы с массивами, поскольку я не мог найти полностью обработанный пример где угодно. Чтобы этот образец работал, целевой массив должен реализовывать 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