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

Viewed 275342

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?

8 Answers

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.

Related