Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you translate a flat dotted list of properties to a constructed object?

I have a list of properties and their values and they are formatted in a Dictionary<string, object> like this:

Person.Name = "John Doe"
Person.Age = 27
Person.Address.House = "123"
Person.Address.Street = "Fake Street"
Person.Address.City = "Nowhere"
Person.Address.State = "NH"

There are two classes. Person is composed of the string Name and primitive Age as well as the complex Address class which has House, Street, City, and State string properties.

Basically what I want to do is look up the class Person in the current assembly and create an instance of it and assign all of the values, no matter how complicated the classes get, as long as at the deepest level they are composed of primitives, strings, and a few common structures such as DateTime.

I have a solution which allows me to assign the top level properties and down into one of the complex properties. I am assuming I must use recursion to solve this, but I would prefer not to.

Though, even with recursion, I am at a loss as to a nice way to get down into each of the properties and assign their values.

In this example below I am trying to translate the dotted representation to classes based on a method's parameters. I look up the appropriate dotted representation based on the parameter's type, trying to find a match. DotField is basically a KeyValuePair<string, object> where the key is the Name property. The code below may not work correctly but it should express the idea well enough.

foreach (ParameterInfo parameter in this.method.Parameters)
{
    Type parameterType = parameter.ParameterType;
    object parameterInstance = Activator.CreateInstance(parameterType);

    PropertyInfo[] properties = parameterType.GetProperties();
    foreach (PropertyInfo property in properties)
    {
        Type propertyType = property.PropertyType;
        if (propertyType.IsPrimitive || propertyType == typeof(string))
        {
            string propertyPath = String.Format("{0}.{1}", parameterType.Name, propertyType.Name);
            foreach (DotField df in this.DotFields)
            {
                if (df.Name == propertyPath)
                {
                    property.SetValue(parameterInstance, df.Value, null);
                    break;
                }
            }
        }
        else
        {
            // Somehow dive into the class, since it's a non-primitive
        }
    }
}
like image 617
Michael J. Gray Avatar asked Oct 17 '13 16:10

Michael J. Gray


2 Answers

Your Dictionary sounds similar to JSON-formatted data. If you first transform it into a compatible form, you can use Json.Net to convert the dictionary into your object. Here's an example of that:

public static void Main()
{
    var dict = new Dictionary<string, object>
    {
        {"Person.Name", "John Doe"},
        {"Person.Age", 27},
        {"Person.Address.House", "123"},
        {"Person.Address.Street", "Fake Street"},
        {"Person.Address.City", "Nowhere"},
        {"Person.Address.State", "NH"},
    };
    var hierarchicalDict = GetItemAndChildren(dict, "Person");
    string json = JsonConvert.SerializeObject(hierarchicalDict);
    Person person = JsonConvert.DeserializeObject<Person>(json);
    // person has all of the values you'd expect
}
static object GetItemAndChildren(Dictionary<string, object> dict, string prefix = "")
{
    object val;
    if (dict.TryGetValue(prefix, out val))
        return val;
    else
    {
        if (!string.IsNullOrEmpty(prefix))
            prefix += ".";
        var children = new Dictionary<string, object>();
        foreach (var child in dict.Where(x => x.Key.StartsWith(prefix)).Select(x => x.Key.Substring(prefix.Length).Split(new[] { '.' }, 2)[0]).Distinct())
        {
            children[child] = GetItemAndChildren(dict, prefix + child);
        }
        return children;
    }
}
like image 78
Tim S. Avatar answered Nov 02 '22 20:11

Tim S.


You could also use reflection for doing so. I had fun writing this down :)

private object Eval(KeyValuePair<string, object> df)
{
    var properties = df.Key.Split('.');
    //line below just creates the root object (Person), you could replace it with whatever works in your example
    object root = Activator.CreateInstance(Assembly.GetExecutingAssembly().GetTypes().First(t => t.Name == properties.First()));

    var temp = root;

    for (int i = 1; i < properties.Length - 1; i++)
    {
        var propertyInfo = temp.GetType().GetProperty(properties[i]);
        var propertyInstance = Activator.CreateInstance(propertyInfo.PropertyType);                
        propertyInfo.SetValue(temp, propertyInstance, null);

        temp = propertyInstance;
    }

    temp.GetType().GetProperty(properties.Last()).SetValue(temp, df.Value, null);
    return root;
}
like image 2
rla4 Avatar answered Nov 02 '22 20:11

rla4