Support for classes with duplicate names
RonSijm opened this issue · comments
Hi there, I've used your library, and it seems to work in most of the cases, unless there are classes with duplicate names. I've created two test scenarios:
Scenario 1: Duplicate classes in a different namespace
using Newtonsoft.Json;
using NUnit.Framework;
namespace JsonKnownTypes.UnitTests
{
public class DuplicateClassInnerClass
{
[Test]
public void BaseClassTest()
{
var c = new ClassInContainer();
var json = JsonConvert.SerializeObject(c);
}
}
[JsonConverter(typeof(JsonKnownTypesConverter<ClassWithClassBase>))]
public class ClassWithClassBase
{
public string Name { get; set; }
}
public class ClassInContainer : ClassWithClassBase
{
}
}
namespace JsonKnownTypes.UnitTests.a
{
public class ClassInContainer : ClassWithClassBase
{
}
}
namespace JsonKnownTypes.UnitTests.b
{
public class ClassInContainer : ClassWithClassBase
{
}
}
Scenario 2: Nested classes:
using Newtonsoft.Json;
using NUnit.Framework;
namespace JsonKnownTypes.UnitTests
{
public class DuplicateClassInnerClass
{
[Test]
public void BaseClassTest()
{
var c = new ClassContainer1.ClassInContainer();
var json = JsonConvert.SerializeObject(c);
}
}
[JsonConverter(typeof(JsonKnownTypesConverter<ClassWithClassBase>))]
public class ClassWithClassBase
{
public string Name { get; set; }
}
public class ClassContainer1
{
// Name: ClassInContainer
// Full name: JsonKnownTypes.UnitTests.ClassContainer1+ClassInContainer
public class ClassInContainer : ClassWithClassBase
{
}
}
public class ClassContainer2
{
// Name: ClassInContainer
// Full name: JsonKnownTypes.UnitTests.ClassContainer2+ClassInContainer
public class ClassInContainer : ClassWithClassBase
{
}
}
}
Both cases fail in DiscriminatorValuesManager.AddAutoDiscriminators
with the exception: JsonKnownTypes.Exceptions.JsonKnownTypesException: 'ClassInContainer discriminator already in use'
I suppose one solution would be to add a JsonDiscriminatorAttribute
on all the classes, But in case of the second scenario, it is a common pattern in MediatR that every inner class is either called Query or Command - so I'd have to add an attribute to every single class.
A simple solution seems to be to simply have longer type descriptors:
internal static void AddAutoDiscriminators(this DiscriminatorValues discriminatorValues, Type[] inherited)
{
foreach (var type in inherited)
{
if (discriminatorValues.Contains(type))
continue;
discriminatorValues.AddType(type, type.FullName); // Full name instead of just name
}
}
though of course this is a big breaking change for everyone...
I was wondering if there's a better approach to this - besides downloading this package from source and modifying the type descriptor code
Perhaps it makes sense to add the ability to define a custom resolver like Func<Type, string>
like this
internal static void AddAutoDiscriminators(this DiscriminatorValues discriminatorValues, Type[] inherited)
{
foreach (var type in inherited)
{
if (discriminatorValues.Contains(type))
continue;
discriminatorValues.AddType(type, resolver(type)); }
}
resolver = x => x.FullName;
@RonSijm Then the general behavior will not change, but you can redefine what you need
Do you want to do that?
Sure, a Func<Type, string>
sounds good.
I have a working prototype locally, but I'm not sure about the implementation. I tried multiple different things, like I've added a public Func<Type, string> NameDiscriminatorResolver { get; set; } = type => type.Name;
in JsonKnownTypes.JsonDiscriminatorSettings
- which works.
The problem with that was that it changes the settings project-wide. Ideally it should be possible to set this name resolver in an attribute or something - but it doesn't seem very possible to do so.
I have an implementation that works with multiple different types - I'll try to create a PR for it
I've created a PR with the implementation I was using: #31
- It implements the
Func<Type, string> NameDiscriminatorResolver
- It also implements a type specific
JsonKnownTypesSettingsManager<T>
- because I didn't want to change the behavior for all types, but only for specific types.
I've added a test to show it works