JSON.Net выдает StackOverflowException при использовании

Я написал этот простой код для classов Serialize как сглаженные, но когда я использую [JsonConverter(typeof(FJson))] , он выдает [JsonConverter(typeof(FJson))] StackOverflowException . Если я вызову SerializeObject вручную, он отлично работает.

Как я могу использовать JsonConvert в режиме annotations:

 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 .

Одним из способов является временное отключение конвертера в рекурсивных вызовах с использованием streamа static Boolean. Статический stream используется, потому что в некоторых ситуациях, включая asp.net-web-api , экземпляры JSON-конвертеров будут совместно использоваться streamами. В этом случае отключение преобразователя через свойство экземпляра не будет streamобезопасным.

 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))] в свой class 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*() методы public bool ShouldSerialize*()
  • null значения
  • типы значений ( 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); } 

Я еще не могу прокомментировать, извините за это … но я просто хотел добавить что-то к решению, предоставленному Полом Киаром. Его решение действительно помогло мне.

Код Павла короткий и просто работает без какого-либо индивидуального создания объектов. Единственное дополнение, которое я хотел бы сделать, это вставить проверку, если свойство игнорируется. Если он установлен для игнорирования, пропустите запись для этого свойства:

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

Поместив атрибут в class A, он вызывается рекурсивно. Первая строка в переопределении WriteJson снова вызывает сериализатор classа 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, который вы получите здесь, будет иметь два ключа под названием « имя » один из classа A, а другой из classа B.

  • Как сериализовать словарь как часть его родительского объекта с помощью Json.Net
  • Служба WCF Максимальная квота длины массива (16384) превышена
  • C # JSON.NET - десериализация ответа, использующего необычную структуру данных
  • Добавление к ObjectOutputStream
  • Django rest framework, используйте разные сериализаторы в том же ModelViewSet
  • Как вы делаете глубокую копию объекта в .NET (например, C #)?
  • Как сериализовать lambda?
  • Как сериализовать Java-примитивы с помощью Jersey REST
  • Специальная сериализация Jackson JSON для определенных полей
  • Как можно полиморфная десериализация Json String с использованием Java и библиотеки Jackson?
  • Рекомендации по сериализации объектов в пользовательский строковый формат для использования в выходном файле
  • Interesting Posts
    Давайте будем гением компьютера.