Daniel O.
Daniel O.

Reputation: 175

JSON.NET: Deserialize a class containig a List of objects derived from an interface

I have troubles deserializing my object. It contains following property:

public List<IShape> Shapes { get; set; };

and JSON.NET deserializer always tells me, that it is not possible to instantiate an interface.

I have several classes which implement interfaces which implement IShape interface, e.g. Polyline -> IPolyline -> IShape. I tried two solutions already:

But I got the same exception, that IShape cannot be instantied, was thrown.

I serialize the object with TypeNameHandling.Auto, TypeNameHandling.All doesn't help too, even when I use the converters mentioned in posts I linked above.

Does anyone know of a solution to this problem? If some code is needed I will gladly post it.

Here is a sample of JSON that is generated.

"$type": "SketchModel.Layer, SketchModel",
        "Id": 57865477,
        "Shapes": {
          "$type": "System.Collections.Generic.List`1[[SketchModel.Shapes.AbstractShapes.IShape, SketchModel]], mscorlib",
          "$values": [
            {
              "$type": "SketchModel.Shapes.Polyline, SketchModel",

This line is responsible for the problem:

"System.Collections.Generic.List`1[[SketchModel.Shapes.AbstractShapes.IShape, SketchModel]], mscorlib"

It simply doesn't know how to instantiate IShape. If I create a custom converter and let it return a Polyline for each IShape, it works, but doesn't create any other shapes (e.g. Ellipses).

In the public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) method which is overriden in the custom converter, if I let me print the full type name of objectType it's always IShape, never anything else...

Upvotes: 6

Views: 9493

Answers (3)

SushiHangover
SushiHangover

Reputation: 74144

Without including concrete types within your Json string, you can use a JsonConverter to convert an IList<SomeInterface> to their concrete type:

Class to deserialize:

public partial class MovieInfo : IMovieInfo
{
    ~~~~

    [JsonProperty("genres")]
    [JsonConverter(typeof(ListConverter<IGenre, Genre>))]
    public IList<IGenre> Genres { get; set; }

    ~~~~
}

JsonConverter example:

public class ListConverter<I, T> : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(I);
    }
    public override void WriteJson(JsonWriter writer,
         object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
         Type objectType, object existingValue,
         JsonSerializer serializer)
    {
        JArray jsonArray = JArray.Load(reader);
        var deserialized = (List<T>)Activator.CreateInstance(typeof(List<T>));
        serializer.Populate(jsonArray.CreateReader(), deserialized);
        return deserialized as IList<I>;
    }
}

Upvotes: 0

Davin Tryon
Davin Tryon

Reputation: 67296

The exception makes sense because the deserializer doesn't know what concrete type the interface is supposed to represent when hydrating.

During serialization, JSON.NET allows you to configure it to add some meta data to be used in this case. This SO question has an answer explaining how to configure it.

The configuration will add a type property to the JSON that will be used during deserialization.

Upvotes: 2

user1869641
user1869641

Reputation: 73

I had exactly this problem and only solved it by explicitly providing a converter for the Type. It doesn't work if I use annotations - I need to pass the converters in at deserialization - basically like this:

state = JsonConvert.DeserializeObject<StateImpl>((String)stateObject, new JsonConverter[] { new StatePersistenceStateEntryConverter(), new StatePersistenceUserInteractionConverter() });

Where my StateImpl object included these properties:

    [DataMember]
    [JsonProperty("stateEntries", TypeNameHandling = TypeNameHandling.Auto)]
    public List<IStateEntry> StateEntries
    {
        get;
        set;
    }

    [DataMember]
    [JsonProperty("precommitStateEntry", TypeNameHandling = TypeNameHandling.Auto)]
    public IPrecommitStateEntry PrecommitStateEntry
    {
        get;
        set;
    }

IPrecommitStateEntry extends the IStateEntry interface (just FYI in case you're wondering why the extra logic in the converter).

Also - in my IStateEntry object, I have a similar child problem:

    [DataMember]
    [JsonProperty("userInteractions", TypeNameHandling = TypeNameHandling.Auto)]
    public List<IUserInteraction> UserInteractions
    {
        get;
        set;
    }

So my object has child list properties of IStateEntry and an IStateEntry has a further child list of IUserInteraction. My converters are as follows:

public class StatePersistenceStateEntryConverter : CustomCreationConverter<IStateEntry>
{
    public override IStateEntry Create(Type objectType)
    {
        if (objectType == typeof(IPrecommitStateEntry))
        {
            return new PrecommitStateEntry();
        }
        else
        {
            return new StateEntry();
        }
    }
}

And...

public class StatePersistenceUserInteractionConverter : CustomCreationConverter<IUserInteraction>
{
    public override IUserInteraction Create(Type objectType)
    {
        return new UserInteraction();
    }
}

Literally all they're doing is creating an instance of that particular object implementation.

So I don't know why the converters are needed as clearly a List can be instantiated - just not the IStateEntry individually. Clearly there's a bug in the NewtonSoft implementation somewhere - or I'm missing something fundamental.

Hope that helps. It was a frustrating few hours for me, but now working!

Upvotes: 1

Related Questions