JSON.Net使用时抛出StackOverflowException [JsonConvert()] [英] JSON.Net throws StackOverflowException when using [JsonConvert()]

查看:489
本文介绍了JSON.Net使用时抛出StackOverflowException [JsonConvert()]的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了这个简单的code到序列化类为扁平化,但是当我使用 [JsonConverter(typeof运算(FJson))] 注释,它抛出一个 StackOverflowException 的。如果我称之为 SerializeObject 手动,它工作正常。

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

 类节目
    {
        静态无效的主要(字串[] args)
        {
            A中新= A();
            a.id = 1;
            a.b.name =值;            JSON字符串= NULL;            // JSON = JsonConvert.SerializeObject(一,新FJson());无[JsonConverter(typeof运算(FJson))]注释workd罚款
            // JSON = JsonConvert.SerializeObject(一); StackOverflowException            Console.WriteLine(JSON);
            到Console.ReadLine();
        }
    }    // [JsonConverter(typeof运算(FJson))] StackOverflowException
    大众A级
    {
        大众A()
        {
            this.b =新B();
        }        公众诠释ID {搞定;组; }
        公共字符串名称{;组; }
        大众B B {搞定;组; }
    }    大众B级
    {
        公共字符串名称{;组; }
    }    公共类FJson:JsonConverter
    {
        公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
        {
            JToken T = JToken.FromObject(值);
            如果(t.Type!= JTokenType.Object)
            {
                t.WriteTo(作家);
                返回;
            }            JObject O =(JObject)吨;
            writer.WriteStartObject();
            WriteJson(作家,O);
            writer.WriteEndObject();
        }        私人无效WriteJson(JsonWriter作家,JObject值)
        {
            的foreach(在value.Properties变种ρ())
            {
                如果(p.Value是JObject)
                    WriteJson(作家(JObject)p.Value);
                其他
                    p.WriteTo(作家);
            }
        }        公众覆盖对象ReadJson(JsonReader读者,类型的objectType,
           对象existingValue,JsonSerializer串行)
        {
            抛出新NotImplementedException();
        }        公众覆盖布尔CanConvert(类型的objectType)
        {
            返回true; //适用于任何类型的
        }
    }


解决方案

Json.NET不具有调用的 JToken.FromObject 生成一个默认的序列化,然后修改所产生的 JToken 输出 - precisely因为 StackOverflowException 将出现

一个解决方法是在递归调用暂时禁用该转换器:

 公共类FJson:JsonConverter
{
    布尔CannotWrite {搞定;组; }    公众覆盖布尔CanWrite {{返回CannotWrite!; }}    公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
    {
        JToken吨;
        使用(新PushValue<布尔>(真的,()=> CannotWrite,(canWrite)=> CannotWrite = canWrite))
        {
            T = JToken.FromObject(值,序列化);
        }        如果(t.Type!= JTokenType.Object)
        {
            t.WriteTo(作家);
            返回;
        }        JObject O =(JObject)吨;
        writer.WriteStartObject();
        WriteJson(作家,O);
        writer.WriteEndObject();
    }    私人无效WriteJson(JsonWriter作家,JObject值)
    {
        的foreach(在value.Properties变种ρ())
        {
            如果(p.Value是JObject)
                WriteJson(作家(JObject)p.Value);
            其他
                p.WriteTo(作家);
        }
    }    公众覆盖对象ReadJson(JsonReader读者,类型的objectType,
       对象existingValue,JsonSerializer串行)
    {
        抛出新NotImplementedException();
    }    公众覆盖布尔CanConvert(类型的objectType)
    {
        返回true; //适用于任何类型的
    }
}公共结构PushValue< T> :IDisposable接口
{
    FUNC< T>的getValue;
    动作< T> setValue方法;
    牛逼的属性oldValue;    公共PushValue(吨价,Func键< T>的getValue,动作< T>的setValue)
    {
        如果(的getValue == NULL ||的setValue == NULL)
            抛出新的ArgumentNullException();
        this.getValue =的getValue;
        this.setValue = setValue方法;
        this.oldValue =的getValue();
        的setValue(值);
    }    #区域IDisposable的成员    //通过使用一次性的结构,我们避免分配和释放一个终结类的一个实例的开销。
    公共无效的Dispose()
    {
        如果(的setValue!= NULL)
            的setValue(属性oldValue);
    }    #endregion
}

在此方案中,需要调用 JToken .FromObject(对象,JsonSerializer) 并传下输入串行器,让您转换器的同一个实例 FJson 被使用。这样做之后,可以还原 [JsonConverter(typeof运算(FJson))] 到类 A

  [JsonConverter(typeof运算(FJson))]
大众A级
{
}

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


  {
  标识:1,
  名:空,
  名:值
}


此,而不是严格的非法,通常被认为是<一个href=\"https://stackoverflow.com/questions/21832701/does-json-syntax-allow-duplicate-keys-in-an-object/23195243#23195243\">bad实践

更新

在某些情况下,例如的 asp.net-Web的API ,JSON转换器的实例将线程之间共享。在这种情况下,通过一个实例属性禁用转换器不会线程安全的。相反, [ThreadStatic] 可以用来禁用该转换器:

 公共类FJson:JsonConverter
{
    [ThreadStatic]
    静态布尔禁用;    //禁止在线程安全的方式转换。
    布尔残疾人{{返回禁用; } {设置禁用=价值; }}    公众覆盖布尔CanWrite {{返回禁用!; }}    公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
    {
        JToken吨;
        使用(新PushValue&LT;布尔&GT;(真的,()=&GT;禁用(canWrite)=&GT;禁用= canWrite))
        {
            T = JToken.FromObject(值,序列化);
        }    //,剩余部分作为原来的答案:        如果(t.Type!= JTokenType.Object)
        {
            t.WriteTo(作家);
            返回;
        }        JObject O =(JObject)吨;
        writer.WriteStartObject();
        WriteJson(作家,O);
        writer.WriteEndObject();
    }    私人无效WriteJson(JsonWriter作家,JObject值)
    {
        的foreach(在value.Properties变种ρ())
        {
            如果(p.Value是JObject)
                WriteJson(作家(JObject)p.Value);
            其他
                p.WriteTo(作家);
        }
    }    公众覆盖对象ReadJson(JsonReader读者,类型的objectType,
       对象existingValue,JsonSerializer串行)
    {
        抛出新NotImplementedException();
    }    公众覆盖布尔CanConvert(类型的objectType)
    {
        返回true; //适用于任何类型的
    }
}

请注意此转换器不仅书写;读未实现。

I wrote this simple code to Serialize classes as flatten, but when I use [JsonConverter(typeof(FJson))] annotation, it throws a StackOverflowException. If I call the SerializeObject manually, it works fine.

How can I use JsonConvert in Annotation mode:

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
        }
    }

解决方案

Json.NET does not have convenient support for converters that call JToken.FromObject to generate a "default" serialization, then modify the resulting JToken for output - precisely because a StackOverflowException will occur.

One workaround is to temporarily disable the converter in recursive calls:

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
    }
}

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
}

In this scheme, it is necessary to call JToken.FromObject(Object, JsonSerializer) and pass down the incoming serializer, so that the same instance of your converter FJson is used. Having done this, you can restore the [JsonConverter(typeof(FJson))] to your class A:

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

Incidentally, your converter as written creates JSON with duplicated names:

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

This, while not strictly illegal, is generally considered to be bad practice

Update

In some situations, for instance , instances of JSON converters will be shared between threads. In that case, disabling the converter via an instance property will not be thread-safe. Instead, a [ThreadStatic] can be used to disable the converter:

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

    // And the remainder is as in the original answer:

        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
    }
}

Note this converter only does writing; reading is not implemented.

这篇关于JSON.Net使用时抛出StackOverflowException [JsonConvert()]的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆