2016-09-02 5 views
1

JSine.NETを使用してIDeserializationCallbackを実装しようとしています。私はオブジェクトをデシリアライズしていますが、IDeserializationCallbackを実装する逆シリアル化されたすべてのオブジェクトのリストを生成したいと思います。これを行うにはどうすればよいでしょうか?これを容易にするためにJSON.NETに適切な拡張ポイントがありますか?私は以下の(一見)働く解決策を持っていますが、それはかなり醜いので、これを行うにはより良い方法が必要であると確信しています。どんな助けでも感謝しています!JSON.NETでは、すべての逆シリアル化されたオブジェクトへの参照を取得する方法は?

private static JsonSerializer serializer = new JsonSerializer(); 

    static cctor() 
    { 
     serializer.Converters.Add(new DeserializationCallbackConverter()); 
    } 

    public static T Deserialize<T>(byte[] data) 
    { 
     using (var reader = new JsonTextReader(new StreamReader(new MemoryStream(data)))) 
     using (DeserializationCallbackConverter.NewDeserializationCallbackBlock(reader)) 
      return serializer.Deserialize<T>(reader); 
    } 

    private class DeserializationCallbackConverter : JsonConverter 
    { 
     [ThreadStatic] 
     private static ScopedConverter currentConverter; 

     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
     { 
      throw new NotImplementedException(); 
     } 

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
     { 
      return currentConverter.ReadJson(reader, objectType, serializer); 
     } 

     public override bool CanConvert(Type objectType) 
     { 
      return currentConverter == null ? false : currentConverter.CanConvert(); 
     } 

     public override bool CanWrite 
     { 
      get { return false; } 
     } 

     public static IDisposable NewDeserializationCallbackBlock(JsonReader reader) 
     { 
      return new ScopedConverter(reader); 
     } 

     private class ScopedConverter : IDisposable 
     { 
      private JsonReader jsonReader; 
      private string currentPath; 
      private List<IDeserializationCallback> callbackObjects; 

      public ScopedConverter(JsonReader reader) 
      { 
       jsonReader = reader; 
       callbackObjects = new List<IDeserializationCallback>(); 
       currentConverter = this; 
      } 

      public object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) 
      { 
       var lastPath = currentPath; 
       currentPath = reader.Path; 
       var obj = serializer.Deserialize(reader, objectType); 
       currentPath = lastPath; 

       var dc = obj as IDeserializationCallback; 
       if (dc != null && callbackObjects != null) 
        callbackObjects.Add(dc); 
       return obj; 
      } 

      public bool CanConvert() 
      { 
       return jsonReader.Path != currentPath; 
      } 

      public void Dispose() 
      { 
       currentConverter = null; 
       foreach (var obj in callbackObjects) 
        obj.OnDeserialization(null); 
      } 
     } 
    } 
+1

私は、あなたがグラフをトラバースしてリストを記入して、グラフの各ノードが(リスト、そう? – Renan

+0

リフレクションベースのアプローチを試しましたか?私の経験ではそれほど遅くはありません。 – Casey

+0

Renan:はい、それは間違いです – redec

答えて

3

あなたは参照型オブジェクトの作成を追跡し、余分な、人工OnDeserializedコールバックを追加しcustom contract resolverを作成することができます。その後、

public interface IObjectCreationTracker 
{ 
    void Add(object obj); 

    ICollection<object> CreatedObjects { get; } 
} 

public class ReferenceObjectCreationTracker : IObjectCreationTracker 
{ 
    public ReferenceObjectCreationTracker() 
    { 
     this.CreatedObjects = new HashSet<object>(); 
    } 

    public void Add(object obj) 
    { 
     if (obj == null) 
      return; 
     var type = obj.GetType(); 
     if (type.IsValueType || type == typeof(string)) 
      return; 
     CreatedObjects.Add(obj); 
    } 

    public ICollection<object> CreatedObjects { get; private set; } 
} 

public class ObjectCreationTrackerContractResolver : DefaultContractResolver 
{ 
    readonly SerializationCallback callback = (o, context) => 
     { 
      var tracker = context.Context as IObjectCreationTracker; 
      if (tracker != null) 
       tracker.Add(o); 
     }; 

    protected override JsonContract CreateContract(Type objectType) 
    { 
     var contract = base.CreateContract(objectType); 
     contract.OnDeserializedCallbacks.Add(callback); 
     return contract; 
    } 
} 

そして、次のようにそれを使用する:これが唯一の非文字列参照型のインスタンスを返すこと

public static class JsonExtensions 
{ 
    public static T DeserializeWithTracking<T>(string json, out ICollection<object> objects) 
    { 
     var tracker = new ReferenceObjectCreationTracker(); 
     var settings = new JsonSerializerSettings 
     { 
      ContractResolver = new ObjectCreationTrackerContractResolver(), 
      Context = new StreamingContext(StreamingContextStates.All, tracker), 
      // Add other settings as required. 
      TypeNameHandling = TypeNameHandling.Auto, 
     }; 
     var obj = (T)JsonConvert.DeserializeObject<T>(json, settings); 
     objects = tracker.CreatedObjects; 
     return obj; 
    } 
} 

注ここでは一例です。最終的にプロパティーセッターを介して大きなオブジェクトに埋め込まれる値タイプと、boxed referenceとしてオブジェクトグラフに保持される値タイプとを区別する明白な方法がないので、値タイプのインスタンスを返す方が問題があります。 this questionに示すように、ボックス化された値の型が最終的に大きなオブジェクトに埋め込まれた場合、そのオブジェクトへの直接参照を保持する方法はありません。

また、トラッカーをコールバックに渡すためにStreamingContext.Contextを使用することにも注意してください。

最高のパフォーマンスを得るには、cache the contract resolverが必要な場合があります。

Json.NETでIDeserializationCallbackを実装する方法の更新の質問への答えに更新

、上記参照型のために働く必要があります。このインタフェースを実装する値型の場合は、あなたの可能性:

  1. IDeserializationCallbackがサポートされていないことを示す例外をスローOnDeserializedコールバックですぐに方法はなく、シリアル化が完了するまでそれを延期、または

  2. を呼び出し構造体のために。

+0

恐ろしい、ありがとう!それは道をきれいにする必要があるように見えます。私は次の週までそれを再びプレイすることはできないだろうが、それは素晴らしいようだ! – redec

+0

これは素晴らしい作品です。とてもきれいです!ありがとう! – redec

関連する問題