忽略序列化为JSON时引发异常的类成员 [英] Ignoring class members that throw exceptions when serializing to JSON

查看:66
本文介绍了忽略序列化为JSON时引发异常的类成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using the Newtonsoft JSON serializer and it works for most objects.

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

Unfortunately I get a JsonSerializationException when I try to serialize a large object, one of whose members throws a NullReferenceException.

总有没有要忽略有问题的成员并序列化对象的其余部分吗?

Is there anyway to ignore the offending member and serialize the rest of the object?

我在想也许在JsonSerializerSettings中?

这是我想做的事情的简化版本:

Here's a simplified version of what I want to do:

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序列化程序.

I'm happy to use a different JSON serializer if someone knows one that will do this.

推荐答案

如果您不控制源代码,则可以使用自定义的

If you don't control the source code, you can use a custom ContractResolver to inject a "ShouldSerialize" method for the problematic property during serialization. You can have that method always return false, or optionally, implement some logic which will detect the situations where the property will throw and return false only in that case.

例如,假设您的班级是这样的:

For example, let's say that your class looks like this:

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

很明显,如果我们尝试序列化上述内容,它将无法正常工作,因为当序列化程序尝试访问Offender属性时,它总是会抛出异常.由于我们知道引起问题的类和属性名称,因此我们可以编写一个自定义的ContractResolver(从DefaultContractResolver派生)来抑制该特定成员的序列化.

Clearly, if we try to serialize the above, it will not work because the Offender property will always throw an exception when the serializer tries to access it. Since we know the class and property name that causes the problem, we can write a custom ContractResolver (derived from the DefaultContractResolver) to suppress the serialization of that specific member.

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

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

Here's a demo showing how to use it:

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

更通用的解决方案

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

A more generic solution

In your comments you indicated that you have many kinds of objects that might throw an exception when any of the properties are accessed. To that end, we need something more generic. Here is a resolver that might work for that case, but you'll need to test it extensively in your own environment. It does not depend on any particular class or property name, but creates a ShouldSerialize predicate for every property that comes its way. In that predicate it uses reflection to get the value of the property inside a try/catch; if successful it returns true, otherwise 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<MightThrow> list = new List<MightThrow>
        {
            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
  }
]

这篇关于忽略序列化为JSON时引发异常的类成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆