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.