C# 忽略序列化为JSON时引发异常的类成员

C# 忽略序列化为JSON时引发异常的类成员,c#,json,serialization,json.net,C#,Json,Serialization,Json.net,我使用的是Newtonsoft JSON序列化程序,它适用于大多数对象 不幸的是,当我尝试序列化一个大对象时,得到了一个JsonSerializationException,该对象的一个成员抛出了一个NullReferenceException 是否仍然可以忽略有问题的成员并序列化对象的其余部分 我想也许在JsonSerializerSettings中 以下是我想做的一个简化版本: private class TestExceptionThrowingClass { public str

我使用的是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序列化程序,我很乐意使用不同的JSON序列化程序。

如果不控制源代码,可以在序列化过程中使用自定义为有问题的属性注入“ShouldSerialize”方法。您可以让该方法始终返回false,或者可以选择实现一些逻辑来检测只有在这种情况下属性才会抛出并返回false的情况

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

class Problematic
{
    public int Id { get; set; }
    public string Name { get; set; }
    public object Offender 
    {
        get { throw new NullReferenceException(); }
    }
}
显然,如果我们尝试序列化上面的内容,它将不起作用,因为当序列化程序尝试访问它时,
属性总是抛出异常。由于我们知道导致问题的类和属性名称,因此可以编写自定义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"}
[
  {
    "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
  }
]
更通用的解决方案 在您的注释中,您指出有许多类型的对象在访问任何属性时可能引发异常。为此,我们需要一些更通用的东西。下面是一个可能适用于这种情况的解析器,但您需要在自己的环境中对其进行广泛测试。它不依赖于任何特定的类或属性名,而是为每个属性创建一个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";
        }
    }
}

忽略错误的更简单方法:

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


我只是想澄清一下,当被访问时,该成员实际上是
null
还是显式抛出
NullReferenceException
?该成员在被访问时抛出NullReferenceException。假设您不控制有问题成员的源代码是否正确?奇怪-描述了类似的问题和设置
NullValueHandling
NullValueHandling.Ignore
似乎是解决方案。想知道你的情况有什么不同……我在寻找另一个问题的解决方案:为什么我会得到JSONSerializationError。你帮我解决了这个问题:“世卫组织的一个成员抛出了一个NullReferenceException。”谢谢你,太酷了!我喜欢。唯一的问题是可能会有多个不同的类被序列化,从而引发异常。所以,检测属性是否将抛出异常的唯一方法是尝试…捕获它。我能在你的解决方案中做到这一点吗?你能举例说明你的意思吗?你是说违犯者属性可以包含多个对象中任意一个的实例,一些对象抛出,而另一些对象不抛出?好的,我有一些似乎有效的东西(请参阅更新的答案)。你的里程可能会有所不同。哇,你太棒了。非常感谢你!它工作得很好。现在我唯一担心的是,当人们看到我的代码时,他们会认为我比我实际聪明得多。我将在代码中添加一条注释,链接回这篇文章,这样就不会产生混淆。谢谢你,伙计!英雄联盟很高兴我能帮上忙。有什么错误吗?假装你没有出错!可能会出什么问题?我通常会同意你的看法,但我认为这是一个基于情况的判断。我正在尝试将对象转储到磁盘,以便能够轻松地比较它们,我真的不想在接下来的几个小时内尝试让自定义解析器为一些代码工作,而这些代码我只用于故障排除目的。我正在使用的对象在库中,我无法更改它的工作方式。如果我用一种方式调用它,一组属性无效,如果我用另一种方式调用它,另一组属性无效。所以要么是这个解析器,要么是自定义解析器。@MikeTaber我同意,如果你别无选择,只能编写坏代码,作为一些你无法控制的坏代码的解决办法,那么你可能需要使用这个解析器。它就像一个符咒。谢谢。我发现这对任意记录对象很有用,我不在乎序列化是否失败。
settings.Error = (serializer,err) => err.ErrorContext.Handled = true;