JSON.Net在使用[JsonConvert()]时抛出StackOverflowException

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

我如何在Annotation模式下使用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()); without [JsonConverter(typeof(FJson))] annotation workd fine
            // 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; // works for any type
        }
    }

对于调用JToken.FromObject生成“默认”序列化,然后修改生成的JToken进行输出的转换器,Json.NET没有方便的支持 - 这正是因为您观察到的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<bool>(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<T> : IDisposable
{
    Func<T> getValue;
    Action<T> setValue;
    T oldValue;

    public PushValue(T value, Func<T> getValue, Action<T> setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.getValue = getValue;
        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
}

注意这个转换器只能写入; 阅读没有实施。

顺便说一句,你写的转换器会创建带有重复名称的JSON:

{
  "id": 1,
  "name": null,
  "name": "value"
}

这虽然不严格违法,但通常被认为是不好的做法

如果你确定你的转换器不会被跨线程共享,你可以使用成员变量:

public class FJson : JsonConverter
{
    bool CannotWrite { get; set; }

    public override bool CanWrite { get { return !CannotWrite; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken t;
        using (new PushValue<bool>(true, () => CannotWrite, (canWrite) => CannotWrite = 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
    }
}

在这个方案中,有必要调用JToken.FromObject(Object, JsonSerializer)并传入串行器,以便使用转换器FJson的相同实例。 完成此操作后,可以将[JsonConverter(typeof(FJson))]恢复到您的类A

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

我不喜欢上面发布的解决方案,因此我计算了序列化程序如何实际序列化对象并试图将其最小化:

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();
}

没有堆栈溢出问题,也不需要递归禁用标志。


在阅读(并测试)Paul Kiar&p.kaneman解决方案后,我认为实现WriteJson似乎是一项具有挑战性的任务。 尽管它适用于大多数情况 - 还有一些尚未覆盖的边缘案例。 例子:

  • public bool ShouldSerialize*()方法
  • null
  • 值类型( struct
  • json转换器属性
  • ..
  • 这是(只是)另一个尝试:

    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);
    }
    
    链接地址: http://www.djcxy.com/p/37831.html

    上一篇: JSON.Net throws StackOverflowException when using [JsonConvert()]

    下一篇: Merging Concatenating JSON(B) columns in query