Ignorando i membri della class che generano eccezioni durante la serializzazione su JSON

Sto usando il serializzatore JSON Newtonsoft e funziona per la maggior parte degli oggetti.

Sfortunatamente ottengo una JsonSerializationException quando provo a serializzare un object grande, uno dei cui membri lancia una NullReferenceException .

C’è comunque modo di ignorare il membro offendente e serializzare il resto dell’object?

Sto pensando forse a JsonSerializerSettings ?

Ecco una versione semplificata di ciò che voglio fare:

 private class TestExceptionThrowingClass { public string Name { get { return "The Name"; } } public string Address { get { throw new NullReferenceException(); } } public int Age { get { return 30; } } } [Test] public void CanSerializeAClassWithAnExceptionThrowingMember() { // Arrange var toSerialize = new TestExceptionThrowingClass(); // Act var serializerSettings = new Newtonsoft.Json.JsonSerializerSettings(); serializerSettings.MaxDepth = 5; serializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; serializerSettings.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore; serializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; serializerSettings.ObjectCreationHandling = Newtonsoft.Json.ObjectCreationHandling.Reuse; serializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore; var result = Newtonsoft.Json.JsonConvert.SerializeObject(toSerialize); // Assert Assert.That(result, Is.EqualTo(@"{""Name"":""The Name"",""Age"":30}")); } 

Ed ecco la traccia dello stack:

 at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType) at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, Formatting formatting, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.SerializeObject(Object value) at AspectsProject.Aspects.CachingPolicy.CachingPolicyCacheKeyCreatorTests.CanSerializeAClassWithAnExceptionThrowingMember() in D:\Dev\test.cs:line 169 --NullReferenceException at AspectsProject.Aspects.CachingPolicy.CachingPolicyCacheKeyCreatorTests.TestExceptionThrowingClass.get_Address() in D:\Dev\test.cs:line 149 at GetAddress(Object ) at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target) 

Sono felice di utilizzare un serializzatore JSON diverso se qualcuno ne conosce uno che lo farà.

Se non si controlla il codice sorgente, è ansible utilizzare un ContractResolver personalizzato per iniettare un metodo “ShouldSerialize” per la proprietà problematica durante la serializzazione. Puoi fare in modo che quel metodo restituisca sempre false o, facoltativamente, implementa una logica che rilevi le situazioni in cui la proprietà genererà e restituirà false solo in quel caso.

Ad esempio, diciamo che la tua class ha questo aspetto:

 class Problematic { public int Id { get; set; } public string Name { get; set; } public object Offender { get { throw new NullReferenceException(); } } } 

Chiaramente, se proviamo a serializzare quanto sopra, non funzionerà perché la proprietà Offender genererà sempre un’eccezione quando il serializzatore tenta di accedervi. Poiché conosciamo la class e il nome della proprietà che causa il problema, possiamo scrivere un ContractResolver personalizzato (derivato da DefaultContractResolver) per sopprimere la serializzazione di quel membro specifico.

 class CustomResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); if (property.DeclaringType == typeof(Problematic) && property.PropertyName == "Offender") { property.ShouldSerialize = instanceOfProblematic => false; } return property; } } 

Ecco una demo che mostra come usarla:

 class Program { static void Main(string[] args) { Problematic obj = new Problematic { Id = 1, Name = "Foo" }; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ContractResolver = new CustomResolver(); string json = JsonConvert.SerializeObject(obj, settings); Console.WriteLine(json); } } 

Produzione:

 {"Id":1,"Name":"Foo"} 

Una soluzione più generica

Nei tuoi commenti hai indicato di avere molti tipi di oggetti che potrebbero generare un’eccezione quando si accede a una delle proprietà. A tal fine, abbiamo bisogno di qualcosa di più generico. Ecco un risolutore che potrebbe funzionare per quel caso, ma dovrai testarlo ampiamente nel tuo ambiente. Non dipende da una particolare class o nome di proprietà, ma crea un predicato ShouldSerialize per ogni proprietà che viene a sua volta. In quel predicato usa la riflessione per ottenere il valore della proprietà all’interno di un try / catch; se ha successo ritorna vero, altrimenti falso.

 class CustomResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); property.ShouldSerialize = instance => { try { PropertyInfo prop = (PropertyInfo)member; if (prop.CanRead) { prop.GetValue(instance, null); return true; } } catch { } return false; }; return property; } } 

Ecco una demo:

 class Program { static void Main(string[] args) { List list = new List { new MightThrow { Flags = ThrowFlags.None, Name = "none throw" }, new MightThrow { Flags = ThrowFlags.A, Name = "A throws" }, new MightThrow { Flags = ThrowFlags.B, Name = "B throws" }, new MightThrow { Flags = ThrowFlags.Both, Name = "both throw" }, }; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ContractResolver = new CustomResolver(); settings.Formatting = Formatting.Indented; string json = JsonConvert.SerializeObject(list, settings); Console.WriteLine(json); } } [Flags] enum ThrowFlags { None = 0, A = 1, B = 2, Both = 3 } class MightThrow { public string Name { get; set; } public ThrowFlags Flags { get; set; } public string A { get { if ((Flags & ThrowFlags.A) == ThrowFlags.A) throw new Exception(); return "a"; } } public string B { get { if ((Flags & ThrowFlags.B) == ThrowFlags.B) throw new Exception(); return "b"; } } } 

Produzione:

 [ { "Name": "none throw", "Flags": 0, "A": "a", "B": "b" }, { "Name": "A throws", "Flags": 1, "B": "b" }, { "Name": "B throws", "Flags": 2, "A": "a" }, { "Name": "both throw", "Flags": 3 } ] 

Un modo più semplice per ignorare gli errori:

 JsonSerializerSettings settings = new JsonSerializerSettings(); settings.Error = (serializer,err) => { err.ErrorContext.Handled = true; } 

o

 settings.Error = (serializer,err) => err.ErrorContext.Handled = true;