c# - Problemas de indexación de JsonConvert.Deserializer
stack json.net (3)
Dado que este es un comportamiento conocido de Json.NET, como se señala en
esta respuesta
, se puede usar un
JsonConverter
personalizado
al deserializar una pila que empuja los elementos en el orden correcto.
El siguiente conversor universal se puede usar con
Stack<T>
para cualquier
T
:
/// <summary>
/// Converter for any Stack<T> that prevents Json.NET from reversing its order when deserializing.
/// </summary>
public class StackConverter : JsonConverter
{
// Prevent Json.NET from reversing the order of a Stack<T> when deserializing.
// https://github.com/JamesNK/Newtonsoft.Json/issues/971
static Type StackParameterType(Type objectType)
{
while (objectType != null)
{
if (objectType.IsGenericType)
{
var genericType = objectType.GetGenericTypeDefinition();
if (genericType == typeof(Stack<>))
return objectType.GetGenericArguments()[0];
}
objectType = objectType.BaseType;
}
return null;
}
public override bool CanConvert(Type objectType)
{
return StackParameterType(objectType) != null;
}
object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var list = serializer.Deserialize<List<T>>(reader);
var stack = existingValue as Stack<T> ?? (Stack<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
for (int i = list.Count - 1; i >= 0; i--)
stack.Push(list[i]);
return stack;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
try
{
var parameterType = StackParameterType(objectType);
var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
var genericMethod = method.MakeGenericMethod(new[] { parameterType });
return genericMethod.Invoke(this, new object[] { reader, objectType, existingValue, serializer });
}
catch (TargetInvocationException ex)
{
// Wrap the TargetInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Luego agréguelo a
JsonSerializerSettings
para corregir el orden de las pilas al deserializar:
var settings = new JsonSerializerSettings { Converters = new[] { new StackConverter() } };
var jsonString = JsonConvert.SerializeObject(progress, settings);
var temp = JsonConvert.DeserializeObject<Progress>(jsonString, settings);
O marque la propiedad
Stack<T>
directamente con
[JsonConverter(typeof(StackConverter))]
:
class Progress
{
[JsonConverter(typeof(StackConverter))]
public Stack<Item> Items { get; set; }
public Progress()
{
Items = new Stack<Item>();
}
}
Mientras jugaba con la colección Stack en C # me encontré con el siguiente problema. Exactamente no estoy seguro de por qué está sucediendo. Por favor, aclare el motivo y la alternativa a la solución.
Problema
Una clase que tiene Stack como propiedad. Por ejemplo, nombre esa clase como Progreso. T es de tipo de clase Artículo.
Ahora, cuando el usuario haga algún progreso, lo almacenaremos en la pila. Y si el usuario se va en el medio, entonces la próxima vez miraremos el artículo desde la pila, desde esa etapa. El siguiente fragmento de código dará una idea de lo que se está intentando ...
using static System.Console;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace StackCollection
{
class Program
{
static void Main(string[] args)
{
Progress progress = new Progress();
progress.Items.Push(new Item { PlanID = null, PlanName = "Plan A" });
var jsonString = JsonConvert.SerializeObject(progress);
var temp = JsonConvert.DeserializeObject<Progress>(jsonString);
temp.Items.Push(new Item { PlanID = null, PlanName = "Plan B" });
jsonString = JsonConvert.SerializeObject(temp);
temp = JsonConvert.DeserializeObject<Progress>(jsonString);
temp.Items.Push(new Item { PlanID = null, PlanName = "Plan C" });
jsonString = JsonConvert.SerializeObject(temp);
temp = JsonConvert.DeserializeObject<Progress>(jsonString);
WriteLine(temp.Items.Peek().PlanName);
ReadLine();
}
}
class Progress
{
public Stack<Item> Items { get; set; }
public Progress()
{
Items = new Stack<Item>();
}
}
class Item
{
public string PlanID { get; set; }
public string PlanName { get; set; }
}
}
ahora la línea
WriteLine(temp.Items.Peek().PlanName);
debería volver
Plan C
pero esta volviendo
Plan B
Entonces, por qué se cambia el índice, cualquier pista o puntero será útil.
Parece que la pila se está serializando como una Lista. El problema es que esto no conserva el orden correcto al deconstruir la pila (los elementos se empujan en el orden inverso). Aquí hay una solución rápida a este problema:
using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace StackCollection
{
class Program
{
static void Main(string[] args)
{
Progress progress = new Progress();
progress.Items.Push(new Item { PlanID = null, PlanName = "Plan A" });
var jsonString = JsonConvert.SerializeObject(progress);
var temp = JsonConvert.DeserializeObject<Progress>(jsonString);
temp.Items.Push(new Item { PlanID = null, PlanName = "Plan B" });
jsonString = JsonConvert.SerializeObject(temp);
temp = JsonConvert.DeserializeObject<Progress>(jsonString);
temp.Items.Push(new Item { PlanID = null, PlanName = "Plan C" });
jsonString = JsonConvert.SerializeObject(temp);
temp = JsonConvert.DeserializeObject<Progress>(jsonString);
WriteLine(temp.Items.Peek().PlanName);
ReadLine();
}
}
class Progress
{
[JsonIgnore]
public Stack<Item> Items { get; set; }
public List<Item> ItemList { get; set; }
[OnSerializing]
internal void OnSerializing(StreamingContext context)
{
ItemList = Items?.ToList();
}
[OnDeserialized]
internal void OnDeserialized(StreamingContext context)
{
ItemList?.Reverse();
Items = new Stack<Item>(ItemList ?? Enumerable.Empty<Item>());
}
public Progress()
{
Items = new Stack<Item>();
}
}
class Item
{
public string PlanID { get; set; }
public string PlanName { get; set; }
}
}
Si intenta depurarlo, notará que el pedido de artículos se rompe después de la deserialización de la
Stack
.
La misma pregunta se hizo en el rastreador de problemas JSON.NET GitHub hace un mes.
La respuesta de JamesNK:
Me temo que esto es una limitación de una pila. Los resultados devueltos cuando se serializa y el orden opuesto para cuando se deserializa.