I am trying to create a JsonConverter
that works like the following:
- Only applies to
abstract
class
es andinterface
s - 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?