MetaGuru
MetaGuru

Reputation: 43833

How to default a null JSON property to an empty array during serialization with a List<T> property in JSON.NET?

Currently I have JSON that either comes in via an HTTP call or is stored in a database but during server processing they are mapped to C# objects.

These objects have properties like public List<int> MyArray.

When the JSON contains MyArray:null I want the resulting property to be an empty List<T> instead of a null List<T> property.

The goal is that the object will "reserialize" to JSON as MyArray:[], thus either saving to the database or responding out via HTTP as an empty array instead of null.

That way, no matter what, the C# class is basically scrubbing and enforcing an empty array for any List<T> property that would otherwise be null and cause things to break in the browser side code (for example: cannot read property 'length' of null).

Is there a way that during the serialization/deserialization I can have any null value that is paired to a List<T> property become an empty array instead?

Upvotes: 10

Views: 33403

Answers (4)

Brian Rogers
Brian Rogers

Reputation: 129697

I was going to suggest using a custom JsonConverter to solve this, but a converter will not get called for null values. Instead, you will need to use a custom IContractResolver in combination with a custom IValueProvider. Here is the code you would need (inspired by this answer):

class NullToEmptyListResolver : DefaultContractResolver
{
    protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
    {
        IValueProvider provider = base.CreateMemberValueProvider(member);

        if (member.MemberType == MemberTypes.Property)
        {
            Type propType = ((PropertyInfo)member).PropertyType;
            if (propType.IsGenericType && 
                propType.GetGenericTypeDefinition() == typeof(List<>))
            {
                return new EmptyListValueProvider(provider, propType);
            }
        }

        return provider;
    }

    class EmptyListValueProvider : IValueProvider
    {
        private IValueProvider innerProvider;
        private object defaultValue;

        public EmptyListValueProvider(IValueProvider innerProvider, Type listType)
        {
            this.innerProvider = innerProvider;
            defaultValue = Activator.CreateInstance(listType);
        }

        public void SetValue(object target, object value)
        {
            innerProvider.SetValue(target, value ?? defaultValue);
        }

        public object GetValue(object target)
        {
            return innerProvider.GetValue(target) ?? defaultValue;
        }
    }
}

Here is a demo which shows how to use the resolver:

class Program
{
    static void Main(string[] args)
    {
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new NullToEmptyListResolver();
        settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
        settings.Formatting = Formatting.Indented;

        Console.WriteLine("Serializing object with null lists...");
        Foo foo = new Foo();
        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
        Console.WriteLine();

        Console.WriteLine("Deserializing JSON with null lists...");
        json = @"{ ""IntList"" : null, ""StringList"" : null }";
        foo = JsonConvert.DeserializeObject<Foo>(json, settings);
        Console.WriteLine("IntList size: " + foo.IntList.Count);
        Console.WriteLine("StringList size: " + foo.StringList.Count);
    }
}

class Foo
{
    public List<int> IntList { get; set; }
    public List<string> StringList { get; set; }
}

Output:

Serializing object with null lists...
{
  "IntList": [],
  "StringList": []
}

Deserializing JSON with null lists...
IntList size: 0
StringList size: 0

Upvotes: 9

Ed&#39;ka
Ed&#39;ka

Reputation: 7140

The following property will have empty collection assigned to it after deserialization instead of null in both cases: when the property is omitted in JSON or when set to null explicitly:

class A
{
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public IEnumerable<int> Prop { get; set; } = new List<int>();
}

Upvotes: 9

Dan Pettersson
Dan Pettersson

Reputation: 743

To use Brian Rogers solution in a .net core solution you need a slight modification to access the "IsGenericType" property as it moved from Type to TypeInfo.

The answer marked as correct (setting NullValueHandling) was not working for me, it just ignores the property if it is null.

Full code for .net core:

using System;
using System.Collections.Generic;
using Newtonsoft.Json.Serialization;
using System.Reflection;

public class NullToEmptyListResolver : DefaultContractResolver
{
    protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
    {
        IValueProvider provider = base.CreateMemberValueProvider(member);

        if (member.MemberType == MemberTypes.Property)
        {
            Type propType = ((PropertyInfo)member).PropertyType;
            TypeInfo propTypeInfo = propType.GetTypeInfo();
            if (propTypeInfo.IsGenericType &&
                propType.GetGenericTypeDefinition() == typeof(List<>))
            {
                return new EmptyListValueProvider(provider, propType);
            }
        }

        return provider;
    }

    class EmptyListValueProvider : IValueProvider
    {
        private IValueProvider innerProvider;
        private object defaultValue;

        public EmptyListValueProvider(IValueProvider innerProvider, Type listType)
        {
            this.innerProvider = innerProvider;
            defaultValue = Activator.CreateInstance(listType);
        }

        public void SetValue(object target, object value)
        {
            innerProvider.SetValue(target, value ?? defaultValue);
        }

        public object GetValue(object target)
        {
            return innerProvider.GetValue(target) ?? defaultValue;
        }
    }
}

Upvotes: 3

jtimperley
jtimperley

Reputation: 2544

You could always lazy load an empty list if its null.

OR

Use the NullValueHandling option on the JsonDeserializer.

var settings = new JsonSerializerSettings();
settings.NullValueHandling = NullValueHandling.Ignore;

return JsonConvert.DeserializeObject<T>(json, settings);

http://james.newtonking.com/json/help/index.html?topic=html/SerializationSettings.htm

Upvotes: 12

Related Questions