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 } }
- Может ли JavaScriptSerializer исключать свойства с нулевыми значениями / значениями по умолчанию?
- Deserialize JSON в динамический объект C #?
- Джерси + jackson JSON формат даты формат - как изменить формат или использовать пользовательские JacksonJsonProvider
- Игнорирование нулевых полей в Json.net
- Избегайте сериализации jacksonа на непривлекательных ленивых объектах
- gwt - Использование списка в вызове RPC?
- Java: объект для байта и байт для конвертера объектов (для Tokyo Cabinet)
- StackOverflowError при сериализации объекта в Java
- Информация о сериализации типов кеша Json.NET?
- Как сериализовать словарь как часть его родительского объекта с помощью Json.Net
- C # десериализация структуры после ее получения через TCP
- Как добавить тип в белый список политики сериализации GWT?
- JSON.NET Parser * кажется * будет двойной сериализацией моих объектов
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.