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

43

我正在使用Newtonsoft JSON序列化器,它对于大多数对象都有效。

不幸的是,当我尝试序列化一个成员抛出NullReferenceException的大型对象时,会出现JsonSerializationException

有没有办法忽略有问题的成员并将对象的其余部分序列化?

我想也许可以在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序列化工具,我很乐意使用。


2
仅为澄清,成员实际上是“null”,还是在访问时明确抛出“NullReferenceException”? - lc.
该成员在访问时会抛出 NullReferenceException 异常。 - Ev.
奇怪 - 这个问题 描述了一个类似的问题,将 NullValueHandling 设置为 NullValueHandling.Ignore 似乎是解决方案。不知道在你的情况下有什么不同.... - Srikanth Venugopalan
@BrianRogers 是的。没错。 - Ev.
1
我正在寻找解决另一个问题的方案:为什么会出现JSonSerilizationError。你为我解决了这个问题:“其中一个成员引发了NullReferenceException异常。”谢谢。 - Andreas
显示剩余6条评论
2个回答

58

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

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

7
遇到任何错误?就假装你没有收到错误!可能会发生什么问题呢? - EKW
13
我通常会同意你的看法,但我觉得这是基于情况而做出的判断。我正在尝试将对象转储到磁盘,以便可以轻松比较它们,而且我真的不想再花费几个小时尝试为我仅用于故障排除目的的代码编写自定义解析器。我正在使用一个库中的对象进行操作,我无法更改其工作方式。如果我以一种方式调用它,则一组属性将无效;如果我以另一种方式调用它,则会有不同的一组属性无效。因此,要么选择这个方法,要么就需要使用自定义解析器。 - Mike Taber
@MikeTaber 我同意这是一种 hack,你可能需要使用它,如果你别无选择,只能写一些糟糕的代码作为一些你无法控制的糟糕代码的解决方法。 - EKW
非常好用。谢谢。 - Sebastián Guerrero
5
我发现这对于任意对象的日志记录很有用,我不关心是否无法序列化而导致失败。 - Alex Logan
显示剩余3条评论

34

如果您无法控制源代码,您可以使用自定义ContractResolver在序列化期间插入一个“ShouldSerialize”方法来处理问题属性。您可以让该方法始终返回false,或者选择实现一些逻辑,以便仅在该情况下检测到属性将抛出并返回false。

例如,假设您的类如下所示:

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

显然,如果我们尝试对上面的内容进行序列化,它将无法工作,因为当序列化器尝试访问Offender属性时,它总是会抛出异常。由于我们知道导致问题的类和属性名称,我们可以编写一个定制的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"}

一种更通用的解决方案

根据您的评论,您表示有许多种对象,在访问任何属性时可能会引发异常。为此,我们需要一些更通用的东西。以下是一个可能适用于该情况的解析器,但您需要在自己的环境中进行广泛测试。它不依赖于任何特定的类或属性名,而是为每个遇到的属性创建一个ShouldSerialize谓词。在该谓词中,它使用反射在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<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
  }
]

1
非常酷!我喜欢它。唯一的问题是可能会有多个不同的类被序列化,这些类可能会抛出异常。因此,检测属性是否会抛出异常的唯一方法是尝试...捕获它。在你的解决方案中,我可以这样做吗? - Ev.
那将是理想的。谢谢你的帮助。 - Ev.
2
好的,我有一个似乎可以工作的东西(请参见更新的答案)。你的结果可能会有所不同。 - Brian Rogers
1
哇,你太厉害了。非常感谢!它运行得很好。现在我唯一的担心是当别人看我的代码时,他们会认为我比实际聪明得多。我将在代码中添加一个链接回到这篇文章的评论,以免混淆。谢谢啊! - Ev.
2
哈哈!很高兴我能帮到你。 - Brian Rogers
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接