2

I have a generic type Container<IContentType> where interface IContentType can be one of four concrete ContentX types.

I serialize and everything is fine.

When deserializing using Newtonsoft I use custom type converters and var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings) works. The debugger shows I have an Container<ContentA> object.

My plan was when deserializing to attempt a deserialization for each of the four possible ContentX types and catch an exception silently until I "guess" the right one.

However, if I do this within a method like so:

public static Container<IContentType> Deserialize(jsonfile)
{
    ...
    var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings)
    return model;
}

I get "Cannot implicitly convert Container<ContentA> to Container<IContentType>". ContentA implements IContentType.

Is there a way I can create a cast operator, conversion, dynamic or make the implicit conversion work?

7
  • Why not just JsonConvert.DeserializeObject<Container<ContentA>>(json, settings) as Container<IContentType>? Commented Oct 12, 2017 at 17:02
  • 1
    You've run into a variance issue. For example, you can't cast List<String> to List<Object>, even though string is a subclass of object, because then the List<Object> reference would have a method Add(Object), which wouldn't be valid for the actual List<String> that the reference "points" to. Commented Oct 12, 2017 at 17:03
  • 2
    You will need to create a custom JsonConverter for IContentType that infers the correct concrete type along the lines of How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? or Deserializing polymorphic json classes without type information using json.net or Json.Net Serialization of Type with Polymorphic Child Object. Then you can just deserialize to Container<IContentType>. Commented Oct 12, 2017 at 17:03
  • 1
    This is covariance in action. I think if you change the signature of the generic class Container<T> accordingly you'll be okay. Commented Oct 12, 2017 at 17:04
  • 1
    @dbc I am using the techniques you point to but you may have discovered an error in my implementation. During type resolution in the JsonConverter I am resolving to a Container<ContentA> and populating Container with those types. I will try instead to resolve to Conatiner<IContentType> but still populate with ContentA's... Commented Oct 12, 2017 at 18:11

1 Answer 1

3

Rather than trying to deserialize as a Container<ContentX> for concrete type(s) X, you should deserialize as a Container<IContentType> using a custom JsonConverter that pre-load the JSON into a JToken and infers the concrete type along the lines of How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? or Deserializing polymorphic json classes without type information using json.net or Json.Net Serialization of Type with Polymorphic Child Object.

Thus your converter would look something like:

public class ContentConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IContentType);
    }

    Type GetConcreteType(JObject obj)
    {
        if (obj.GetValue(nameof(ContentA.SomePropertyOfContentA), StringComparison.OrdinalIgnoreCase) != null)
            return typeof(ContentA);
        // Add other tests for other content types.
        // Return a default type or throw an exception if a unique type cannot be found.
        throw new JsonSerializationException("Cannot determine concrete type for IContentType");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var obj = JObject.Load(reader);
        var concreteType = GetConcreteType(obj);
        return obj.ToObject(concreteType, serializer);
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And your call to JsonConvert would look like:

var settings = new JsonSerializerSettings
{
    Converters = { new ContentConverter() },
};
var model = JsonConvert.DeserializeObject<Container<IContentType>>(json, settings);

Finally, you might be able to choose the type entirely automatically using

new JsonDerivedTypeConverer<IContentType>(typeof(ContentA), typeof(ContentB), typeof(ContentC), typeof(ContentD))

Where JsonDerivedTypeConverer<T> is taken from JsonConverter with Interface.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.