如何将属性的平面虚线列表转换为构造对象
本文关键字:转换 对象 列表 虚线 属性 平面 | 更新日期: 2023-09-27 18:35:44
我有一个属性及其值的列表,它们的格式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"
有两个类。 Person
由字符串Name
和基元Age
以及具有House
、Street
、City
和State
字符串属性的复Address
类组成。
基本上我想做的是在当前程序集中查找类Person
并创建它的实例并分配所有值,无论类变得多么复杂,只要在最深层次上它们由原语、字符串和一些常见的结构(如 DateTime
)组成。
有一个解决方案,它允许我分配顶级属性并向下分配到其中一个复杂属性中。我假设我必须使用递归来解决这个问题,但我宁愿不这样做。
虽然,即使使用递归,我也不知道有一种很好的方法来深入了解每个属性并分配它们的值。
在下面的示例中,我尝试根据方法的参数将虚线表示转换为类。我根据参数的类型查找适当的虚线表示形式,试图找到匹配项。 DotField
基本上是一个KeyValuePair<string, object>
,其中键是Name
属性。下面的代码可能无法正常工作,但它应该足够好地表达这个想法。
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
}
}
}
您的Dictionary
听起来类似于 JSON 格式的数据。如果先将其转换为兼容的形式,则可以使用 Json.Net 将字典转换为对象。下面是一个示例:
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;
}
}
您也可以使用反射来执行此操作。我很乐意把这个写下来:)
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;
}
这是我的完整代码示例。我决定远离疯狂的反射和映射,并从虚线列表结构中获取大量上下文信息。
我要感谢Tim
和rla4
提供的解决方案,并提供将我带到这个解决方案的信息。
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;
}