使用时,JSON.Net抛出StackOverflowException

我将这个简单的代码编写为Serialize类为flatten,但是当我使用[JsonConverter(typeof(FJson))]注释时,它会抛出StackOverflowException 。 如果我手动调用SerializeObject ,它可以正常工作。

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

 class Program { static void Main(string[] args) { A a = new A(); a.id = 1; abname = "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没有方便的支持转换器调用JToken.FromObject生成“默认”序列化,然后修改生成的JToken输出 – 正是因为您观察到的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 { Func getValue; Action setValue; T oldValue; public PushValue(T value, Func getValue, Action 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(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); } 

我还不能发表评论,很抱歉……但我只想在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(); } 

通过将属性放在类A上,它将被递归调用。 WriteJson覆盖的第一行再次调用A类的序列化器。

 JToken t = JToken.FromObject(value); 

这会导致递归调用,从而导致StackOverflowException。

从您的代码中,我认为您正试图平息层次结构。 您可以通过将转换器属性放在属性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类。