JsonConvert.Deserializer 索引问题

Posted

技术标签:

【中文标题】JsonConvert.Deserializer 索引问题【英文标题】:JsonConvert.Deserializer indexing issues 【发布时间】:2017-01-01 09:42:15 【问题描述】:

在 C# 中玩 Stack 集合时,我遇到了以下问题。确切地说,我不确定为什么会这样。请说明原因和解决方案的替代方案。

问题 -

具有 Stack 作为属性的类。例如,将该类命名为 Progress。 T 是类类型 Item。

现在,每当用户取得任何进展时,我们都会将其存储在堆栈中。如果用户在两者之间离开,那么下次我们将从堆栈中窥视该项目,以便从那个阶段开始。下面的代码 sn-p 将给出正在尝试什么的想法......

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; 
    

现在该行 -

WriteLine(temp.Items.Peek().PlanName);

应该返回

C计划

但它正在返回

B计划

那么,为什么要更改索引,任何线索或指针都会有所帮助。

【问题讨论】:

也很少有调查显示,JSON 字符串的顺序正确,但在最后一个 temp = JsonConvert.DeserializeObject(jsonString);索引洗牌。 【参考方案1】:

由于这是 Json.NET 的已知行为,如 this answer 所述,在反序列化以正确顺序推送项目的堆栈时可以使用 custom JsonConverter

以下通用转换器可与Stack&lt;T&gt; 一起用于任何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();
    

然后将其添加到JsonSerializerSettings 以更正反序列化时的堆栈顺序:

var settings = new JsonSerializerSettings  Converters = new[]  new StackConverter()  ;
var jsonString = JsonConvert.SerializeObject(progress, settings);
var temp = JsonConvert.DeserializeObject<Progress>(jsonString, settings);

或者直接用[JsonConverter(typeof(StackConverter))]标记Stack&lt;T&gt;属性:

class Progress

    [JsonConverter(typeof(StackConverter))]
    public Stack<Item> Items  get; set; 

    public Progress()
    
        Items = new Stack<Item>();
    

【讨论】:

【参考方案2】:

似乎堆栈正在被序列化为列表。问题是这在解构堆栈时不能保持正确的顺序(项目实际上是按相反的顺序推送的)。以下是此问题的快速解决方法:

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; 
    

【讨论】:

是的,同意项目以相反的顺序推送。让我试试解决方案。【参考方案3】:

如果您尝试调试它,那么您会注意到在Stack 反序列化之后项目顺序被破坏。

一个月前 JSON.NET GitHub 问题跟踪器上的相同问题 has been asked。

JamesNK 的回答:

恐怕这是堆栈的限制。序列化时返回的结果与反序列化时的顺序相反。

【讨论】:

感谢指针,还有 1 次观察,在 Stack 中,每当一个项目被推送时,它都会被推送到索引 0。所以当我们像 foreach(var item in _temp) 一样运行 for 循环时,它开始弹出在 LIFO 但是当我们使用 JSONConvert 反序列化时,我认为它会尝试反序列化为 Queue - FIFO。不确定只是一个疯狂的猜测。【参考方案4】:

在 Visual Studio 2019 中,此 C# 有效:

List<string> ls = null;
Stack<string> ss = null;
if (json != null)

    ls = JsonConvert.DeserializeObject<List<string>>(json);
    ss = new Stack<string>(ls);

(这是对来自here 的答案的编辑,它最初在列表中有一个错误的 Reverse 方法调用,导致与预期结果相反。)

【讨论】:

以上是关于JsonConvert.Deserializer 索引问题的主要内容,如果未能解决你的问题,请参考以下文章