Reputation: 9906
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
}
}
}
Upvotes: 5
Views: 577
Reputation: 9906
Here is my complete code example. I decided to stay away from the crazy amount of reflection and mapping and get a lot of context information from the dotted list structure.
I want to thank Tim
and rla4
for their solutions and providing the information that brought me to this solution.
private static int GetPathDepth(string path)
{
int depth = 0;
for (int i = 0; i < path.Length; i++)
{
if (path[i] == '.')
{
depth++;
}
}
return depth;
}
private static string GetPathAtDepth(string path, int depth)
{
StringBuilder pathBuilder = new StringBuilder();
string[] pathParts = path.Split('.');
for (int i = 0; i < depth && i < pathParts.Length; i++)
{
string pathPart = pathParts[i];
if (i == depth - 1 || i == pathParts.Length - 1)
{
pathBuilder.Append(pathPart);
}
else
{
pathBuilder.AppendFormat("{0}.", pathPart);
}
}
string pathAtDepth = pathBuilder.ToString();
return pathAtDepth;
}
private static string[] GetIntermediatePaths(string path)
{
int depth = GetPathDepth(path);
string[] intermediatePaths = new string[depth];
for (int i = 0; i < intermediatePaths.Length; i++)
{
string intermediatePath = GetPathAtDepth(path, i + 1);
intermediatePaths[i] = intermediatePath;
}
return intermediatePaths;
}
private static PropertyInfo GetProperty(Type root, string path)
{
PropertyInfo result = null;
string[] pathParts = path.Split('.');
foreach (string pathPart in pathParts)
{
if (Object.ReferenceEquals(result, null))
{
result = root.GetProperty(pathPart);
}
else
{
result = result.PropertyType.GetProperty(pathPart);
}
}
if (Object.ReferenceEquals(result, null))
{
throw new ArgumentException("A property at the specified path could not be located.", "path");
}
return result;
}
private static object GetParameter(ParameterInfo parameter, Dictionary<string, string> valueMap)
{
Type root = parameter.ParameterType;
Dictionary<string, object> instanceMap = new Dictionary<string, object>();
foreach (KeyValuePair<string, string> valueMapEntry in valueMap)
{
string path = valueMapEntry.Key;
string value = valueMapEntry.Value;
string[] intermediatePaths = GetIntermediatePaths(path);
foreach (string intermediatePath in intermediatePaths)
{
PropertyInfo intermediateProperty = GetProperty(root, intermediatePath);
object propertyTypeInstance;
if (!instanceMap.TryGetValue(intermediatePath, out propertyTypeInstance))
{
propertyTypeInstance = Activator.CreateInstance(intermediateProperty.PropertyType);
instanceMap.Add(intermediatePath, propertyTypeInstance);
}
}
PropertyInfo property = GetProperty(root, path);
TypeConverter converter = TypeDescriptor.GetConverter(property.PropertyType);
object convertedValue = converter.ConvertFrom(value);
instanceMap.Add(path, convertedValue);
}
object rootInstance = Activator.CreateInstance(root);
foreach (KeyValuePair<string, object> instanceMapEntry in instanceMap)
{
string path = instanceMapEntry.Key;
object value = instanceMapEntry.Value;
PropertyInfo property = GetProperty(root, path);
object instance;
int depth = GetPathDepth(path);
if (depth == 0)
{
instance = rootInstance;
}
else
{
string parentPath = GetPathAtDepth(path, depth);
instance = instanceMap[parentPath];
}
property.SetValue(instance, value);
}
return rootInstance;
}
Upvotes: 1
Reputation: 1246
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;
}
Upvotes: 2
Reputation: 56536
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;
}
}
Upvotes: 2