Using Json.NET converters to deserialize properties

.NetSerializationjson.net

.Net Problem Overview


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?

.Net Solutions


Solution 1 - .Net

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

Solution 2 - .Net

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.

Solution 3 - .Net

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; }
}

Solution 4 - .Net

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.

Solution 5 - .Net

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.

Solution 6 - .Net

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.

Solution 7 - .Net

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

Solution 8 - .Net

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.

Solution 9 - .Net

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondthrasherView Question on Stackoverflow
Solution 1 - .NetDaniel T.View Answer on Stackoverflow
Solution 2 - .NetMrMDavidsonView Answer on Stackoverflow
Solution 3 - .NetErhhungView Answer on Stackoverflow
Solution 4 - .NetSamuelDavisView Answer on Stackoverflow
Solution 5 - .NetBruno AltinetView Answer on Stackoverflow
Solution 6 - .NetSteve GreatrexView Answer on Stackoverflow
Solution 7 - .NetLuis ArmandoView Answer on Stackoverflow
Solution 8 - .NetTimothy BaldridgeView Answer on Stackoverflow
Solution 9 - .NetramView Answer on Stackoverflow