c# - serialize - Superponer datos de cadena JSON a instancia de objeto existente
newtonsoft json serialize vb net (4)
Después de hurgar en el código fuente (mucho más fácil que leer la documentación, ¿eh?) JSON.NET
hace exactamente lo que yo quiero:
JsonConvert.PopulateObject(string, object)
Quiero deserializar una cadena JSON que no contenga necesariamente datos para cada miembro, por ejemplo:
public class MyStructure
{
public string Field1;
public string Field2;
}
Supongamos que tengo una instancia:
Field1: "data1"
Field2: "data2"
y deserializo una cadena:
{ "Field1": "newdata1" }
El resultado debe ser
Field1: "newdata1"
Field2: "data2"
Framework JavascriptSerializer
y JSON.NET
devuelven nuevos objetos en sus métodos de deserialización, por lo que la única forma en que puedo pensar en hacer esto directamente sería comparar el objeto deserializado con el existente usando el reflejo que parece una sobrecarga innecesaria. Idealmente, algún software tendría un método en el que pasara una instancia existente de un objeto, y solo los miembros que existían en la cadena se actualizarían. El punto aquí es que me gustaría poder pasar solo los datos que han cambiado al servidor y actualizar un objeto existente.
¿Es posible usar cualquiera de estas herramientas, y si no, alguna sugerencia sobre cómo abordar el problema?
Me encontré con esta publicación y pensé que compartiría mi solución para tratar con arreglos, ya que no pude encontrar un ejemplo completamente trabajado en ninguna parte. Para que esta muestra funcione, la matriz de destino debe implementar IEnumerable e IList, y los objetos de matriz de destino deben implementar IEquatable (Of JToken). La implementación de IEtable (De JToken) es donde pones tu lógica para determinar si el deserializador debería actuar sobre un elemento existente o crear uno nuevo. El ejemplo también elimina cualquier elemento del objetivo que no esté en el json. No he agregado un control de eliminación de los artículos eliminados, pero es trivial de hacer.
La nueva llamada de 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
El convertidor:
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
Y una implementación de ejemplo de IEquatable (de JToken), codificada en un campo entero ''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
Realize - JsonConvert.PopulateObject (cadena, objeto) NO funcionará para colecciones.
Incluso con PreserveReferencesHandling = Objects / Array / All y un IReferenceResolver. JSON.NET no actualizará los elementos en las colecciones. En cambio, duplicará sus elementos de colección.
JSON.NET solo usa sus identificadores de referencia de conservación ("ref") para reutilizar las referencias leídas dentro del JSON serializado. JSON.NET no reutilizará instancias en gráficos de objetos anidados existentes. Intentamos agregar una propiedad de ID a todos nuestros objetos, pero JSON.NET IReferenceResolver no proporciona las facilidades para encontrar y unir las referencias existentes dentro de las colecciones.
Our solution will be to deserialize JSON into a new object instance and map properties across the 2 instances using either Fasterflect or AutoMapper.
Tenga en cuenta que JsonConvert.PopulateObject
JsonConvert.PopulateObject(json, item, new JsonSerializerSettings());
Simplemente llama a jsonSerializer.Populate ( ver aquí )
string json = "{ ''someJson'':true }";
var jsonSerializer = new JsonSerializer();
jsonSerializer.Populate(new StringReader(json), item);
Por lo tanto, si necesita convertir repetidamente mil objetos, puede obtener un mejor rendimiento en esta ruta, de modo que un nuevo JsonSerializer no se convierta en instancia cada vez.