Skip to content
This repository has been archived by the owner on Oct 19, 2020. It is now read-only.

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

Open
uheee opened this issue Apr 10, 2020 · 2 comments
Open

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

uheee opened this issue Apr 10, 2020 · 2 comments

Comments

@uheee
Copy link

uheee commented Apr 10, 2020

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?

@paiden
Copy link
Owner

paiden commented Apr 10, 2020

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
}

@uheee
Copy link
Author

uheee commented Apr 10, 2020

@paiden Thanks, it works.

paiden added a commit that referenced this issue Apr 21, 2020
User can now map TOML objects into specific base types via
specifying a concrete activator. A consequence is that the
ambiguous API MapTableKey is not needed anymore. This
is a good thing but will introduce a breaking change with
the next release.

While this commit makes it work and all test are green, I have
the feeling not all scenarios will work as e.g. the ToDictionary
test are not good enough to catch all issues here.

In general the conversion from TOML object graph to a CLR
object graph has a lot of technical debt. E.g. conversion is
partially handled by converter and partially handled by the
TOML objects themselves. The visitor pattern there is a pain.
Future work will be needed to improve the code base in this
space.

Another consequence of this inconsistencies is, that the
CreateInstanceContext KeyChain property most of the time
is not filled with correct data.

Related #90
paiden added a commit that referenced this issue Apr 22, 2020
User can now map TOML objects into specific base types via
specifying a concrete activator. A consequence is that the
ambiguous API MapTableKey is not needed anymore. This
is a good thing but will introduce a breaking change with
the next release.

While this commit makes it work and all test are green, I have
the feeling not all scenarios will work as e.g. the ToDictionary
test are not good enough to catch all issues here.

In general the conversion from TOML object graph to a CLR
object graph has a lot of technical debt. E.g. conversion is
partially handled by converter and partially handled by the
TOML objects themselves. The visitor pattern there is a pain.
Future work will be needed to improve the code base in this
space.

Another consequence of this inconsistencies is, that the
CreateInstanceContext KeyChain property most of the time
is not filled with correct data.

Related #90
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants