Reputation: 155
Given these class definitions:
public class TypeConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => serializer.Serialize(writer, value);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => serializer.Deserialize<T>(reader);
}
public interface ISubStuff
{
string Item { get; set; }
}
public class SubStuff : ISubStuff
{
public string Item { get; set; }
}
public interface IMainStuff
{
Dictionary<string, ISubStuff> SubStuff { get; set; }
}
I am trying to use the TypeConverter class in method declaration for deserialization as below but it's NOT working:
public class MainStuff : IMainStuff
{
[JsonConverter(typeof(TypeConverter<Dictionary<string, SubStuff>>))]
public Dictionary<string, ISubStuff> SubStuff
{
get;
set;
}
}
The call below to deserialize the json causes an unable to cast object of type Dictionary<string, SubStuff> to Dictionary<string, ISubStuff>
exception.
var jsonText = "{ \"SubStuff\": { } }";
var deser = JsonConvert.DeserializeObject<MainStuff><jsonText);
Upvotes: 1
Views: 2040
Reputation: 117026
Your problem is that the c# Dictionary<TKey, TValue>
is not covariant. I.e., even though SubStuff
is an ISubStuff
, Dictionary<string, SubStuff>
is not a Dictionary<string, ISubStuff>
. Thus when Json.NET tries to set the Dictionary<string, SubStuff>
back into the MainStuff.SubStuff
property, an InvalidCastException
is thrown.
For a general explanation of why read/write collections are not covariant, see this answer. Its discussion of the lack of covariance of List<T>
applies equally to generic dictionaries.
What you can do is use JsonProperty.ItemConverterType
to apply your TypeConverter<T>
only to the values in the dictionary, like so:
public class MainStuff : IMainStuff
{
[JsonProperty(ItemConverterType = typeof(TypeConverter<SubStuff>))]
public Dictionary<string, ISubStuff> SubStuff
{
get;
set;
}
}
public class TypeConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var msg = string.Format("This converter should be applied directly with [JsonProperty(ItemConverterType = typeof(TypeConverter<{0}>))] or [JsonProperty(typeof(TypeConverter<{0}>))]",
typeof(T));
throw new NotImplementedException(msg);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<T>(reader);
}
}
Sample fiddle.
Incidentally, rather than inheriting from JsonConverter
, you could inherit from CustomCreationConverter<>
instead:
public class MainStuff : IMainStuff
{
[JsonProperty(ItemConverterType = typeof(TypeConverter<ISubStuff, SubStuff>))]
public Dictionary<string, ISubStuff> SubStuff
{
get;
set;
}
}
public class TypeConverter<T, TSerialized> : CustomCreationConverter<T>
where TSerialized : T, new()
{
public override T Create(Type objectType)
{
return new TSerialized();
}
}
Sample fiddle #2.
Finally, as an alternative, you could investigate use of the TypeNameHandling
setting.
Upvotes: 5