dmitry-bym / JsonKnownTypes

Simple way to serialize and deserialize polymorphic types for Json.NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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