43

I'm using the Newtonsoft JSON serializer and it works for most objects.

Unfortunately I get a JsonSerializationException when I try to serialize a large object, one of whose members throws a NullReferenceException.

Is there anyway to ignore the offending member and serialize the rest of the object?

I'm thinking perhaps in the JsonSerializerSettings?

Here's a simplified version of what I want to do:

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

And here's the stack trace:

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)

I'm happy to use a different JSON serializer if someone knows one that will do this.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Ev.
  • 7,109
  • 14
  • 53
  • 87
  • 2
    Just for clarification, is the member actually `null` or does it explicitly throw a `NullReferenceException` when accessed? – lc. Jan 07 '14 at 01:19
  • The member throws a NullReferenceException with accessed. – Ev. Jan 07 '14 at 01:26
  • Is it correct to assume that you do not control the source code for the offending member? – Brian Rogers Jan 07 '14 at 01:32
  • Strange - [this question](http://stackoverflow.com/q/6507889/326543) describes a similar problem and setting `NullValueHandling` to `NullValueHandling.Ignore` seems to be the solution. Wonder what's different in your case.... – Srikanth Venugopalan Jan 07 '14 at 01:50
  • Can you paste the relevant exception info (message, stack trace) here? – sinelaw Jan 07 '14 at 02:09
  • @BrianRogers Yup. That's true. – Ev. Jan 07 '14 at 02:19
  • @SrikanthVenugopalan my case is a little different, as the value of the property is null in that one. In mine, the property throws an excpetion. – Ev. Jan 07 '14 at 02:20
  • @sinelaw. It's sensitive information, but I'll add some more info to the question. Just need a minute to write it up.... shouldn't take long. – Ev. Jan 07 '14 at 02:21
  • @sinelaw and it's up. Let me know what you think. To re-iterate, I'm happy to use a different serializer if a different one works. – Ev. Jan 07 '14 at 02:39
  • If you have multiple instances of a Class, some of which may (for some unknown arcane reason) throw an exception when you access a property, then it sounds like you have bigger issues to deal with before you worry about converting to JSON. If you can predict when it will throw an exception you could build a wrapper to protect against the exception and have it return null instead so the default `NullValueHandling` rule works. – Timothy Walters Jan 07 '14 at 04:56
  • 1
    I was searching for a solution for another problem: why I get JSonSerilizationError. You solved it for me: "one of who's members throws a NullReferenceException." Thanks – Andreas Nov 12 '14 at 08:59

2 Answers2

58

A simpler way to ignore errors:

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

or

settings.Error = (serializer,err) => err.ErrorContext.Handled = true;
noamtcohen
  • 4,432
  • 2
  • 20
  • 14
  • 7
    Get an error of *any sort*? Just pretend you didn't get an error! What could possibly go wrong? – EKW Nov 04 '15 at 22:43
  • 13
    I would normally agree with you, but I think this is a judgement call based on the situation. I'm trying to dump objects to disk so I can compare them easily and I really don't feel like spending the next few hours trying to get a custom parser working for some code that I'm only using for troubleshooting purposes. The object I'm working with is in a library and I can't change how it works. If I call it one way, one set of properties are invalid and if I call it another, a different set are invalid. So its either this or custom parser. – Mike Taber Mar 15 '16 at 02:46
  • @MikeTaber I agree that this is a hack you might need to use if you have no choice but to write bad code as a workaround for some bad code you don't control. – EKW May 08 '17 at 07:43
  • Works like a charm. Thank you. – Sebastián Guerrero May 10 '18 at 18:55
  • Nice solution. THX! @EKW: There are in fact situations where you just want to get out as much information as possible without fiddling with exceptions. For me it was an API endpoint for testing that dumps all sort of request info and server settings for further analysis. No production ready code, but very much needed to __plan and design__ the final code. – Jpsy Oct 11 '18 at 10:03
  • @Jpsy Bad code is bad code. There are reasons to write bad code when you have to, but this is unambiguously bad, and people will ABSOLUTELY use it in production systems. – EKW Oct 14 '18 at 06:48
  • 5
    I've found this useful for arbitrary logging of objects, where I don't care if something fails serialisation. – Alex Logan May 14 '19 at 14:14
  • Please be aware of this code. We did this in production for logging purposes and get OutOfMemory because of circular dependencies in DBEntityValidationException – AlexSolovyov Jan 31 '20 at 09:33
34

If you don't control the source code, you can use a custom ContractResolver to inject a "ShouldSerialize" method for the problematic property during serialization. You can have that method always return false, or optionally, implement some logic which will detect the situations where the property will throw and return false only in that case.

For example, let's say that your class looks like this:

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

Clearly, if we try to serialize the above, it will not work because the Offender property will always throw an exception when the serializer tries to access it. Since we know the class and property name that causes the problem, we can write a custom ContractResolver (derived from the DefaultContractResolver) to suppress the serialization of that specific member.

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

Here's a demo showing how to use it:

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

Output:

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

A more generic solution

In your comments you indicated that you have many kinds of objects that might throw an exception when any of the properties are accessed. To that end, we need something more generic. Here is a resolver that might work for that case, but you'll need to test it extensively in your own environment. It does not depend on any particular class or property name, but creates a ShouldSerialize predicate for every property that comes its way. In that predicate it uses reflection to get the value of the property inside a try/catch; if successful it returns true, otherwise 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;
    }
}

Here is a 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";
        }
    }
}

Output:

[
  {
    "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
  }
]
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 1
    Very cool! I like it. The only problem is there could be multiple different classes that will be serialized that could throw an exception. So that only way to detect if the property is going to throw an exception is to try...catch it. Can I do this in your solution? – Ev. Jan 07 '14 at 02:55
  • Can you give an example of what you mean? Are you saying that the Offender property could contain an instance of any of several objects, and some throw while others don't? – Brian Rogers Jan 07 '14 at 03:08
  • I have lots of different objects I'd like to serialize. So there might be an exception thrown in any of them. That means I can't say: "if type of object is XXX and name of member is YYY then don't serialize." instead I have to say "try to get the value of the current member to be serialized. If it throws an exception, don't serialize it. If it doesn't throw, do serialize" – Ev. Jan 07 '14 at 03:13
  • protected override JsonProperty CreateProperty() { JsonProperty property = base.CreateProperty(member, memberSerialization); try { base.CreateMemberValueProvider(member).GetValue(property); } catch(Exception ex) { property.ShouldSerialize = instanceOfProblematic => false; } return property; } – Ev. Jan 07 '14 at 03:17
  • The above isn't pretty (and doesn't work) but that's roughly what I'm thinking. – Ev. Jan 07 '14 at 03:17
  • So you're saying that any property of any object could potentially throw, and you want a generic solution that does try/catch on everything? – Brian Rogers Jan 07 '14 at 03:27
  • That would be ideal. Thank you for the help. – Ev. Jan 07 '14 at 03:35
  • 2
    OK, I've got something that appears to work (see updated answer). Your mileage may vary. – Brian Rogers Jan 07 '14 at 05:02
  • 1
    Woah, you're incredible. Thank you so much! It works great. Now my only concern is that when people look at my code they'll think I'm much smarter than I really am. I'll add a comment in the code linking back to this post so there's no confusion. Thanks man! – Ev. Jan 07 '14 at 05:28