【问题标题】:Ignoring class members that throw exceptions when serializing to JSON忽略序列化为 JSON 时抛出异常的类成员
【发布时间】:2014-01-07 01:17:58
【问题描述】:

我正在使用 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 序列化程序。

【问题讨论】:

  • 为了澄清,该成员实际上是null,还是在访问时明确抛出NullReferenceException
  • 成员抛出 NullReferenceException 并访问。
  • 假设您不控制违规成员的源代码是否正确?
  • Strange - this question 描述了一个类似的问题,将 NullValueHandling 设置为 NullValueHandling.Ignore 似乎是解决方案。想知道你的情况有什么不同......
  • 我正在寻找另一个问题的解决方案:为什么我会收到 JSonSerilizationError。你为我解决了这个问题:“其中一个成员抛出了 NullReferenceException。”谢谢

标签: c# json serialization json.net


【解决方案1】:

忽略错误的更简单方法:

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

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

【讨论】:

  • 得到任何类型的错误?假装你没有收到错误!可能出了什么问题?
  • 我通常会同意你的观点,但我认为这是基于情况的判断。我正在尝试将对象转储到磁盘,以便可以轻松地比较它们,而且我真的不想花接下来的几个小时尝试让自定义解析器为一些我仅用于故障排除目的的代码工作。我正在使用的对象在一个库中,我无法更改它的工作方式。如果我称它为一种方式,一组属性是无效的,如果我称它为另一组,则另一组是无效的。所以它要么是这个解析器,要么是自定义解析器。
  • @MikeTaber 我同意这是一个技巧,如果你别无选择,只能编写糟糕的代码作为你无法控制的一些糟糕代码的解决方法。
  • 像魅力一样工作。谢谢。
  • 我发现这对于任意记录对​​象很有用,我不在乎是否有序列化失败。
【解决方案2】:

如果不控制源代码,可以使用自定义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"}

更通用的解决方案

在您的 cmets 中,您指出您有多种对象在访问任何属性时可能会引发异常。为此,我们需要更通用的东西。这是一个可能适用于这种情况的解析器,但您需要在自己的环境中对其进行广泛测试。它不依赖于任何特定的类或属性名称,而是为出现的 每个 属性创建一个 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
  }
]

【讨论】:

  • 非常酷!我喜欢。唯一的问题是可能有多个不同的类将被序列化,这可能会引发异常。因此,检测属性是否会引发异常的唯一方法是尝试...捕获它。我可以在您的解决方案中这样做吗?
  • 你能举例说明你的意思吗?您是说 Offender 属性可以包含多个对象中的任何一个的实例,并且有些对象会抛出而有些则不会?
  • 好的,我有一些似乎可以工作的东西(请参阅更新的答案)。您的里程可能会有所不同。
  • 哇,你太棒了。太感谢了!它工作得很好。现在我唯一担心的是,当人们查看我的代码时,他们会认为我比实际聪明得多。我将在链接回这篇文章的代码中添加评论,以免造成混淆。谢谢大佬!
  • 哈哈!很高兴我能帮上忙。
猜你喜欢
  • 2020-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 1970-01-01
相关资源
最近更新 更多