Last active
April 18, 2018 03:38
-
-
Save andrewjw1995/94794ccfeedeccf8a521ef4f2d644ca6 to your computer and use it in GitHub Desktop.
A JsonConverter that allows implementations for an interface or abstract class to be chosen based on a discriminating property
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Collections.Generic; | |
namespace Utilities | |
{ | |
/// <summary> | |
/// Allows interfaces to be deserialized by creating a concrete implementation, based on some discriminating key in | |
/// the interface. | |
/// </summary> | |
/// <typeparam name="TInterface">The interface type</typeparam> | |
/// <typeparam name="TKey">The key type</typeparam> | |
public class TaggedUnionConverter<TInterface, TKey> : JsonConverter | |
{ | |
/// <summary> | |
/// The field in the interface that defines which implementation to use. | |
/// </summary> | |
public string Discriminator { get; set; } | |
/// <summary> | |
/// A mapping of keys to concrete implementations of <see cref="TInterface"/>. | |
/// </summary> | |
public IDictionary<TKey, Type> Implementations { get; set; } | |
/// <summary> | |
/// Gets a value indicating whether this Newtonsoft.Json.JsonConverter can read JSON. | |
/// </summary> | |
public override bool CanRead => true; | |
/// <summary> | |
/// Gets a value indicating whether this Newtonsoft.Json.JsonConverter can write JSON. | |
/// </summary> | |
public override bool CanWrite => false; | |
/// <summary> | |
/// Constructs a new converter, using the given discriminator and implementations. | |
/// </summary> | |
/// <param name="discriminator">The field in the interface that defines which implementation to use.</param> | |
/// <param name="implementations">A mapping of keys to concrete implementations of <see cref="TInterface"/>.</param> | |
public TaggedUnionConverter(string discriminator, IDictionary<TKey, Type> implementations) | |
{ | |
Discriminator = discriminator ?? throw new ArgumentNullException(nameof(discriminator)); | |
Implementations = implementations ?? throw new ArgumentNullException(nameof(implementations)); | |
} | |
/// <summary> | |
/// Determines whether this instance can convert the specified object type. | |
/// </summary> | |
/// <param name="objectType">The object type</param> | |
/// <returns>true if this instance can convert the specified object type; otherwise, false.</returns> | |
public override bool CanConvert(Type objectType) | |
{ | |
return objectType == typeof(TInterface); | |
} | |
/// <summary> | |
/// Reads the JSON representation of the object, using the discriminator to determine the appropriate type. If | |
/// the discriminator is not present, or the value cannot be converted to the key type, or the value is not | |
/// contained in the set of implementations, an exception will be thrown, and the target object will be left | |
/// with its default value. | |
/// </summary> | |
/// <param name="reader">The Newtonsoft.Json.JsonReader to read from.</param> | |
/// <param name="objectType">Type of the object.</param> | |
/// <param name="existingValue">The existing value of object being read.</param> | |
/// <param name="serializer">The calling serializer.</param> | |
/// <returns>The object value.</returns> | |
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |
{ | |
if (reader.TokenType == JsonToken.Null) | |
{ | |
reader.Read(); | |
return null; | |
} | |
var jsonObject = JObject.Load(reader); | |
if (!jsonObject.TryGetValue(Discriminator, out var discriminator)) | |
throw new InvalidOperationException("The model does not contain a value for " + Discriminator + ", so the type cannot be determined"); | |
var value = discriminator.ToObject<TKey>(serializer); | |
if (value == null) | |
throw new InvalidOperationException("The value '" + discriminator + "' is not a valid '" + typeof(TKey).Name + "', so the type cannot be determined"); | |
if (!Implementations.TryGetValue(value, out var type)) | |
throw new InvalidOperationException("The value '" + discriminator + "' is not a valid value for '" + Discriminator + "', so the type cannot be determined"); | |
return jsonObject.ToObject(type, serializer); | |
} | |
/// <summary> | |
/// Writes the JSON representation of the object. Calling this method will result in an exception, as it is not | |
/// a valid operation. | |
/// </summary> | |
/// <param name="writer">The Newtonsoft.Json.JsonWriter to write to.</param> | |
/// <param name="value">The value.</param> | |
/// <param name="serializer">The calling serializer.</param> | |
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |
{ | |
throw new InvalidOperationException("Use default serialization."); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment