1

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);
dbc
  • 104,963
  • 20
  • 228
  • 340
SYL
  • 155
  • 1
  • 14
  • 3
    can you please be more specific? what does not work? is it not compiling? is it compiling but throwing run time error? is it compiling but the attribute is not working? – jmesolomon Nov 15 '16 at 21:53
  • 1
    You want to do X, you think Y (above question) is the way to do it. But Y doesn't work. We don't have enough info to guess what you want to to do with Y , and no idea what X is... – L.B Nov 15 '16 at 22:13
  • The error is unable to cast object of type Dictionary to Dictionary. – SYL Nov 15 '16 at 23:19
  • @SusanLee Just look at the code what you've posted so far plus the error message... And now assume you see this question at SO and want to answer it. All you know is above.. What additional info would you need? – L.B Nov 15 '16 at 23:36
  • Instead of what I have how do you make the declaration such that it will not cause this error and will correctly deserialize? – SYL Nov 16 '16 at 01:12

1 Answers1

5

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Awesome! Thank you! – SYL Nov 16 '16 at 22:18
  • I have a more complex scenario here. https://dotnetfiddle.net/JSoAug Can you please take a look? – SYL Nov 17 '16 at 00:19
  • @SusanLee - that's complicated enough that you might want to ask another question, as the preferred format on stackoverflow is [one question per post](https://meta.stackexchange.com/questions/222735/can-i-ask-only-one-question-per-post). Did you try `TypeConverter` as well as `TypeConverter`? – dbc Nov 17 '16 at 01:04
  • Yes, I have tried that. Created new question for this here http://stackoverflow.com/questions/40663658/deserializing-complex-nested-dictionary-type-using-json-net – SYL Nov 17 '16 at 19:44