Tuesday, March 3, 2015

JsonConverter that converts 'false' into null values

I'm deserializing JSON into C# classes and I ran into the problem that the returned JSON contains a property, let's say 'items', and in case there are no items found - it specifies 'false', otherwise an array of items.

So in case no items are found:
{
  "items" : false
}

Otherwise:
{
  "items" : [
    { 
      "quantity": 2,
      "name": "product"
    },
    { 
      "quantity": 2,
      "name": "product"
    }]
}

Default deserialization with JSON.Net doesn't seem to handle this very well. The obvious error message 'Cannot cast an instance of bool to a List of type 'item'' error is what I got.

So I wrote a custom converter that handles this case:


    using System;
    using System.Collections.Generic;

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// 
    /// This converter was created in order to cater with situations where
    /// the JSON indicated 'false' in case an object was not present in the JSON.
    /// 
    /// This works well for weakly typed JavaScript but not as much for C#.
    /// 
    /// Example - trying to parse this JSON into a person class:
    /// var person = {
    ///     name : 'John Doe',
    ///     childredn : false
    /// }
    /// 
    /// var anotherPerson = {
    ///     name : 'Jane Doe',
    ///     children : [
    ///         { 'name' : 'Janine Doe' }
    ///     ]
    /// }
    /// 
    /// This converter will return NULL in case 'false' is detected in code.
    /// 
    /// The underlying type instance - or NULL in case JSON states 'false'
    public class FalseReturnsNullConverter : JsonConverter where T : new()
    {
        /// 
        /// Serializes the instance as JSON
        /// 
        /// The JSON writer
        /// The value to be serialized
        /// The JSON serialized
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }

        /// 
        /// Reads the JSON and converts it into:
        /// - NULL in case value = 'false'
        /// - An instance of T in case 'false' was not found
        /// 
        /// The JsonReader instance
        /// The type of object
        /// The existing value of the object
        /// The JSON serializer
        /// The deserialized instance
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
            JsonSerializer serializer)
        {           
            // Detect if the underlying value is 'false' - note that in case
            // the reader finds the 'false' value - the underlying type of the reader.value object
            // is a bool
            var isNull = reader.Value is bool && (bool)reader.Value == false;

            // In case the value is boolean false - return null, otherwise deserialize as usual
            if (isNull)
            {
                return null;
            }

            // If not - business as usual
            return serializer.Deserialize(reader);
        }

        /// 
        /// Returns a value indicating whether the type can be converted using this 
        /// converter
        /// 
        /// The type of object which needs to be converted
        /// True in case the underlying type can be converted using this converter
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(T);
        }
    }
The only thing you need to do is place this attribute on top of the model so that JSON.Net knows to use this converter when deserializing this property:

[JsonConverter(typeof(FalseReturnsNullConverter>))]
public List offers { get; set; }

Easy enough and pretty elegant. It's generic too - so you should be able to use it with any underlying type.

No comments:

Post a Comment