91

I have a class definition that contains a property that returns an interface.

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
}

Attempting to serialize the Foo class using Json.NET gives me an error message like, "Could not create an instance of type 'ISomething'. ISomething may be an interface or abstract class."

Is there a Json.NET attribute or converter that would let me specify a concrete Something class to use during deserialization?

David
  • 1,754
  • 23
  • 35
dthrasher
  • 40,656
  • 34
  • 113
  • 139
  • I believe you need to specify a property name which gets/sets ISomething – ram Feb 12 '10 at 21:13
  • I have. I'm using the shorthand for auto-implemented properties introduced in C# 3.5. http://msdn.microsoft.com/en-us/library/bb384054.aspx – dthrasher Feb 14 '10 at 16:30
  • 4
    Isn't ISomething the type. I think ram is right, you still need a property name. I know this isn't related to your problem, but your comment above made me think I was missing some new feature in .NET that allowed you to specify a property without a name. – Mr Moose May 23 '12 at 04:42

9 Answers9

97

One of the things you can do with Json.NET is:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

The TypeNameHandling flag will add a $type property to the JSON, which allows Json.NET to know which concrete type it needs to deserialize the object into. This allows you to deserialize an object while still fulfilling an interface or abstract base class.

The downside, however, is that this is very Json.NET-specific. The $type will be a fully-qualified type, so if you're serializing it with type info,, the deserializer needs to be able to understand it as well.

Documentation: Serialization Settings with Json.NET

James Newton-King
  • 48,174
  • 24
  • 109
  • 130
Daniel T.
  • 37,212
  • 36
  • 139
  • 206
  • 2
    For Newtonsoft.Json it works similar, but the property is "$type" – Jaap Apr 08 '12 at 11:59
  • 1
    Watch out for possible security issues here when using `TypeNameHandling`. See [TypeNameHandling caution in Newtonsoft Json](https://stackoverflow.com/q/39565954/3744182) for details. – dbc Dec 15 '17 at 20:24
54

You can achieve this through the use of the JsonConverter class. Suppose you have a class with an interface property;

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(TycoonConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

Your JsonConverter is responsible for serializing and de-serializing the underlying property;

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

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    return serializer.Deserialize<Tycoon>(reader);
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

When you work with an Organisation deserialized via Json.Net the underlying IPerson for the Owner property will be of type Tycoon.

MrMDavidson
  • 2,705
  • 21
  • 20
  • 4
    Would the tag "[JsonConverter(typeof(TycoonConverter))]" still work if it was on a list of the interface? – Zwik Apr 02 '14 at 13:22
42

Instead of passing a customized JsonSerializerSettings object to JsonConvert.SerializeObject() with the TypeNameHandling.Objects option, as previously mentioned, you can just mark that specific interface property with an attribute so the generated JSON wouldn't be bloated with "$type" properties on EVERY object:

public class Foo
{
    public int Number { get; set; }

    // Add "$type" property containing type info of concrete class.
    [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
    public ISomething { get; set; }
}
Erhhung
  • 967
  • 9
  • 14
  • 6
    For collections of interfaces or abstract classes the property is "ItemTypeNameHandling". e.g. : [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)] – Anthony F Feb 14 '18 at 19:17
26

In the most recent version of the third party Newtonsoft Json converter you can set a constructor with a concrete type relating to the interfaced property.

public class Foo
{ 
    public int Number { get; private set; }

    public ISomething IsSomething { get; private set; }

    public Foo(int number, Something concreteType)
    {
        Number = number;
        IsSomething = concreteType;
    }
}

As long as Something implements ISomething this should work. Also do not put a default empty constructor in case the JSon converter attempts to use that, you must force it to use the constructor containing the concrete type.

PS. this also allows you to make your setters private.

Andiih
  • 12,285
  • 10
  • 57
  • 88
SamuelDavis
  • 3,312
  • 3
  • 17
  • 19
  • 7
    This should be shouted from the rooftops! True, it add constraints on the concrete implementation, but it is so much more straightforward than the other approaches for those situations where it can be used. – Mark Meuer Aug 09 '13 at 12:40
  • 4
    What if we have more than 1 constructor with multiple concrete types, will it still know? – Teoman shipahi Nov 19 '14 at 18:42
  • 1
    This answer is so elegant compared to all the convoluted nonsense you'd have to do otherwise. This should be the accepted answer. One caveat in my case, though, was I had to add [JsonConstructor] before the constructor in order for it to work.... I suspect that using this on only ONE of your concrete constructors would solve your (4 year old) problem @Teomanshipahi – nacitar sevaht Jan 11 '18 at 20:11
  • @nacitarsevaht I can go back and fix my problem now :) anyway I don't even remember what was it, but when I re-look this is a good solution for certain cases. – Teoman shipahi Jan 11 '18 at 20:15
  • 1
    we use this too but i prefer the convert in most cases because coupling the concrete type to the constructor defeats the purpose of using an interface for the property in the first place! – gabe Sep 23 '19 at 21:20
18

Had a same problem so i came up with my own Converter which uses known types argument.

public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+",")));
        }

        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

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

I defined two extension methods for deserializing and serializing:

public static class AltiJsonSerializer
{
    public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
    {
        if (string.IsNullOrEmpty(jsonString))
            return default(T);

        return JsonConvert.DeserializeObject<T>(jsonString,
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = new List<JsonConverter>
                        (
                            new JsonConverter[]
                            {
                                new JsonKnownTypeConverter(knownTypes)
                            }
                        )
                }
            );
    }

    public static string SerializeJson(this object objectToSerialize)
    {
        return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
        new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
    }
}

You can define your own way of comparing and identifying types in the convertes, i only use class name.

Furkan Ekinci
  • 2,472
  • 3
  • 29
  • 39
Bruno Altinet
  • 286
  • 2
  • 4
  • 1
    This JsonConverter is great, I used it but faced a couple of issues I solved this way: - Using JsonSerializer.CreateDefault() instead to Populate, because my object had a deeper hierarchy. - Using reflection to retreive the constructor and Instanciate it in the Create() method – Aurel May 22 '17 at 15:13
4

Normally I have always used the solution with TypeNameHandling as suggested by DanielT, but in cases here I have not had control over the incoming JSON (and so cannot ensure that it includes a $type property) I have written a custom converter that just allows you to explicitly specify the concrete type:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

This just uses the default serializer implementation from Json.Net whilst explicitly specifying the concrete type.

The source code and an overview are available on this blog post.

Steve Greatrex
  • 15,789
  • 5
  • 59
  • 73
2

I just wanted to complete the example that @Daniel T. showed us above:

If you are using this code to serialize your object:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

The code to deserialize the json should look like this:

var settings = new JsonSerializerSettings(); 
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);

This is how a json gets conformed when using the TypeNameHandling flag:enter image description here

Luis Armando
  • 444
  • 1
  • 7
  • 17
-5

I've wondered this same thing, but I'm afraid it can't be done.

Let's look at it this way. You hand to JSon.net a string of data, and a type to deserialize into. What is JSON.net to do when it hit's that ISomething? It can't create a new type of ISomething because ISomething is not an object. It also can't create an object that implements ISomething, since it doesn't have a clue which of the many objects that may inherit ISomething it should use. Interfaces, are something that can be automatically serialized, but not automatically deserialized.

What I would do would be to look at replacing ISomething with a base class. Using that you might be able to get the effect you are looking for.

Timothy Baldridge
  • 10,455
  • 1
  • 44
  • 80
  • 1
    I realize it won't work "out of the box". But I was wondering if there were some attribute like "[JsonProperty(typeof(SomethingBase))]" that I could use in order to provide a concrete class. – dthrasher Feb 15 '10 at 22:32
  • So why not use SomethingBase instead of ISomething in the above code? It could be argued that we are also looking at this the wrong way as Interfaces shouldn't be used in serialization, since they simply define communication "interface" with a given class. Serializing a interface technically is nonsense, as is serializing an abstract class. So while it "could be done" I'd argue that it "shouldn't be done". – Timothy Baldridge Feb 16 '10 at 05:14
  • Have you looked at any of the classes in the Newtonsoft.Json.Serialization Namespace? particularly the JsonObjectContract class? – johnny Feb 16 '10 at 23:00
-9

Here is a reference to an article written by ScottGu

Based on that, I wrote some code which I think might be helpful

public interface IEducationalInstitute
{
    string Name
    {
        get; set;
    }

}

public class School : IEducationalInstitute
{
    private string name;
    #region IEducationalInstitute Members

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    #endregion
}

public class Student 
{
    public IEducationalInstitute LocalSchool { get; set; }

    public int ID { get; set; }
}

public static class JSONHelper
{
    public static string ToJSON(this object obj)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(obj);
    }
    public  static string ToJSON(this object obj, int depth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RecursionLimit = depth;
        return serializer.Serialize(obj);
    }
}

And this is how you would call it

School myFavSchool = new School() { Name = "JFK High School" };
Student sam = new Student()
{
    ID = 1,
    LocalSchool = myFavSchool
};
string jSONstring = sam.ToJSON();

Console.WriteLine(jSONstring);
//Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}

If I understand it correctly, I do not think you need to specify a concrete class which implements the interface for JSON serialization.

Tok'
  • 565
  • 8
  • 11
ram
  • 11,468
  • 16
  • 63
  • 89
  • 1
    Your sample uses the JavaScriptSerializer, a class in the .NET Framework. I'm using Json.NET as my serializer. http://www.codeplex.com/Json – dthrasher Feb 14 '10 at 16:20
  • 3
    Does not refer to the original question, Json.NET was explicitly mentioned there. – Oliver Dec 06 '12 at 12:51