I recently have to build a class which has a property of type object. This property can be one of three types based on another property in the same class.
public class Transaction
{
public Guid Id { get; set; }
public AccountType AccountType { get; set; }
public object Account { get; set; }
}
public enum AccountType
{
BankAccount,
BPay,
PayId
}
public class BankAccount
{
// Bank account properties here
}
public class BPay
{
// BPay properties here
}
public class PayId
{
// Pay ID properties here
}
Serialising Transaction class is fine and would just work. However, deserialising it would be a problem. For the Account property, it would not know which class to deserialise to.
To solve this, I have a custom JSON converter for this class where the class Account property will be deserialised to will be based on the AccountType property.
public class TransactionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(Transaction);
// False because we don't need to use this converter for serialisation
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jsonObject = JObject.Load(reader);
var accountType = jsonObject.GetValue("accountType", StringComparison.OrdinalIgnoreCase)?.ToObject<AccountType>();
object account = null;
if (accountType != null)
{
switch (accountType)
{
case AccountType.BankAccount:
account = jsonObject.GetValue("account", StringComparison.OrdinalIgnoreCase)?.ToObject<BankAccount>();
break;
case AccountType.BPay:
account = jsonObject.GetValue("account", StringComparison.OrdinalIgnoreCase)?.ToObject<BPay>();
break;
case AccountType.PayId:
account = jsonObject.GetValue("account", StringComparison.OrdinalIgnoreCase)?.ToObject<PayId>();
break;
default:
throw new ArgumentException($"Account type {accountType} is not supported");
}
}
return new Transaction
{
Id = jsonObject.GetValue("Id", StringComparison.OrdinalIgnoreCase)?.ToString(),
AccountType = accountType,
Account = account
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// This won't be used because we don't need to use this converter for serialisation
throw new NotImplementedException();
}
}
To use this custom converter when deserialising Transaction class, add JsonConverter attribute on the class.
[JsonConverter(typeof(TransactionConverter))]
public class Transaction
{
public Guid Id { get; set; }
public AccountType AccountType { get; set; }
public object Account { get; set; }
}