Skip to content

Instantly share code, notes, and snippets.

@luisgoncalves
Created October 3, 2018 22:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luisgoncalves/5c6a5b39d80bf78155ae6a518dc60a24 to your computer and use it in GitHub Desktop.
Save luisgoncalves/5c6a5b39d80bf78155ae6a518dc60a24 to your computer and use it in GitHub Desktop.
A Json.NET converter that handles an hierarchy of types based on a custom discriminator property
class SubTypesConverter<T> : JsonConverter<T> where T: new()
{
[ThreadStatic]
private static bool isWriting;
private readonly string discriminatorName;
private readonly Dictionary<string, Func<T>> factories;
private readonly Dictionary<Type, string> discriminators;
public override bool CanRead => true;
public override bool CanWrite => !isWriting;
public SubTypesConverter(string discriminatorName)
{
this.discriminatorName = discriminatorName;
this.factories = new Dictionary<string, Func<T>>();
this.discriminators = new Dictionary<Type, string>();
var types = typeof(T).Assembly
.GetTypes()
.Where(t => typeof(T).IsAssignableFrom(t) && !t.IsAbstract);
foreach (var t in types)
{
var discriminator = this.GetDiscriminator(t);
this.factories.Add(discriminator, CreateFactory(t));
this.discriminators.Add(t, discriminator);
}
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (hasExistingValue)
{
throw new NotSupportedException($"{nameof(SubTypesConverter<T>)} does not allow reading into an existing instance");
}
var jsonObject = JObject.Load(reader);
var discriminator = jsonObject[this.discriminatorName].Value<string>();
var value = this.factories[discriminator]();
serializer.Populate(jsonObject.CreateReader(), value);
return value;
}
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
try
{
isWriting = true;
var jsonObject = JObject.FromObject(value, serializer);
jsonObject.AddFirst(new JProperty(this.discriminatorName, this.discriminators[value.GetType()]));
jsonObject.WriteTo(writer);
}
finally
{
isWriting = false;
}
}
protected virtual string GetDiscriminator(Type type)
{
return type.Name;
}
private static Func<T> CreateFactory(Type t)
{
var newExp = Expression.New(t.GetConstructor(Type.EmptyTypes));
return Expression.Lambda<Func<T>>(newExp).Compile();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment