0

I am trying to create a JsonConverter that works like the following:

  • Only applies to abstract classes and interfaces
  • Writes to JSON in the "default" manner
  • Reads from JSON by looking for all derived concrete classes and trying to deserialize into each. Uses the first one that works, or if there are multiple that work, throw an error (with a way to override this setting and instead take the first that works).
  • It does not work recursively, meaning that any classes properties that are abstract or interface need to be tagged with the attribute

Example of how it should work:

If we have

    /// <summary>
    /// Interface with multiple non-conflicting implementations
    /// </summary>
    [JsonConverter(typeof(PolyJsonConverter))]
    private interface IBar
    {
        string StringProperty { get; }
    }

    private class Bar1 : IBar
    {
        public string StringProperty { get; set; }

        public string OtherStringProperty { get; set; }
    }

    private class Bar2 : IBar
    {
        public string StringProperty { get; set; }

        public double[] DoubleArrayProperty { get; set; }
    }

then here is a test that should pass

    [TestMethod]
    public void InterfaceWithNonConflictingImplementationsDerivedFromOtherInterfaceCanSerializeAndDeserializeToBaseInterfaceTest()
    {
        var concreteBaz = new Baz1()
        {
            StringProperty = "blah blah",
            IntArrayProperty = new int[] { 1, 2, 3 }
        };

        string json = JsonConvert.SerializeObject(concreteBaz);

        IBar bar = JsonConvert.DeserializeObject<IBar>(json);

        Assert.IsNotNull(bar);
        Assert.AreEqual(concreteBaz.StringProperty, bar.StringProperty);
    }

I have an attempt that almost works, except that there is an infinite loop in the ReadJson

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);

        string json = jObject.ToString();

        var derivedTypes = GetDerivedTypes(objectType);

        object match = null;

        foreach (var dervivedType in derivedTypes)
        {
            object deserialized;

            try
            {
                deserialized = JsonConvert.DeserializeObject(json, dervivedType, requiredSerializerSettings);
            }
            catch
            {
                continue;
            }

            if (match != null)
            {
                if (conflictResolutionMode == ConflictResolutionMode.ThrowError)
                {
                    throw new JsonException($"Multiple matching implementations found for base type {objectType}.");
                }
            }
            else
            {
                match = deserialized;

                if (conflictResolutionMode == ConflictResolutionMode.UseArbitrary)
                {
                    break;
                }
            }
        }

        if (match == null)
        {
            throw new JsonException($"Could not find match for type {objectType} among derived types {string.Join(", ", derivedTypes)}.");
        }

        return match;
    }

    private static IEnumerable<Type> GetDerivedTypes(Type baseType)
    {
        return derivedTypesCache.GetOrAdd(baseType, t => QueryDerivedTypes(t));
    }

    private static IEnumerable<Type> QueryDerivedTypes(Type baseType)
    {
        return from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
               from assemblyType in domainAssembly.GetTypes()
               where baseType.IsAssignableFrom(assemblyType)
                 && !assemblyType.IsInterface
                 && !assemblyType.IsAbstract
               select assemblyType;
    }

I understand "why," but I can't figure out how to solve it. Any ideas?

  • the `[JsonConverter]` attribute applies to all concrete types that implement the interface. So, you could apply `NoConverter` as shown in [How to call JsonConvert.DeserializeObject and disable a JsonConverter applied to a base type via `[JsonConverter]`?](https://stackoverflow.com/a/45554062/3744182). Or you can allocate the concrete object yourself and use `serialize.Populate()` as shown in [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/q/29528648/3744182). Either one of those work for you? – dbc Oct 07 '18 at 21:47
  • For another option see [JsonConverter with Interface](https://stackoverflow.com/q/33321698). – dbc Oct 07 '18 at 21:51
  • 1
    [This question](https://stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-base) is very similar with users taking note that some methods can cause infinite loops. The general outline is to use the serializers' Populate method rather than JsonConvert methods inside ReadJson. – Mike Zboray Oct 07 '18 at 22:02

0 Answers0