当使用[JsonConvert()]时,JSON.Net会抛出StackOverflowException异常。

20 浏览
0 Comments

当使用[JsonConvert()]时,JSON.Net会抛出StackOverflowException异常。

我写了这段简单的代码来将类序列化为扁平化的形式,但是当我使用[JsonConverter(typeof(FJson))]注释时,它会抛出StackOverflowException异常。如果我手动调用SerializeObject,它就可以正常工作。

如何在注释模式下使用JsonConvert:

class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.id = 1;
            a.b.name = "value";
            string json = null;
            // json = JsonConvert.SerializeObject(a, new FJson()); 没有 [JsonConverter(typeof(FJson))] 注释可以正常工作
            // json = JsonConvert.SerializeObject(a); StackOverflowException
            Console.WriteLine(json);
            Console.ReadLine();
        }
    }
    //[JsonConverter(typeof(FJson))] StackOverflowException
    public class A
    {
        public A()
        {
            this.b = new B();
        }
        public int id { get; set; }
        public string name { get; set; }
        public B b { get; set; }
    }
    public class B
    {
        public string name { get; set; }
    }
    public class FJson : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JToken t = JToken.FromObject(value);
            if (t.Type != JTokenType.Object)
            {
                t.WriteTo(writer);
                return;
            }
            JObject o = (JObject)t;
            writer.WriteStartObject();
            WriteJson(writer, o);
            writer.WriteEndObject();
        }
        private void WriteJson(JsonWriter writer, JObject value)
        {
            foreach (var p in value.Properties())
            {
                if (p.Value is JObject)
                    WriteJson(writer, (JObject)p.Value);
                else
                    p.WriteTo(writer);
            }
        }
        public override object ReadJson(JsonReader reader, Type objectType,
           object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
        public override bool CanConvert(Type objectType)
        {
            return true; // 适用于任何类型
        }
    }

0
0 Comments

在使用JSON.Net进行序列化时,有时候会出现StackOverflowException异常。这个问题的出现原因是在序列化过程中发生了递归调用,导致栈溢出。

解决这个问题的方法是通过重写JsonConverter的WriteJson方法,避免递归调用。下面是一个解决方法的示例代码:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
   JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
   writer.WriteStartObject();
   foreach (var property in contract.Properties)
   {
      writer.WritePropertyName(property.PropertyName);
      writer.WriteValue(property.ValueProvider.GetValue(value));
   }
   writer.WriteEndObject();
}

通过重写WriteJson方法,我们可以手动控制序列化的过程,避免递归调用导致的栈溢出异常。这段代码先获取到要序列化对象的类型,然后遍历该类型的所有属性,将属性名和属性值写入JsonWriter中,最后写入结束标记。

使用这种方法可以避免StackOverflowException异常的发生,并且不需要使用递归禁用标志。

0
0 Comments

在使用Json.Net时,当使用JsonConvert()方法时,可能会抛出StackOverflowException异常。这个问题的出现原因是Json.Net对调用JToken.FromObject方法生成“默认”序列化并修改结果JToken以进行输出的转换器没有方便的支持,正是因为你观察到的由于递归调用JsonConverter.WriteJson()而导致的StackOverflowException异常将会发生。

一种解决方法是在递归调用中临时禁用转换器,使用一个线程静态的布尔值。使用线程静态是因为在某些情况下,包括asp.net-web-api等情况下,JSON转换器的实例将在多个线程之间共享。在这种情况下,通过实例属性禁用转换器将不是线程安全的。

public class FJson : JsonConverter
{
    [ThreadStatic]
    static bool disabled;
    // Disables the converter in a thread-safe manner.
    bool Disabled { get { return disabled; } set { disabled = value; } }
    public override bool CanWrite { get { return !Disabled; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken t;
        using (new PushValue(true, () => Disabled, (canWrite) => Disabled = canWrite))
        {
            t = JToken.FromObject(value, serializer);
        }
        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
            return;
        }
        JObject o = (JObject)t;
        writer.WriteStartObject();
        WriteJson(writer, o);
        writer.WriteEndObject();
    }
    private void WriteJson(JsonWriter writer, JObject value)
    {
        foreach (var p in value.Properties())
        {
            if (p.Value is JObject)
                WriteJson(writer, (JObject)p.Value);
            else
                p.WriteTo(writer);
        }
    }
    public override object ReadJson(JsonReader reader, Type objectType,
       object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
    public override bool CanConvert(Type objectType)
    {
        return true; // works for any type
    }
}
public struct PushValue : IDisposable
{
    Action setValue;
    T oldValue;
    public PushValue(T value, Func getValue, Action setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }
    #region IDisposable Members
    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }
    #endregion
}

这样做之后,可以将[JsonConverter(typeof(FJson))]恢复到类A中:

[JsonConverter(typeof(FJson))]
public class A
{
}

第二种更简单的解决方法是为应用了JsonConverter的成员生成默认的JToken表示。这个方法利用了转换器应用于成员优先于应用于类型或设置的转换器的事实。根据文档的说法:

“使用的JsonConverter的优先级是首先是成员上定义的JsonConverter,然后是类上定义的JsonConverter,最后是传递给JsonSerializer的任何转换器。”

因此,可以通过将类型嵌套在具有单个成员的DTO中,该成员的值是该类型的实例,并应用一个什么都不做的虚拟转换器,从而为类型生成默认的序列化。以下扩展方法和转换器可以完成这个任务:

public static partial class JsonExtensions
{
    public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
    {
        if (value == null)
            return JValue.CreateNull();
        var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
        var root = JObject.FromObject(dto, serializer);
        return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
    }
    public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
    {
        var oldParent = token.Parent;
        var dtoToken = new JObject(new JProperty("Value", token));
        var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type);
        var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType);
        if (oldParent == null)
            token.RemoveFromLowestPossibleParent();
        return dto == null ? null : dto.GetValue();
    }
    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        // If the parent is a JProperty, remove that instead of the token itself.
        var contained = node.Parent is JProperty ? node.Parent : node;
        contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (contained is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
    interface IHasValue
    {
        object GetValue();
    }
    [JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
    class DefaultSerializationDTO : IHasValue
    {
        public DefaultSerializationDTO(T value) { this.Value = value; }
        public DefaultSerializationDTO() { }
        [JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
        public T Value { get; set; }
        object IHasValue.GetValue() { return Value; }
    }
}
public class NoConverter : JsonConverter
{
    // NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
    // To https://stackoverflow.com/questions/39738714
    // By https://stackoverflow.com/users/3744182/dbc
    public override bool CanConvert(Type objectType)  { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
    public override bool CanRead { get { return false; } }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); }
    public override bool CanWrite { get { return false; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}

然后在FJson.WriteJson()方法中使用它,如下所示:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JToken t = serializer.DefaultFromObject(value);
    // Remainder as before
    if (t.Type != JTokenType.Object)
    {
        t.WriteTo(writer);
        return;
    }
    JObject o = (JObject)t;
    writer.WriteStartObject();
    WriteJson(writer, o);
    writer.WriteEndObject();
}

这种方法的优缺点是:

1. 它不依赖于递归禁用转换器,因此可以正确处理递归数据模型。

2. 它不需要重新实现从属性序列化对象的整个逻辑。

3. 它序列化和反序列化为中间的JToken表示。当尝试将默认序列化直接流式传输到传入的JsonReader或JsonWriter时,不适用。

注意事项:

1. 两种转换器版本只处理写入操作,读取操作没有实现。要解决反序列化过程中的类似问题,可以参考这个问题的解答:Json.NET custom serialization with JsonConverter - how to get the "default" behavior.

2. 你编写的转换器会创建具有重复名称的JSON,这在严格来说是不合法的,通常被认为是不好的做法,所以最好避免这种情况。

0
0 Comments

JSON.Net throws StackOverflowException when using [JsonConvert()]

问题出现的原因:

在使用JSON.Net时,当使用[JsonConvert()]时,会抛出StackOverflowException异常。这可能是因为在实现WriteJson方法时没有考虑到一些边缘情况,例如ShouldSerialize*()方法、null值、value types(struct)、json converter属性等。

解决方法:

为了解决这个问题,可以尝试以下方法:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
    if (ReferenceEquals(value, null)) {
        writer.WriteNull();
        return;
    }
    var contract = (JsonObjectContract)serializer
        .ContractResolver
        .ResolveContract(value.GetType());
    writer.WriteStartObject();
    foreach (var property in contract.Properties) {
        if (property.Ignored) continue;
        if (!ShouldSerialize(property, value)) continue;
        var property_name = property.PropertyName;
        var property_value = property.ValueProvider.GetValue(value);
        writer.WritePropertyName(property_name);
        if (property.Converter != null && property.Converter.CanWrite) {
            property.Converter.WriteJson(writer, property_value, serializer);
        } else {
            serializer.Serialize(writer, property_value);
        }
    }
    writer.WriteEndObject();
}
private static bool ShouldSerialize(JsonProperty property, object instance) {
    return property.ShouldSerialize == null 
        || property.ShouldSerialize(instance);
}

这个方法是对之前提到的解决方案的改进,可以解决大多数情况下的问题。需要注意的是,在实现WriteJson方法时需要考虑到一些边缘情况,例如ShouldSerialize*()方法、null值、value types(struct)、json converter属性等。

使用JSON.Net时,如果在使用[JsonConvert()]时抛出StackOverflowException异常,可能是因为在实现WriteJson方法时没有考虑到一些边缘情况。为了解决这个问题,可以根据上述提供的代码进行改进,考虑到一些边缘情况,以避免异常的发生。

0