c# - tipo - there was an error reflecting type




Ignorando i membri della classe che generano eccezioni durante la serializzazione su JSON (2)

Se non si controlla il codice sorgente, è possibile utilizzare un ContractResolver personalizzato per iniettare un metodo "ShouldSerialize" per la proprietà problematica durante la serializzazione. Puoi fare in modo che quel metodo restituisca sempre false o, facoltativamente, implementa una logica che rilevi le situazioni in cui la proprietà genererà e restituirà false solo in quel caso.

Ad esempio, diciamo che la tua classe ha questo aspetto:

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

Chiaramente, se proviamo a serializzare quanto sopra, non funzionerà perché la proprietà Offender genererà sempre un'eccezione quando il serializzatore tenta di accedervi. Poiché conosciamo la classe e il nome della proprietà che causa il problema, possiamo scrivere un ContractResolver personalizzato (derivato da DefaultContractResolver) per sopprimere la serializzazione di quel membro specifico.

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

Ecco una demo che mostra come usarla:

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

Produzione:

{"Id":1,"Name":"Foo"}

Una soluzione più generica

Nei tuoi commenti hai indicato di avere molti tipi di oggetti che potrebbero generare un'eccezione quando si accede a una delle proprietà. A tal fine, abbiamo bisogno di qualcosa di più generico. Ecco un risolutore che potrebbe funzionare per quel caso, ma dovrai testarlo ampiamente nel tuo ambiente. Non dipende da una particolare classe o nome di proprietà, ma crea un predicato ShouldSerialize per ogni proprietà che viene a sua volta. In quel predicato usa la riflessione per ottenere il valore della proprietà all'interno di un try / catch; se ha successo ritorna vero, altrimenti falso.

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

Ecco una demo:

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

Produzione:

[
  {
    "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
  }
]

Sto usando il serializzatore JSON Newtonsoft e funziona per la maggior parte degli oggetti.

Sfortunatamente ottengo una JsonSerializationException quando provo a serializzare un oggetto grande, uno dei cui membri lancia una NullReferenceException .

C'è comunque modo di ignorare il membro offendente e serializzare il resto dell'oggetto?

Sto pensando forse a JsonSerializerSettings ?

Ecco una versione semplificata di ciò che voglio fare:

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

Ed ecco la traccia dello stack:

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)

Sono felice di utilizzare un serializzatore JSON diverso se qualcuno ne conosce uno che lo farà.


Un modo più semplice per ignorare gli errori:

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

o

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




json.net