web-dev-qa-db-ja.com

JSONにシリアル化するときに例外をスローするクラスメンバーを無視する

Newtonsoft JSONシリアライザーを使用していますが、ほとんどのオブジェクトで機能します。

残念ながら、メンバーの1つが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シリアライザーを使用してうれしいです。

31
Ev.

ソースコードを制御しない場合は、カスタム ContractResolver を使用して、シリアル化中に問題のあるプロパティに「ShouldSerialize」メソッドを挿入できます。そのメソッドに常にfalseを返すようにするか、オプションで、プロパティがその場合にのみfalseをスローして返す状況を検出するロジックを実装できます。

たとえば、クラスが次のようになっているとしましょう。

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

明らかに、上記をシリアライズしようとすると、シリアライザーがアクセスしようとするとOffenderプロパティが常に例外をスローするため、機能しません。問題の原因となるクラスとプロパティ名がわかっているため、(DefaultContractResolverから派生した)カスタムContractResolverを記述して、その特定のメンバーのシリアル化を抑制できます。

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

より一般的なソリューション

コメントでは、プロパティにアクセスしたときに例外をスローする可能性のある多くの種類のオブジェクトがあることを示しました。そのためには、より一般的なものが必要です。その場合に役立つリゾルバを次に示しますが、独自の環境で広範囲にテストする必要があります。特定のクラスやプロパティ名に依存しませんが、everyプロパティの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
  }
]
28
Brian Rogers

エラーを無視するより簡単な方法:

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

または

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