using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
public static void Main()
JsonTypeConverterProblem problem = new JsonTypeConverterProblem();
problem.ShowSerializationBug();
problem.DeserializationWorks();
public string Id { get; set; }
public A Child { get; set; }
public sealed class JsonTypeConverterProblem
public void ShowSerializationBug()
Child = new C() { Id = "bar" }
JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new TypeHintContractResolver();
string json = JsonConvert.SerializeObject(a, Formatting.Indented, jsonSettings);
Assert.Contains(@"""Target"": ""B""", json);
Assert.Contains(@"""Is"": ""C""", json);
public void DeserializationWorks()
JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new TypeHintContractResolver();
A a = JsonConvert.DeserializeObject<A>(json, jsonSettings);
Assert.IsType<C>(a.Child);
public class TypeHintValueProvider : IValueProvider
private readonly string _value;
public TypeHintValueProvider(string value)
public void SetValue(object target, object value)
public object GetValue(object target)
public class TypeHintContractResolver : DefaultContractResolver
public override JsonContract ResolveContract(Type type)
JsonContract contract = base.ResolveContract(type);
if ((contract is JsonObjectContract)
&& ((type == typeof(A)) || (type == typeof(B))) )
contract.Converter = new TypeHintJsonConverter(type);
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
IList<JsonProperty> result = base.CreateProperties(type, memberSerialization);
result.Add(CreateTypeHintProperty(type,"Hint", "A"));
else if (type == typeof(B))
result.Add(CreateTypeHintProperty(type,"Target", "B"));
else if (type == typeof(C))
result.Add(CreateTypeHintProperty(type,"Is", "C"));
private JsonProperty CreateTypeHintProperty(Type declaringType, string propertyName, string propertyValue)
JsonProperty property = new JsonProperty();
property.PropertyType = typeof(string);
property.DeclaringType = declaringType;
property.PropertyName = propertyName;
property.ValueProvider = new TypeHintValueProvider(propertyValue);
property.Readable = true;
property.Writable = false;
public class TypeHintJsonConverter : JsonConverter
private readonly Type _declaredType;
public TypeHintJsonConverter(Type declaredType)
_declaredType = declaredType;
public override bool CanConvert(Type objectType)
return objectType == _declaredType;
private Type TypeFromTypeHint(JObject jo)
if (new JValue("B").Equals(jo["Target"]))
else if (new JValue("A").Equals(jo["Hint"]))
else if (new JValue("C").Equals(jo["Is"]))
throw new ArgumentException("Type not recognized from JSON");
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (! CanConvert(objectType))
throw new InvalidOperationException("Can't convert declaredType " + objectType + "; expected " + _declaredType);
var jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.Null)
if (jToken.Type != JTokenType.Object)
throw new InvalidOperationException("Json: expected " + _declaredType + "; got " + jToken.Type);
JObject jObject = (JObject) jToken;
Type deserializingType = TypeFromTypeHint(jObject);
var target = Activator.CreateInstance(deserializingType);
serializer.Populate(jObject.CreateReader(), target);
public override bool CanWrite
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotSupportedException("TypeHintJsonConverter can be only used for deserializing");