Serializing Custom Enumeration With Json.NET

When I went to Codemash a few months ago I watched Jimmy Bogard’s Crafting Wicked Domain Models talk. One of the interesting things in his talk was his implementation of a Java like enum. It was something I had seen before with varying implementations but I had never felt really compelled to switch to. What changed my mind was Jon Skeet’s talk on C# Greatest Mistakes where he showed several problems with the C# implementation of enums. After that I decided to start using Jimmy’s implementation full time.

Last week I came across a serialization issue using Json.Net with it though. Look at this sample implementation:

public class ColorEnum : Enumeration
{
    public static readonly ColorEnum Black = new ColorEnum(1,"Black");

    public static readonly ColorEnum White = new ColorEnum(2,"White");
    
    [JsonConstructor]
    private ColorEnum(int value, string displayName) : base(value, displayName)
    {
    }
}

The code for the Enumeration class can be found here: https://github.com/jbogard/presentations/blob/master/WickedDomainModels/After/Model/Enumeration.cs

First, notice that in order to be able to deserialize it properly I had to add the JsonConstructor attribute to the constructor. But that’s not my issue yet.

My first issue is that the deserialized object will be a new instance of my ColorEnum item. That’s not bad as the Enumeration class is prepared to handle it by overriding the Equals method like this:

public override bool Equals(object obj)
{
    var otherValue = obj as Enumeration;

    if (otherValue == null)
    {
        return false;
    }

    var typeMatches = GetType().Equals(obj.GetType());
    var valueMatches = _value.Equals(otherValue.Value);

    return typeMatches && valueMatches;
}

It would still fail however if we did a comparison using the “==” operator. But that can still be fixed by overriding the operator in the Enumeration class like so:

public static bool operator ==(Enumeration left, Enumeration right)
{
    return Equals(left, right);
}

public static bool operator !=(Enumeration left, Enumeration right)
{
    return !Equals(left, right);
}

So if we can get away with multiple instances why would I want to use a single instance? Because I can :-)

The neat thing by using a single instance I can make the serialization look more like with the regular enum. So here what it would be like when serialized:

{"Color":{"Value":1,"DisplayName":"Black"}}

And this is how I’d like it to be:

{"Color":1}

So how can we achieve this? Json.Net custom converters.

Custom converters are not complicated to implement, the one I created to take care of serializing my enumeration is as follows:

public class EnumerationConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var enumeration = (Enumeration)value;
        serializer.Serialize(writer, enumeration.Value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        int value = serializer.Deserialize<int>(reader);
        foreach (Enumeration enumeration in Enumeration.GetAll(objectType))
        {
            if (enumeration.Value == value)
            {
                return enumeration;
            }
        }

        throw new Exception("Value not found in enumeration. Type:{0} Value:{1}".Frmt(objectType, value));
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsSubclassOf(typeof(Enumeration));
    }
}

During deserialization I use the GetAll method from the Enumeration class to retrieve all the enumerated items for the specific type and try to match it's values to the value being deserialized. With a few tests we can easily prove that we get our expected results:

[TestFixture]
public class EnumerationConverterTests
{
    [Test]
    public void Should_serialize_Enumeration_to_simplified_json()
    {
        var brush = new Brush {Color = ColorEnum.Black};
        string json = JsonConvert.SerializeObject(brush, new EnumerationConverter());
        Assert.AreEqual(@"{""Color"":1}", json);
    }

    [Test]
    public void Should_serialize_null_Enumeration()
    {
        var brush = new Brush();
        string json = JsonConvert.SerializeObject(brush, new EnumerationConverter());
        Assert.AreEqual(@"{""Color"":null}", json);
    }

    [Test]
    public void Should_deserialize_Enumeration()
    {
        string json = @"{""Color"":1}";
        var deserializeObject = JsonConvert.DeserializeObject<Brush>(json, new EnumerationConverter());
        Assert.AreEqual(ColorEnum.Black, deserializeObject.Color);
    }

    [Test]
    public void Should_deserialize_null_Enumeration()
    {
        string json = @"{""Color"":null}";
        var deserializeObject = JsonConvert.DeserializeObject<Brush>(json, new EnumerationConverter());
        Assert.IsNull(deserializeObject.Color);
    }

    public class Brush
    {
        public ColorEnum Color { get; set; } 
    }

    public class ColorEnum : Enumeration
    {
        public static readonly ColorEnum Black = new ColorEnum(1,"Black");

        public static readonly ColorEnum White = new ColorEnum(2,"White");
    
        [JsonConstructor]
        private ColorEnum(int value, string displayName) : base(value, displayName)
        {
        }
    }
}

I hope this was useful information. See you next time.

blog comments powered by Disqus