cft

Enumeration class and JSON Serialization

This is the second post in the Series: Enumeration classes – DDD and beyond. If you have jumped here right in and are new to the Enumeration classes, I suggest going through the previous post first.


user

Ankit Vijay

2 years ago | 6 min read

This is the second post in the Series: Enumeration classes – DDD and beyond. If you have jumped here right in and are new to the Enumeration classes, I suggest going through the previous post first.

  • Part 1: Introduction to Enumeration Classes
  • Part 2: Enumeration class and JSON Serialization (this post)
  • Part 3: Enumeration class as query string parameter
  • Part 4: Generating client code with NSwag for Enumeration class
  • Part 5: Implementing Inheritance with Enumeration class

In part 1, I gave an introduction to Enumeration class and what problem it solves. In this and upcoming posts, I will explain how we can use Enumeration class for advanced scenarios. This post would cover how to we can serialize an Enumeration class.

A disclaimer before I go further:

While the Enumeration class is an excellent alternate to an Enum, it brings along a fair deal of complexity. Enumeration class solves specific-business scenarios and may not be fit for general purposes. Please evaluate if it suits your needs before adopting the Enumeration class.

NUGET AND SOURCE CODE

The Enumeration class and other dependent classes are available as the NuGet packages. You can find the source code for the series at this GitHub link.

Why serialize an Enumeration Class?

There can be a few reasons you may need to serialize and deserialize an Enumeration class, such as:

  • Saving and retrieving your domain object in No-SQL DBs like Cosmos, Raven DB, etc.
  • Using Enumeration class in the request body and response of a Web API.
  • Publishing and retrieving an object with Enumeration class from a message bus.

Let us go back to our PaymentType example from our last post.

public abstract class PaymentType : Enumeration
{
public static readonly PaymentType DebitCard = new DebitCardType();

public static readonly PaymentType CreditCard = new CreditCardType();

public abstract string Code { get; }

private PaymentType(int value, string name = null) : base(value, name)
{
}

private class DebitCardType : PaymentType
{
public DebitCardType() : base(0, "DebitCard")
{
}

public override string Code => "DC";
}

private class CreditCardType : PaymentType
{
public CreditCardType() : base(1, "CreditCard")
{
}

public override string Code => "CC";
}
}

Unlike an Enum, PaymentType is a class with static readonly members. We would need a custom logic or converter to serialize it to JSON and deserialize it back.

Version 1 – Using System.Text.Json

We can extend System.Text.Json –>JsonConverter of to serialize and deserialize an Enumeration class.

public abstract class PaymentType : Enumeration
{
public static readonly PaymentType DebitCard = new DebitCardType();

public static readonly PaymentType CreditCard = new CreditCardType();

public abstract string Code { get; }

private PaymentType(int value, string name = null) : base(value, name)
{
}

private class DebitCardType : PaymentType
{
public DebitCardType() : base(0, "DebitCard")
{
}

public override string Code => "DC";
}

private class CreditCardType : PaymentType
{
public CreditCardType() : base(1, "CreditCard")
{
}

public override string Code => "CC";
}
}

Version 2 – Using Newtonsoft.Json

Unfortunately, System.Text.Json has still not reached feature parity to Newtonsoft.Json. As a result, so often, we fall back to Newtonsoft.Json.

Here is the Newtonsoft.Json version of JsonConverter

// Import Newtonsoft.Json

public class EnumerationJsonConverter : JsonConverter<Enumeration>
{
public override void WriteJson(JsonWriter writer, Enumeration value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
}
else
{
writer.WriteValue(value.Name);
}
}

public override Enumeration ReadJson(JsonReader reader,
Type objectType,
Enumeration existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
return reader.TokenType switch
{
JsonToken.Integer => GetEnumerationFromJson(reader.Value.ToString(), objectType),
JsonToken.String => GetEnumerationFromJson(reader.Value.ToString(), objectType),
JsonToken.Null => null,

_ => throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing an enumeration")
};
}

private static Enumeration GetEnumerationFromJson(string nameOrValue, Type objectType)
{
try
{
object result = default;
var methodInfo = typeof(Enumeration).GetMethod(
nameof(Enumeration.TryGetFromValueOrName)
, BindingFlags.Static | BindingFlags.Public);

if (methodInfo == null)
{
throw new JsonSerializationException("Serialization is not supported");
}

var genericMethod = methodInfo.MakeGenericMethod(objectType);

var arguments = new[] { nameOrValue, result };

genericMethod.Invoke(null, arguments);
return arguments[1] as Enumeration;
}
catch (Exception ex)
{
throw new JsonSerializationException($"Error converting value '{nameOrValue}' to a enumeration.", ex);
}
}
}

Usage

Let us consider a class Transaction with property PaymentType.

public class Transaction
{
public double Amount { get; set; }

public PaymentType PaymentType { get; set; }
}

We can serialize and serialize the Transaction class using EnumerationJsonConverter, as shown in the below test:

// Import System.Text.Json;

public class EnumerationJsonConverterTests
{
private readonly ITestOutputHelper _testOutputHelper;

public EnumerationJsonConverterTests(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}

[Fact]
public void EnumerationIsSerializesAndDeserializesCorrectly()
{
var expected = new Transaction
{
Amount = 100,
PaymentType = PaymentType.CreditCard
};

var json = JsonSerializer.Serialize(expected,
new JsonSerializerOptions
{
Converters =
{
new EnumerationJsonConverter()
}
});

_testOutputHelper.WriteLine(json);

var actual= JsonSerializer.Deserialize<Transaction>(json, new JsonSerializerOptions()
{
Converters = { new EnumerationJsonConverter() }
});

Assert.Equal(expected.Amount, actual.Amount);
Assert.Equal(expected.PaymentType, actual.PaymentType);
}
}

Here is the JSON output:

{
"Amount":100,
"PaymentType":"CreditCard"
}

I hope you enjoyed this post and learning about how you can create a custom JSON converter to serialize an Enumeration class.

Upvote


user
Created by

Ankit Vijay

Hi... I’m Ankit Vijay. I hold around 14 years of experience in application development & consulting. I’m a Dotnet Foundation member. I have worked in various roles ranging from Individual Contributor, DevOps, Solution Architect, Consultant, and Dev Lead depending on the nature of the project. I am passionate about technology and write about the topics I love. If you like my blogs, you can follow me on Twitter @ https://twitter.com/vijayankit or GitHub @ https://github.com/ankitvijay


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles