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?

phil
  • 1,938
  • 4
  • 23
  • 33
  • Why not just `JsonConvert.DeserializeObject>(json, settings) as Container`? – Camilo Terevinto Oct 12 '17 at 17:02
  • 1
    You've run into a variance issue. For example, you can't cast `List` to `List`, even though `string` is a subclass of `object`, because then the `List` reference would have a method `Add(Object)`, which wouldn't be valid for the actual `List` that the reference "points" to. – 15ee8f99-57ff-4f92-890c-b56153 Oct 12 '17 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?](https://stackoverflow.com/q/8030538/3744182) or [Deserializing polymorphic json classes without type information using json.net](https://stackoverflow.com/q/19307752/3744182) or [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182). Then you can just deserialize to `Container`. – dbc Oct 12 '17 at 17:03
  • 1
    This is covariance in action. I think if you change the signature of the generic class `Container` accordingly you'll be okay. –  Oct 12 '17 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 and populating Container with those types. I will try instead to resolve to Conatiner but still populate with ContentA's... – phil Oct 12 '17 at 18:11
  • @dbc If you'd like to post this answer I'll mark it as the solution. As I said I was using the methods in the links you provided but it prompted me to check my implementation which was wrong. During type resolution I was creating and instance of Container instead of Container. Once I changed that and added an IContentType converter to pass into Newtonsoft as a Converter, everything worked. – phil Oct 13 '17 at 13:57
  • @user1200984 - OK, done. – dbc Oct 13 '17 at 18:04

1 Answers1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340