0

Summary

Is it possible to use Newtonsoft.Json.NullValueHandling.Ignore for output (serialization) but use NullValueHandling.Include for input (deserialization)?


Details

I'm building a JSON API using ASP.NET Web API. I want to remove unnecessary null values from the output which is easy to do with the following code:

config.Formatters.JsonFormatter.SerializerSettings =
    new Newtonsoft.Json.JsonSerializerSettings {
        NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
    };

However, on input, I want to use NullValueHandling.Include to include null values and have that trigger property setters on my model objects.

Why?

These property setters look like the following:

public long? Foo
{
    get { return _Foo; }
    set { _Foo = MarkApplied(nameof(Foo), value); }
}

where MarkApplied remembers that the Foo property was explicity set. This allows me to tell the difference between an input JSON object of:

{ Foo: 123, Bar: "bar" }

which intends for me to update the Foo property to 123 and the Bar property to "bar",

{ Foo: null, Bar: "bar" }

which intends for me to update the Foo property to null and the Bar property to "bar", and

{ Bar: "bar" }

which intends for me to update the Bar property to "bar" and not change anything about the Foo property (as opposed to setting it to null).

What won't work

I've already looked at DefaultValueHandling as an option. For one, I really only want this behavior for null values, not for numerics and booleans which have meaningful default values. Also, Include and Ignore seems to pretty much match the behavior of NullValueHandling, and the Populate and IgnoreAndPopulate options aren't what I want, because they would trigger the property setters even when the property isn't included in the input JSON.

1 Answers1

0

Ok, so I found this StackOverflow post, and was able to create a custom JsonConverter that gets the job done:

    public class V2JsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType.IsSubclassOf(typeof(ApiModelBase));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jsonObj = JObject.Load(reader);
            var targetObj = (ApiModelBase)Activator.CreateInstance(objectType);

            foreach (var prop in objectType.GetProperties().Where(p => p.CanWrite))
            {
                JToken value;
                if (jsonObj.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out value))
                {
                    prop.SetValue(targetObj, value.ToObject(prop.PropertyType, serializer));
                }
            }

            return targetObj;
        }

        public override bool CanWrite => false;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }