2017-12-23 27 views
1

StockOverflowExceptionは、JObject.FromObject(value)の呼び出しでWriteJsonメソッド内の以下のコードによって発生しています。それはWriteJsonメソッドを呼び出します。JObject.FromValueを使用する場合のStackOverflow

再帰的なスタックオーバーフローの問題を避けるためにAggregateEventConverterを書き直すにはどうすればよいですか?

イベントが永続的にストリームに書き込まれ、他のコーダーが古いイベントクラスの名前をリファクタリングしてから数年後に逆シリアル化できる必要があるため、コードはこのように記述されます。たとえば、class AppleFellOffTreeclass AppleFellOffTree_v001に変更する可能性がありますが、古いイベントを逆シリアル化する目的でアセンブリに保持します。 AggregateEventTypeId属性は、イベントクラスをシフト/リファクタリングしながらコーダーがこれらの属性をそのまま維持する限り、jsonを正しいクラスにデシリアライズするのに役立ちます。

Newtonsoft独自のTypeNameHandling機能は、名前がリファクタリングされたクラスを正確に逆シリアル化するのに役立ちません。

class Program { 
    static void Main(string[] args) { 
     var e1 = new AppleFellOffTree { 
      At = TimeStamp.Now, 
      Id = Guid.NewGuid(), 
      VersionNumber = 21, 
     }; 
     var json = JsonConvert.SerializeObject(e1); 
     var e2 = JsonConvert.DeserializeObject<AggregateEvent>(json); 
    } 
} 

[Serializable] 
[JsonConverter(typeof(AggregateEventConverter))] 
public class AggregateEvent { 
    public string EventName => GetType().Name; 
    public Guid Id; 
    public int VersionNumber; 
    public TimeStamp At; 
} 

[AggregateEventTypeId("{44B9114E-085F-4D19-A142-0AC76573602B}")] 
public class AppleFellOffTree : AggregateEvent { 
} 

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 
public class AggregateEventTypeIdAttribute : Attribute { 
    public readonly Guid Id; 
    public AggregateEventTypeIdAttribute(string guid) { 
     Id = Guid.Parse(guid); 
    } 
} 

public class AggregateEventConverter : JsonConverter { 

    public override bool CanRead => true; 
    public override bool CanWrite => true; 
    public override bool CanConvert(Type objectType) => objectType == typeof(AggregateEvent) || objectType.IsSubclassOf(typeof(AggregateEvent)); 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { 
     if (null == value) { 
      writer.WriteValue(value); 
      return; 
     } 
     var jObject = JObject.FromObject(value); 
     jObject.Add("$typeId", EventTypes.GetEventTypeId(value.GetType())); 
     jObject.WriteTo(writer); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { 
     var jToken = JToken.ReadFrom(reader); 
     if (jToken.Type != JTokenType.Object) { 
      throw new NotImplementedException(); 
     } else { 
      var jObject = (JObject)jToken; 
      var eventTypeId = (Guid)jObject.GetValue("$typeId"); 
      var eventType = EventTypes.GetEventType(eventTypeId); 
      return JsonConvert.DeserializeObject(jToken.ToString(), eventType); 
     } 
    } 
} 

internal static class EventTypes { 

    static readonly Dictionary<Guid, Type> Data = new Dictionary<Guid, Type>(); 

    static EventTypes() { 

     var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 

     var eventTypes = assemblies.SelectMany(a => a.GetTypes() 
      .Where(t => t.IsSubclassOf(typeof(AggregateEvent))) 
      .Where(t => !t.IsAbstract)) 
      .ToArray(); 

     // t is for eventType 
     foreach (var t in eventTypes) { 
      var id = GetEventTypeId(t); 
      if (Data.ContainsKey(id)) 
       throw new Exception($"Duplicate {nameof(AggregateEventTypeIdAttribute)} value found on types '{t.FullName}' and '{Data[id].FullName}'"); 
      Data[id] = t; 
     } 
    } 

    public static Type GetEventType(Guid eventTypeId) { 
     return Data[eventTypeId]; 
    } 

    public static Guid GetEventTypeId(Type type) { 

     // a is for attribute 
     var a = type.GetCustomAttributes(typeof(AggregateEventTypeIdAttribute), false) 
      .Cast<AggregateEventTypeIdAttribute>() 
      .FirstOrDefault(); 

     if (null == a) 
      throw new Exception($"{nameof(AggregateEventTypeIdAttribute)} attribute does not exist on type {type.FullName}."); 

     if (Guid.Empty == a.Id) 
      throw new Exception($"{nameof(AggregateEventTypeIdAttribute)} attribute was not set to a proper value on type {type.FullName}"); 

     return a.Id; 
    } 

    public static IEnumerable<KeyValuePair<Guid, Type>> GetAll => Data; 
} 
+2

(https://stackoverflow.com/q [ '[JsonConverter]'?を介してベースタイプに適用される方法JsonConvert.DeserializeObjectを呼び出し、JsonConverterを無効にする]の重複のように見えます/ 45547123/3744182)。 '[JsonConvert()]'](https://stackoverflow.com/q/29719509/3744182)を使用すると[JSON.NetからStackOverflowExceptionがスローされます。 – dbc

+0

リンクされたソリューションがうまくいかない場合は、どこに問題があるか教えてください。質問を再開します。 – dbc

+0

@dbcこれらのリンクは助けました。ありがとうございます。質問をもう一度開いて、私が使用したコードを投稿してください。 – bboyle1234

答えて

1

コメントに記載されているリンクを読み終えたら、私はこの解決策を考え出しました。

https://gist.github.com/bboyle1234/46291a8c8d42f797405057844eeb4bda

using Newtonsoft.Json; 
using Newtonsoft.Json.Linq; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using System.Runtime.Serialization; 

namespace Migratable { 
    [JsonConverter(typeof(MigratableConverter))] 
    public interface IMigratable { 
    } 

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 
    public class MigratableAttribute : Attribute { 
     public readonly Guid Id; 
     public MigratableAttribute(string guid) { 
      Id = Guid.Parse(guid); 
     } 
    } 

    public class MigratableConverter : JsonConverter { 

     [ThreadStatic] 
     static bool writeDisabled = false; 

     [ThreadStatic] 
     static bool readDisabled = false; 

     public override bool CanRead => !readDisabled; 
     public override bool CanWrite => !writeDisabled; 
     public override bool CanConvert(Type objectType) => typeof(IMigratable).IsAssignableFrom(objectType); 

     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { 
      try { 
       writeDisabled = true; 
       if (null == value) { 
        writer.WriteValue(value); 
       } else { 
        var jObject = JObject.FromObject(value); 
        jObject.Add("$typeId", MigratableTypes.GetTypeId(value.GetType())); 
        jObject.WriteTo(writer); 
       } 
      } finally { 
       writeDisabled = false; 
      } 
     } 

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { 
      try { 
       readDisabled = true; 
       var jObject = JToken.ReadFrom(reader) as JObject; 
       if (null == jObject) return null; 
       var typeId = (Guid)jObject.GetValue("$typeId"); 
       var type = MigratableTypes.GetType(typeId); 
       return JsonConvert.DeserializeObject(jObject.ToString(), type); 
      } finally { 
       readDisabled = false; 
      } 
     } 
    } 

    internal static class MigratableTypes { 

     static readonly Dictionary<Guid, Type> Data = new Dictionary<Guid, Type>(); 

     static MigratableTypes() { 
      foreach (var type in GetIMigratableTypes()) { 
       CheckIMigratableRules(type); 
       Data[GetTypeId(type)] = type; 
      } 
     } 

     static IEnumerable<Type> GetIMigratableTypes() { 
      return AppDomain.CurrentDomain.GetAssemblies() 
       .SelectMany(a => a.GetTypes() 
       .Where(t => typeof(IMigratable).IsAssignableFrom(t)) 
       .Where(t => !t.IsAbstract)); 
     } 

     static void CheckIMigratableRules(Type type) { 

      // Check for duplicate IMigratable identifiers 
      var id = GetTypeId(type); 
      if (Data.ContainsKey(id)) 
       throw new Exception($"Duplicate '{nameof(MigratableAttribute)}' value found on types '{type.FullName}' and '{Data[id].FullName}'."); 

      // [DataContract] attribute is required, on EVERY class, not just base classes 
      if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length == 0) 
       throw new Exception($"'{nameof(IMigratable)}' objects are required to use the '[DataContract]' attribute. Class: '{type.FullName}'."); 

      // Collect information about [DataMember] attributes on all fields and properties including inherited and private. 
      var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 
      var fields = type.GetFields(bindingFlags).Where(f => null != f.GetCustomAttribute(typeof(DataMemberAttribute))).ToArray(); 
      var properties = type.GetProperties(bindingFlags).Where(p => null != p.GetCustomAttribute(typeof(DataMemberAttribute))).ToArray(); 
      var members = fields.Cast<MemberInfo>().Concat(properties.Cast<MemberInfo>()) 
       .Select(m => new { 
        Member = m, 
        DataMemberAttribute = (DataMemberAttribute)m.GetCustomAttribute(typeof(DataMemberAttribute)) 
       }).ToArray(); 

      // Check that DataMember names are explicitly set eg [DataMember(Name = "xx")] 
      var noName = members.FirstOrDefault(m => !m.DataMemberAttribute.IsNameSetExplicitly); 
      if (null != noName) { 
       var message = $"'{nameof(IMigratable)}' objects are required to set DataMember names explicitly. Class: '{type.FullName}', Field: '{noName.Member.Name}'."; 
       throw new Exception(message); 
      } 

      // Check that DataMember names are not accidentally duplicated. 
      var duplicateName = members.GroupBy(m => m.DataMemberAttribute.Name).FirstOrDefault(g => g.Count() > 1); 
      if (null != duplicateName) { 
       throw new Exception($"Duplicate DataMemberName '{duplicateName.Key}' found on class '{type.FullName}'."); 
      } 
     } 

     public static Type GetType(Guid typeId) { 
      return Data[typeId]; 
     } 

     public static Guid GetTypeId(Type type) { 

      var a = type.GetCustomAttributes(typeof(MigratableAttribute), false) 
       .Cast<MigratableAttribute>() 
       .FirstOrDefault(); 

      if (null == a) 
       throw new Exception($"'{nameof(MigratableAttribute)}' attribute does not exist on type '{type.FullName}'."); 

      if (Guid.Empty == a.Id) 
       throw new Exception($"'{nameof(MigratableAttribute)}' attribute was not set to a proper value on type '{type.FullName}'."); 

      return a.Id; 
     } 
    } 
} 
関連する問題