Search code examples
c#interfacenestedjson.netdeserialization

C# Newtonsoft: how do deserialize interface containing interface containing interface, without Json attributes


I have the following code which breaks at the last line with exception "Could not create an instance of type DeserializeInterfaces.IC". How can I deserialize without using any attributes like:

[JsonProperty(ItemConverterType = typeof(ConcreteConverter<C>))]`  

I can't use the attributes because the interfaces are in a different project and I can't have a circular dependency of the interfaces on the concrete classes.

My code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace DeserializeInterfaces
{
  public class CustomInterfaceConverter : JsonConverter
  {
    public override bool CanConvert( Type objectType )
    {
      bool convertible = typeof( IB ).IsAssignableFrom( objectType )
                         || typeof( IC ).IsAssignableFrom( objectType );

      return convertible;
    }

    public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
    {
      JObject jsonObject = JObject.Load( reader );
          
      if (objectType.Name == "IB")
      {
        B retVal = jsonObject.ToObject<B>();
        return retVal;
      }
      if (objectType.Name == "IC")
      {
        C retVal = jsonObject.ToObject<C>();
        return retVal;
      }
      // Handle other cases or return a default implementation
      return null;
    }
    public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
    {
      throw new NotImplementedException();
    }
  }

  public interface IA
  {
    IB b { get; set; }
  }

  public class A : IA
  {
    public A() { }

    public IB b { get; set; }
  }

  public interface IB
  {
    IC c { get; set; }
  }


  public class B : IB
  {
    public B() { }

    public B( IC pC )
    {
      c = pC;
    }

    public IC c { get; set; }
  }

  public interface IC
  {
    int v { get; set; }
  }

  public class C : IC
  {
    public C() { }

    public C( int pV )
    {
      v = pV;
    }

    public int v { get; set; }
  }

  class Program
  {
    static void Main( string[] args )
    {
      CustomInterfaceConverter jsonConverter = new CustomInterfaceConverter();
      IA a = new A();
      a.b = new B();
      a.b.c = new C();
      a.b.c.v = 1;
      string aSer = JsonConvert.SerializeObject( a );

      A aDeser = JsonConvert.DeserializeObject<A>( aSer, jsonConverter );
    }
  }
}

Solution

  • Assuming that IA should always be deserialized as A, IB as B and IC as C, you may use three instances of InterfaceToConcreteConverter<TInterface, TConcrete> to deserialize your JSON, one for each interface, where the converter is taken from this answer to Json.Net - performant deserialization onto interface based data structure?:

    public class InterfaceToConcreteConverter<TInterface, TConcrete> : JsonConverter where TConcrete : TInterface
    {
        static InterfaceToConcreteConverter()
        {
            // TConcrete should be a subtype of an abstract type, or an implementation of an interface.  If they
            // are identical an infinite recursion could result, so throw an exception.
            if (typeof(TInterface) == typeof(TConcrete))
                throw new InvalidOperationException(string.Format("typeof({0}) == typeof({1})", typeof(TInterface), typeof(TConcrete)));
        }
    
        public override bool CanConvert(Type objectType) =>objectType == typeof(TInterface);
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => serializer.Deserialize(reader, typeof(TConcrete));
        public override bool CanWrite => false;
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
    }
    

    With this converter, your serialization code would look like:

    var settings = new JsonSerializerSettings
    {
        Converters = { new InterfaceToConcreteConverter<IA, A>(), 
                      new InterfaceToConcreteConverter<IB, B>(),
                      new InterfaceToConcreteConverter<IC, C>(),}
    };
    
    string aSer = JsonConvert.SerializeObject( a, settings );
    
    A aDeser = JsonConvert.DeserializeObject<A>( aSer, settings );
    

    Notes:

    • This answer depends on the fact that each interface has one and only one concrete implementation. If this is not correct, you will need to use some sort of previously serialized type identifier to determine the concrete type.

    • Single-responsibility principle is your friend here. It's much easier to create a generic interface-to-concrete converter and instantiate three instances than create some omnibus converter for all interfaces.

      If you have many different places where you need to serialize or deserialize using the converters, you could create static default settings for projectwide use:

      public static class JsonExtensions
      {
          public static JsonSerializerSettings DefaultSettings { get; } =
              new JsonSerializerSettings
              {
                  Converters = { new InterfaceToConcreteConverter<IA, A>(), 
                                new InterfaceToConcreteConverter<IB, B>(),
                                new InterfaceToConcreteConverter<IC, C>(),}
              };
      }
      
      A aDeser = JsonConvert.DeserializeObject<A>( aSer, JsonExtensions.DefaultSettings );
      
    • By returning false from CanWrite default serialization will be used when writing.

    Demo fiddle here.