【问题标题】:JsonConvert.Deserializer indexing issuesJsonConvert.Deserializer 索引问题
【发布时间】: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);索引洗牌。

标签: c# stack json.net


【解决方案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 方法调用,导致与预期结果相反。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-05
      • 2011-06-18
      • 2013-07-03
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多