Nett icon indicating copy to clipboard operation
Nett copied to clipboard

How to convert a dictionary whose value is an interface type?

Open uheee opened this issue 4 years ago • 2 comments

Hello, I have a interface and some derived classes, like:

public interface ISomeInterface
{
    int SomeInteger { get; set; }
    string SomeString { get; set; }
}

public class ClassA : ISomeInterface
{
    public int SomeInteger { get; set; } = 1;
    public string SomeString { get; set; } = "This is Class A.";
    public string ClassAProperty { get; set; } = "Only for Class A!";
}

public class ClassB : ISomeInterface
{
    public int SomeInteger { get; set; } = 2;
    public string SomeString { get; set; } = "This is Class B.";
    public string ClassBProperty { get; set; } = "Only for Class B!";
}

And I have a entity class with a IDictionary property with the interface above as value type:

public class TomlEntity
{
    public IDictionary<string, ISomeInterface> Dictionary { get; set; } =
        new Dictionary<string, ISomeInterface>();
}

Now I can write the entity to string but cannot read into it:

var entity = new TomlEntity();
var classA = new ClassA();
var classB = new ClassB();
entity.Dictionary.Add(nameof(ClassA), classA);
entity.Dictionary.Add(nameof(ClassB), classB);
var result = Toml.WriteString(entity);
Console.Write(result);
var reader = Toml.ReadString<TomlEntity>(result); // error

In fact I will have more derived classes and I have their own reflection types, how can I specify their type and instantiate them into the dictionary?

uheee avatar Apr 10 '20 05:04 uheee

Unfortunately this use case is not natively supported at the moment as a quick short investigations brings up.

At the moment you can only do the following workaround:

 var result = Toml.WriteString(entity);

var settings = TomlSettings.Create(cfg => cfg
    .ConfigureType<IDictionary<string, ISomeInterface>>(tc => tc
        .CreateInstance(() => new Dictionary<string, ISomeInterface>()))
    .ConfigurePropertyMapping(pm => pm
        .OnTargetPropertyNotFound(FailedToMapToTarget)));


void FailedToMapToTarget(string[] keyChain, object target, TomlObject source)
{
    var tgtDict = (Dictionary<string, ISomeInterface>)target;
    TomlTable srcTable = (TomlTable)source;

    var key = keyChain.Last();

    if (key == "ClassA")
    {
        tgtDict.Add(key, srcTable.Get<ClassA>());
    }
    else if (key == "ClassB")
    {
        tgtDict.Add(key, srcTable.Get<ClassB>());
    }
}
Console.Write(result);
var read = Toml.ReadString<TomlEntity>(result, settings);

But I think I will add this functionality in the near future. This will be a breaking change and the configuration will have to be changed to something like this, that is conceptually better

var settings = TomlSettings.Create(cfg => cfg
    .ConfigureType<IDictionary<string, ISomeInterface>>(tc => tc
        .CreateInstance(() => new Dictionary<string, ISomeInterface>()))
    .ConfigureType<ISomeInterface>(tc => tc
        .WithConversionFor<TomlTable>(conv => conv
            .FromToml(SomeInterfaceFromTomlTable)))
            
var read = Toml.ReadString<TomlEntity>(result, settings);

ISomeInterface SomeInterfaceFromTomlTable(string key, TomlTable tbl)
{
    if (key == "ClassA") { return tbl.Get<ClassA>(); }
    else if (key == "ClassB") { return tbl.Get<ClassB>(); }
    else { return null; } // probably throw EXC
}

paiden avatar Apr 10 '20 10:04 paiden

@paiden Thanks, it works.

uheee avatar Apr 10 '20 16:04 uheee