使用JObject.FromValue时的StackOverflow

时间:2017-12-23 10:22:01

标签: c# json json.net event-sourcing

在调用WriteJsonJObject.FromObject(value)方法内的代码导致了StockOverflowException。它回顾了WriteJson方法。

如何重写AggregateEventConverter以避免递归堆栈溢出问题?

因为我知道有人会问,代码是这样写的,因为事件是永久写入流的,并且需要能够在其他编码器重构旧事件类的名称之后几年后准确地反序列化。例如,他们可能会将class AppleFellOffTree更改为class 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;
}

1 个答案:

答案 0 :(得分:1)

在阅读评论中提供的链接后,我想出了这个解决方案。

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

const elem = document.createElement('a');
elem.href = '//Twitter.com/mhluska';
console.log(elem.href);