忽略在序列化为JSON时抛出exception的类成员

我正在使用Newtonsoft JSON序列化程序,它适用于大多数对象。

不幸的是,当我尝试序列化一个大对象时,我得到一个JsonSerializationException ,其中一个成员抛出一个NullReferenceException

反正有没有忽略违规成员并序列化对象的其余部分?

我想也许在JsonSerializerSettings

这是我想要做的简化版本:

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

这是堆栈跟踪:

 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) 

我很高兴使用不同的JSON序列化程序,如果有人知道会这样做。

如果您不控制源代码,则可以使用自定义ContractResolver在序列化期间为有问题的属性注入“ShouldSerialize”方法。 您可以让该方法始终返回false,或者可选地,实现一些逻辑,该逻辑将检测属性将抛出的情况,并且仅在该情况下返回false。

例如,假设您的类看起来像这样:

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

显然,如果我们尝试序列化上述内容,它将无法工作,因为当序列化程序尝试访问它时, Offender属性将始终抛出exception。 由于我们知道导致问题的类和属性名称,因此我们可以编写自定义ContractResolver(派生自DefaultContractResolver)以禁止该特定成员的序列化。

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

这是一个演示如何使用它的演示:

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

输出:

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

更通用的解决方案

在您的注释中,您指出在访问任何属性时,您有许多可能引发exception的对象。 为此,我们需要更通用的东西。 这是一个可能适用于该情况的解析器,但您需要在自己的环境中进行广泛测试。 它不依赖于任何特定的类或属性名称,但会为每个属性创建一个ShouldSerialize谓词。 在该谓词中,它使用reflection来获取try / catch中的属性值; 如果成功则返回true,否则返回false。

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

这是一个演示:

 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"; } } } 

输出:

 [ { "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 } ] 

一种忽略错误的简单方法:

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

要么

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