Support for classes with duplicate names
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: https://github.com/dmitry-bym/JsonKnownTypes/pull/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