Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用[JsonConvert()]时,JSON.Net抛出StackOverflowException_C#_Json_Serialization_Json.net_Asp.net Web Api - Fatal编程技术网

C# 使用[JsonConvert()]时,JSON.Net抛出StackOverflowException

C# 使用[JsonConvert()]时,JSON.Net抛出StackOverflowException,c#,json,serialization,json.net,asp.net-web-api,C#,Json,Serialization,Json.net,Asp.net Web Api,我编写这个简单的代码是为了将类序列化为flant,但是当我使用[JsonConverter(typeof(FJson))]注释时,它抛出了一个StackOverflowException。如果我手动调用serialized对象,它可以正常工作 如何在注释模式下使用JsonConvert: class Program { static void Main(string[] args) { A a = new A();

我编写这个简单的代码是为了将类序列化为flant,但是当我使用
[JsonConverter(typeof(FJson))]
注释时,它抛出了一个StackOverflowException。如果我手动调用
serialized对象
,它可以正常工作

如何在注释模式下使用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
        }
    }

通过将属性放置在类A上,可以递归地调用它。WriteJson覆盖中的第一行再次调用类A上的序列化程序

JToken t = JToken.FromObject(value);
这会导致递归调用,从而导致StackOverflowException

从你的密码来看,我认为你在试图削弱继承权。您可能可以通过将converter属性放在属性B上来实现这一点,这将避免递归

//remove the converter from here
public class A
{
    public A()
    {
        this.b = new B();
    }

    public int id { get; set; }
    public string name { get; set; }
    [JsonConverter(typeof(FJson))] 
    public B b { get; set; }
}

警告:您在这里获得的Json将有两个名为“name”的键,一个来自类A,另一个来自类B。

Json.NET不支持调用以生成“默认值”的转换器序列化,然后为输出修改生成的
JToken
——这正是因为您观察到的
StackOverflowException
是由于对
JsonConverter.WriteJson()
的递归调用而产生的

一种解决方法是使用线程静态布尔值在递归调用中临时禁用转换器。之所以使用线程静态,是因为在某些情况下,包括在线程之间共享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
{
    Action<T> setValue;
    T oldValue;

    public PushValue(T value, Func<T> getValue, Action<T> 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
}
演示小提琴#1

为应用了
JsonConverter
的类型生成默认序列化的第二个更简单的解决方法
利用应用于成员的转换器取代应用于类型或设置中的转换器这一事实。从:

使用JsonConverter的优先级是由成员上的属性定义的JsonConverter,然后是由类上的属性定义的JsonConverter,最后是传递给JsonSerializer的任何转换器

因此,通过将类型嵌套在具有单个成员(其值是您的类型的实例)的内部,并具有一个应用程序,该应用程序除了返回默认序列化进行读写之外,什么都不做,从而可以为您的类型生成默认序列化

以下扩展方法和转换器执行此任务:

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<T> : 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/selectively-use-default-json-converter
    // 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(); }
}
这种方法的优点是:

  • 它不依赖于递归地禁用转换器,因此可以正确地使用递归数据模型

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

  • 演示小提琴2

    注释

    • 两个转换器版本只处理写入;阅读没有实现

      要解决反序列化过程中的等效问题,请参阅例如

    • 您编写的转换器使用重复的名称创建JSON:

      这虽然不是严格的非法行为,但通常被认为是非法的,因此可能应该避免


    我不喜欢上面发布的解决方案,因此我找到了序列化程序实际序列化对象的方式,并尝试将其提取到最低限度:

    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提供的解决方案中添加一些内容。他的解决方案真的帮了我大忙

    保罗的法典很短,没有任何自定义的对象构建。 我想做的唯一补充是在忽略属性时插入一个检查。如果设置为忽略,则跳过该属性的写入:

        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)
            {
                if (property.Ignored)
                    continue;
    
                writer.WritePropertyName(property.PropertyName);
                writer.WriteValue(property.ValueProvider.GetValue(value));
            }
            writer.WriteEndObject();
        }
    
    在阅读(并测试)Paul Kiar&p.kaneman解决方案后,我认为实现
    WriteJson
    似乎是一项具有挑战性的任务。尽管它适用于大多数情况,但仍有一些边缘情况尚未涵盖。 示例:

    • 公共bool应该序列化*()
      方法
    • 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);
    }
    

    +1一个答案不被接受,并且0次投票通常不会引起注意,但我想指出的是,这是最先进的解决方案,使我更接近我想要的位置(我在序列化某些属性时也需要忽略空值,但这样做很容易)。谢谢
    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();
    }
    
        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)
            {
                if (property.Ignored)
                    continue;
    
                writer.WritePropertyName(property.PropertyName);
                writer.WriteValue(property.ValueProvider.GetValue(value));
            }
            writer.WriteEndObject();
        }
    
    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);
    }