NewtonSoft.Json Serialize and Deserialize class with property of type IEnumerable<ISomeInterface>

C#JsonSerializationjson.netDeserialization

C# Problem Overview


I am trying to move some code to consume ASP.NET MVC Web API generated Json data instead of SOAP Xml.

I have run into a problem with serializing and deserializing properties of type:

IEnumerable<ISomeInterface>.

Here is a simple example:

public interface ISample{
  int SampleId { get; set; }
}
public class Sample : ISample{
  public int SampleId { get; set; }
}
public class SampleGroup{
  public int GroupId { get; set; }
  public IEnumerable<ISample> Samples { get; set; }
 }
}

I can serialize instances of SampleGroup easily with:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );

However the corresponding deserialize fails:

JsonConvert.DeserializeObject<SampleGroup>( sz );

with this exception message:

"Could not create an instance of type JsonSerializationExample.ISample. Type is an interface or abstract class and cannot be instantated."

If I derive a JsonConverter I can decorate my property as follows:

[JsonConverter( typeof (SamplesJsonConverter) )]
public IEnumerable<ISample> Samples { get; set; }

Here is the JsonConverter:

public class SamplesJsonConverter : JsonConverter{
  public override bool CanConvert( Type objectType ){
    return ( objectType == typeof (IEnumerable<ISample>) );
  }

  public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ){
    var jA = JArray.Load( reader );
    return jA.Select( jl => serializer.Deserialize<Sample>( new JTokenReader( jl ) ) ).Cast<ISample>( ).ToList( );
  }

  public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ){
    ... What works here?
  }
}

This converter solves the deserialization problem but I cannot figure how to code the WriteJson method to get serialization working again.

Can anybody assist?

Is this a "correct" way to solve the problem in the first place?

C# Solutions


Solution 1 - C#

You don't need to use JsonConverterAttribute, just keep your model clean and use CustomCreationConverter instead, the code is simpler:

public class SampleConverter : CustomCreationConverter<ISample>
{
    public override ISample Create(Type objectType)
    {
        return new Sample();
    }
}

Then:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );
JsonConvert.DeserializeObject<SampleGroup>( sz, new SampleConverter());

Documentation: Deserialize with CustomCreationConverter

Solution 2 - C#

It is quite simple and out of the box support provided by json.net, you just have to use the following JsonSettings while serializing and Deserializing:

JsonConvert.SerializeObject(graph,Formatting.None, new JsonSerializerSettings()
{
    TypeNameHandling =TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

and for Deserialzing use the below code:

JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bData),type,
    new JsonSerializerSettings(){TypeNameHandling = TypeNameHandling.Objects}
);

Just take a note of the JsonSerializerSettings object initializer, that is important for you.

Solution 3 - C#

I solved that problem by using a special setting for JsonSerializerSettings which is called TypeNameHandling.All

TypeNameHandling setting includes type information when serializing JSON and read type information so that the create types are created when deserializing JSON

Serialization:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var text = JsonConvert.SerializeObject(configuration, settings);

Deserialization:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var configuration = JsonConvert.DeserializeObject<YourClass>(json, settings);

The class YourClass might have any kind of base type fields and it will be serialized properly.

Solution 4 - C#

Great solution, thank you! I took the AndyDBell's question and Cuong Le's answer to build an example with two diferent interface's implementation:

public interface ISample
{
    int SampleId { get; set; }
}

public class Sample1 : ISample
{
    public int SampleId { get; set; }
    public Sample1() { }
}


public class Sample2 : ISample
{
    public int SampleId { get; set; }
    public String SampleName { get; set; }
    public Sample2() { }
}

public class SampleGroup
{
    public int GroupId { get; set; }
    public IEnumerable<ISample> Samples { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        //Sample1 instance
        var sz = "{\"GroupId\":1,\"Samples\":[{\"SampleId\":1},{\"SampleId\":2}]}";
        var j = JsonConvert.DeserializeObject<SampleGroup>(sz, new SampleConverter<Sample1>());
        foreach (var item in j.Samples)
        {
            Console.WriteLine("id:{0}", item.SampleId);
        }
        //Sample2 instance
        var sz2 = "{\"GroupId\":1,\"Samples\":[{\"SampleId\":1, \"SampleName\":\"Test1\"},{\"SampleId\":2, \"SampleName\":\"Test2\"}]}";
        var j2 = JsonConvert.DeserializeObject<SampleGroup>(sz2, new SampleConverter<Sample2>());
        //Print to show that the unboxing to Sample2 preserved the SampleName's values
        foreach (var item in j2.Samples)
        {
            Console.WriteLine("id:{0} name:{1}", item.SampleId, (item as Sample2).SampleName);
        }
        Console.ReadKey();
    }
}

And a generic version to the SampleConverter:

public class SampleConverter<T> : CustomCreationConverter<ISample> where T: new ()
{
    public override ISample Create(Type objectType)
    {
        return ((ISample)new T());
    }
}

Solution 5 - C#

In my projects, this piece of code always worked as a default serializer which serializes the specified value as if there was no special converter:

serializer.Serialize(writer, value);

Solution 6 - C#

I got this to work:

explicit conversion

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
                                    JsonSerializer serializer)
    {
        var jsonObj = serializer.Deserialize<List<SomeObject>>(reader);
        var conversion = jsonObj.ConvertAll((x) => x as ISomeObject);

        return conversion;
    }

Solution 7 - C#

Having that:

public interface ITerm
{
    string Name { get; }
}

public class Value : ITerm...

public class Variable : ITerm...

public class Query
{
   public IList<ITerm> Terms { get; }
...
}

I managed conversion trick implementing that:

public class TermConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var field = value.GetType().Name;
        writer.WriteStartObject();
        writer.WritePropertyName(field);
        writer.WriteValue((value as ITerm)?.Name);
        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var properties = jsonObject.Properties().ToList();
        var value = (string) properties[0].Value;
        return properties[0].Name.Equals("Value") ? (ITerm) new Value(value) : new Variable(value);
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (ITerm) == objectType || typeof (Value) == objectType || typeof (Variable) == objectType;
    }
}

It allows me to serialize and deserialize in JSON like:

string JsonQuery = "{\"Terms\":[{\"Value\":\"This is \"},{\"Variable\":\"X\"},{\"Value\":\"!\"}]}";
...
var query = new Query(new Value("This is "), new Variable("X"), new Value("!"));
var serializeObject = JsonConvert.SerializeObject(query, new TermConverter());
Assert.AreEqual(JsonQuery, serializeObject);
...
var queryDeserialized = JsonConvert.DeserializeObject<Query>(JsonQuery, new TermConverter());

Solution 8 - C#

Considering that in most cases you don't want your entire data contract to have types supplied, but only those which are containing an abstract or interface, or list thereof; and also considering these instances are very rare and easily identifiable within your data entities, the easiest and least verbose way is to use

[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Objects)]
public IEnumerable<ISomeInterface> Items { get; set; }

as attribute on your property containing the enumerable/list/collection. This will target only that list, and only append type information for the contained objects, like this:

{
  "Items": [
    {
      "$type": "Namespace.ClassA, Assembly",
      "Property": "Value"
    },
    {
      "$type": "Namespace.ClassB, Assembly",
      "Property": "Value",
      "Additional_ClassB_Property": 3
    }
  ]
}

Clean, simple, and located where the complexity of your data model is introduced, instead of hidden away in some converter.

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
QuestionAndyDBellView Question on Stackoverflow
Solution 1 - C#cuongleView Answer on Stackoverflow
Solution 2 - C#Sunil SView Answer on Stackoverflow
Solution 3 - C#adam.bielastyView Answer on Stackoverflow
Solution 4 - C#Daniel MeloView Answer on Stackoverflow
Solution 5 - C#feroView Answer on Stackoverflow
Solution 6 - C#dvrView Answer on Stackoverflow
Solution 7 - C#Stanislav TrifanView Answer on Stackoverflow
Solution 8 - C#Marco JansenView Answer on Stackoverflow