1

I am trying to create an implementation of JsonConverter that can deserialize interfaces and abstract classes by using the first derived type that can be deserialized through default deserialization. Here is my unit test to give you can idea:

    [TestMethod]
    public void BaseTypeConverterCanConvertPolymorphicClass()
    {
        var polymorphicClass = new ConcreteFoo()
        {
            SomeString = "test string",
            Bar = new ConcreteBar()
            {
                SomeInt = 18237
            }
        };

        string serialized = JsonConvert.SerializeObject(polymorphicClass);

        IFoo deserialized = JsonConvert.DeserializeObject<IFoo>(serialized);

        Assert.IsTrue(deserialized is ConcreteFoo);
    }

    [JsonConverter(typeof(BaseTypeConverter))]
    private interface IFoo
    {
        [JsonProperty("someString")]
        string SomeString { get; }

        [JsonProperty("bar")]
        IBar Bar { get; }
    }

    private class ConcreteFoo : IFoo
    {
        public string SomeString { get; set; }

        public IBar Bar { get; set; }
    }

    [JsonConverter(typeof(BaseTypeConverter))]
    private interface IBar
    {
        [JsonProperty("someInt")]
        int SomeInt { get; }
    }

    private class ConcreteBar : IBar
    {
        public int SomeInt { get; set; }
    }

    private class OtherConcreteBar : IBar
    {
        public int SomeInt { get; set; }

        [JsonProperty("someDouble")]
        public double SomeDouble { get; set; }
    }

and here is my implementation:

public class BaseTypeConverter : JsonConverter
{
    private static IDictionary<Type, ICollection<Type>> cache = new Dictionary<Type, ICollection<Type>>();

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAbstract || objectType.IsInterface;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        IEnumerable<Type> derived = GetDerivedTypes(objectType);

        var defaultSerializer = JsonSerializer.CreateDefault();

        foreach (Type type in derived)
        {
            object deserialized = defaultSerializer.Deserialize(reader, type);

            if (deserialized != null)
            {
                return deserialized;
            }
        }

        throw new JsonException($"Could not deserialize type {objectType} into any of the dervied types: {string.Join(",", derived)}.");
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException($"Should never have to write because {nameof(CanWrite)} is {CanWrite}.");
    }

    private static IEnumerable<Type> GetDerivedTypes(Type baseType)
    {
        if (cache.ContainsKey(baseType))
        {
            return cache[baseType];
        }

        var derivedTypes =
            (from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
             from assemblyType in domainAssembly.GetTypes()
             where baseType.IsAssignableFrom(assemblyType)
                && baseType != assemblyType
             select assemblyType).ToList();

        cache[baseType] = derivedTypes;

        return derivedTypes;
    }
}

The problem I'm finding is that somehow defaultSerializer.Deserialize(reader, type); is re-calling my ReadJson method rather than the "default" one like I expect. In other words, it calls it on type ConcreteFoo.

Where is the flaw in my logic?

See Sharp
  • 379
  • 1
  • 4
  • 11
  • 1
    I have the impression that this line won't work: `IFoo deserialized = JsonConvert.DeserializeObject(serialized);` - given that you might have **N** implementations of `IFoo`, how is the serializer supposed to know which type to deserialize? You should specify the type of the object to deserialize, something like `IFoo deserialized = JsonConvert.DeserializeObject(serialized);` – Rui Jarimba Aug 21 '18 at 23:39
  • Also, do you really need to create a custom `JsonConverter`? – Rui Jarimba Aug 21 '18 at 23:40
  • @RuiJarimba The idea is that it tries "normal" deserialization on each of the N implementations and returns the first that works. Maybe later I'll have it throw an exception if more than one works. But this is a custom converter that is needed if I can't use type-name handling for some reason (doesn't matter the reason). – See Sharp Aug 21 '18 at 23:43
  • Possible duplicate of [Casting interfaces for deserialization in JSON.NET](https://stackoverflow.com/questions/5780888/casting-interfaces-for-deserialization-in-json-net) – Rui Jarimba Aug 21 '18 at 23:52
  • I believe the solution to your problem is in the link I posted, there are a few different approaches you can try – Rui Jarimba Aug 21 '18 at 23:54

0 Answers0