2016-08-25 5 views
1

C#でStackコレクションを再生しているときに、次の問題が発生しました。正確に私はなぜそれが起こっているのか分かりません。その理由とソリューションの代替案についていくつか明記してください。JsonConvert.Deserializerインデックスの問題

問題 -

クラスプロパティとしてスタックを持ちます。たとえば、そのクラスの名前をProgressとします。 Tはクラス型のItemです。

ユーザーが進捗するたびにスタックに格納されます。そして、ユーザーがその間を去ると、次回はその段階からスタックからアイテムを覗き見します。コードスニペットの下に試されているかのアイデアを与えるだろう...

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

インデックスが変更されている理由ので、任意の手掛かりやポインタが参考になります。

+0

JSON文字列が正しい順序を持っていますが、最後の文字列の後にあります。 temp = JsonConvert.DeserializeObject (jsonString); インデックスシャッフル。我々は foreachの(_te​​mpでのvarアイテム) { のようなforループを実行したときにスタック内のポインタのための –

答えて

1

スタックがリストとしてシリアル化されているようです。問題は、スタックを分解するときに正しい順序を保持しないことです(アイテムは実際には逆の順序でプッシュされます)。ここでは、この問題への迅速な回避策があります:

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; } 
    } 
} 
+0

はい、項目が逆の順序でプッシュされることに同意しました。私は解決策を試してみましょう。 –

+0

おかげさまで@ greenshadeそれは魅力的なように働いた...クール –

0

デバッグしようとすると、Stackデシリアライズ後にアイテムの順序が壊れていることがわかります。

同じ質問has been asked on JSON.NET GitHub issue tracker 1ヶ月前。

JamesNKからの回答

私は、これはスタックのlimitionで怖いです。シリアル化されたときの結果が返され、逆シリアル化されたときの結果の逆順。

+0

おかげで、1つの以上の観測、今までにアイテムが押されたとき、それがインデックス0 にプッシュされるので、それはLIFO に飛び出る開始します} JSONConvertを使用して逆シリアル化するとき、私はそれがキューFIFOとして逆直列化しようとしていると思います。 野生の推測は不明です。 –

1

これはJson.NETの既知の動作ですのでthis answerで述べたように、正しい順序で上の項目をプッシュスタックをデシリアライズするとき、custom JsonConverterを使用することができます。

次のユニバーサルコンバータは、任意のTためStack<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); 

またはStack<T>プロパティをマーク[JsonConverter(typeof(StackConverter))]と直接:

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

    public Progress() 
    { 
     Items = new Stack<Item>(); 
    } 
} 
関連する問題