Overriding the default serialization behavior in Json.NET

We use Newtonsoft Json.NET heavily in our project to control our JSON serialization. For the most part, it works absolutely perfectly out-of-the-box, but every now and then, we need to customize the way that serialization works for a particular object.

Such an occasion occurred today. Basically, I have a list of EmployeeActivity objects – where each instance is a subclass of EmployeeActivity. So, for example:

public abstract class EmployeeActivity
{
    public TimeSpan Start { get; set; }
    public TimeSpan Stop { get; set; }

    public virtual bool IsAvailable
    {
        get { return false; }
    }
}

public class AbsenceActivity : EmployeeActivity
{
    public string Reason { get; set; }
}

public class BreakActivity : EmployeeActivity
{
    public string BreakName { get; set; }
}

json.NET does a perfect job of serializing my objects, and they rebuild perfectly when I’m back in javascript land – however, I have no way of knowing the actual type of each activity in my array! To alleviate this, I wanted to create my own serializer which also injected the type name into each of the serialized objects – or at least, that’s what I thought I wanted to do!

As it turns out, there’s a bit of an easier option. JsonConvert uses a ContractResolver in order to work out how to Serialize/Deserialize each class, and you can override the default contract resolver when running serialization:

// This class contains the activity list
ActivityViewModel = new ActivityViewModel(DateTime.Today);

// Serialize using a custom contract resolver
string jsonViewModel = JsonConvert.SerializeObject(viewModel,
    Formatting.None, new JsonSerializerSettings
    {
        ContractResolver = new ActivityJsonContractResolver()
    });

The contract resolver’s job is to return a JsonContract which describes the object to be returned. For a POCO, this would typically be an instance of JsonObjectContract, which (amongst other things) describes the properties of the class which should be serialized.

So, all I needed to do was to extend the contract for subclasses of EmployeeActivity, such that it inherited a new Property to be serialized:

public class DiaryActivityJsonContractResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        if (typeof(EmployeeActivity).IsAssignableFrom(objectType))
        {
            contract.Properties.Add(new JsonProperty
            {
                Readable = true,
                ShouldSerialize = value => true,
                PropertyName = "Type",
                PropertyType = typeof(string),
                Converter = ResolveContractConverter(typeof(string)),
                ValueProvider = new StaticValueProvider(objectType.Name)
            });
        }

        return contract;
    }
}

Truth be told, I’m not 100% sure if I need to implement as many of the properties on the JsonProperty that I’m creating. But you can see what’s going on here – I’m effectively telling the object contract that there’s an additional property called Type which needs to be serialized, that it’s a string, and that it should return whatever value is resolved by the StaticValueProvider. Speaking of which, here’s the last piece of the puzzle – a simple value resolver that always returns the same value:

/// <summary>
/// JSON value provider that always returns a static value
/// </summary>
public class StaticValueProvider : IValueProvider
{
    private readonly object _staticValue;

    public StaticValueProvider(object staticValue)
    {
        _staticValue = staticValue;
    }

    public void SetValue(object target, object value)
    {
        throw new NotSupportedException();
    }

    public object GetValue(object target)
    {
        return _staticValue;
    }
}

json.NET is fantastic, and it’s very extensible, but finding out where to start and what to do can be a little bit tricky. I managed to figure this out only by downloading the source files and poking through the code myself; hopefully this will be helpful to someone else in the same situation!

Posted in Asp.Net, C# by Gerrod at August 8th, 2011.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Quickduck logo