将对象映射为字典及其反向操作

131

有没有一种优雅的快速方法将对象映射到字典,反之亦然?

示例:

IDictionary<string,object> a = new Dictionary<string,object>();
a["Id"]=1;
a["Name"]="Ahmad";
// .....

转变为

SomeClass b = new SomeClass();
b.Id=1;
b.Name="Ahmad";
// ..........

最快的方法是通过代码生成,例如protobuf...我尽可能使用表达式树来避免反射。 - Konrad
以下所有自编码的答案都不支持深度转换,并且在实际应用中也无法正常工作。你应该使用一些库,比如earnerplates建议的Newtonsoft。 - Cesar
10个回答

229
使用一些反射和泛型在两个扩展方法中,你可以实现这个功能。
其他人大多采用了相同的解决方案,但这种方法使用了更少的反射,性能更好,而且更易读:
public static class ObjectExtensions
{
    public static T ToObject<T>(this IDictionary<string, object> source)
        where T : class, new()
    {
            var someObject = new T();
            var someObjectType = someObject.GetType();

            foreach (var item in source)
            {
                someObjectType
                         .GetProperty(item.Key)
                         .SetValue(someObject, item.Value, null);
            }

            return someObject;
    }

    public static IDictionary<string, object> AsDictionary(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
    {
        return source.GetType().GetProperties(bindingAttr).ToDictionary
        (
            propInfo => propInfo.Name,
            propInfo => propInfo.GetValue(source, null)
        );

    }
}

class A
{
    public string Prop1
    {
        get;
        set;
    }

    public int Prop2
    {
        get;
        set;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, object> dictionary = new Dictionary<string, object>();
        dictionary.Add("Prop1", "hello world!");
        dictionary.Add("Prop2", 3893);
        A someObject = dictionary.ToObject<A>();

        IDictionary<string, object> objectBackToDictionary = someObject.AsDictionary();
    }
}

1
我喜欢这个实现,我已经开始使用了,但是您对性能有任何反馈吗?我知道当涉及到性能时反射不是最好的选择。您有任何意见吗? - nolimit
@nolimit 实际上我不知道它的实际表现如何,但我猜测它并不是很差(当然比避免反射要差一些...)。事实上,还有什么选择呢?根据项目的需求,也许有其他选项,比如使用动态类型,这比反射快得多...谁知道呢! :) - Matías Fidemraizer
在我的情况下,替代方案就是通过赋值来构建对象,但我正在寻找一种既整洁又廉价的构建对象的方法。话虽如此,我发现了这个,这对我来说意味着使用那段代码并不太昂贵。 - nolimit
@nolimit 是的,看起来没有问题。关键是使用适合所需问题的正确工具:D 在你的情况下,它应该像魔法般运作。我相信我可以在代码中调整一些东西! - Matías Fidemraizer
1
此解决方案不适用于匿名对象(例如当您没有要转换的类型时),请参见https://dev59.com/smsz5IYBdhLWcg3w47-m#7596697。 - Ivan Castellanos
显示剩余3条评论

50

首先使用Newtonsoft将字典转换为JSON字符串。

var json = JsonConvert.SerializeObject(advancedSettingsDictionary, Newtonsoft.Json.Formatting.Indented);

然后将JSON字符串反序列化为您的对象

var myobject = JsonConvert.DeserializeObject<AOCAdvancedSettings>(json);

2
谨慎使用此方法。如果用于许多(数百个)字典,它将拖慢您的应用程序。 - amackay11
1
谢谢。这是目前最简单的解决方案。 - sebastian.roibu

15

似乎只有反射可以在这里帮助..我已经做了一个将对象转换为字典和反之的小例子:

[TestMethod]
public void DictionaryTest()
{
    var item = new SomeCLass { Id = "1", Name = "name1" };
    IDictionary<string, object> dict = ObjectToDictionary<SomeCLass>(item);
    var obj = ObjectFromDictionary<SomeCLass>(dict);
}

private T ObjectFromDictionary<T>(IDictionary<string, object> dict)
    where T : class 
{
    Type type = typeof(T);
    T result = (T)Activator.CreateInstance(type);
    foreach (var item in dict)
    {
        type.GetProperty(item.Key).SetValue(result, item.Value, null);
    }
    return result;
}

private IDictionary<string, object> ObjectToDictionary<T>(T item)
    where T: class
{
    Type myObjectType = item.GetType();
    IDictionary<string, object> dict = new Dictionary<string, object>();
    var indexer = new object[0];
    PropertyInfo[] properties = myObjectType.GetProperties();
    foreach (var info in properties)
    {
        var value = info.GetValue(item, indexer);
        dict.Add(info.Name, value);
    }
    return dict;
}

这不支持嵌套。 - Cesar
@Cesar 是的,它可以。你只需要实现它。也许在 PropertyInfo 上使用递归。 - Adjit

12

我强烈推荐使用Castle DictionaryAdapter,这是该项目中最好的保密之一。您只需定义一个带有所需属性的接口,然后在一行代码中,适配器将生成一个实现、实例化它,并将其值与传入的字典同步。我在 Web 项目中使用它来对我的 AppSettings 进行强类型管理:

var appSettings =
  new DictionaryAdapterFactory().GetAdapter<IAppSettings>(ConfigurationManager.AppSettings);
请注意,我不需要创建实现IAppSettings接口的类 - 适配器会即时完成。此外,虽然在这种情况下我只是读取属性值,但理论上,如果我要在appSettings上设置属性值,适配器将使底层字典与这些更改保持同步。

2
我猜“最好的保密”已经孤独地死去了。上面的Castle DictionaryAdapter链接以及http://www.castleproject.org/上的“DictionaryAdapter”的下载链接都指向404页面:( - Shiva
1
不是!它仍然在Castle.Core中。我已经修复了链接。 - Gaspar Nagy
Castle是一个很棒的库,但不知何故被忽视了。 - bikeman868

6
我认为你应该使用反射。像这样:

我认为您应该使用反射。类似于以下代码:

private T ConvertDictionaryTo<T>(IDictionary<string, object> dictionary) where T : new()
{
    Type type = typeof (T);
    T ret = new T();

    foreach (var keyValue in dictionary)
    {
        type.GetProperty(keyValue.Key).SetValue(ret, keyValue.Value, null);
    }

    return ret;
}

它会取出你的字典并循环遍历来设置值。虽然有改进空间,但这是一个开始。你应该这样调用它:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);

如果你可以使用“new”泛型约束,为什么还要使用激活器呢? :) - Matías Fidemraizer
@Matías Fidemraizer,您是完全正确的。我的错误。已更正。 - TurBas
谢谢,你太棒了。有一个问题,如果某个类没有字典中的某些属性,它应该只匹配存在于该类中的属性并跳过其他属性。目前我使用了try catch,是否有更好的选项? - Sunil Chaudhary

5

在Matías Fidemraizer的回答基础上,这里提供了一种支持绑定到除字符串以外的对象属性的版本。

using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace WebOpsApi.Shared.Helpers
{
    public static class MappingExtension
    {
        public static T ToObject<T>(this IDictionary<string, object> source)
            where T : class, new()
        {
            var someObject = new T();
            var someObjectType = someObject.GetType();

            foreach (var item in source)
            {
                var key = char.ToUpper(item.Key[0]) + item.Key.Substring(1);
                var targetProperty = someObjectType.GetProperty(key);

                //edited this line
                if (targetProperty.PropertyType == item.Value.GetType())
                {
                    targetProperty.SetValue(someObject, item.Value);
                }
                else
                {

                    var parseMethod = targetProperty.PropertyType.GetMethod("TryParse",
                        BindingFlags.Public | BindingFlags.Static, null,
                        new[] {typeof (string), targetProperty.PropertyType.MakeByRefType()}, null);
                    
                    if (parseMethod != null)
                    {
                        var parameters = new[] { item.Value, null };
                        var success = (bool)parseMethod.Invoke(null, parameters);
                        if (success)
                        {
                            targetProperty.SetValue(someObject, parameters[1]);
                        }

                    }
                }
            }

            return someObject;
        }

        public static IDictionary<string, object> AsDictionary(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
        {
            return source.GetType().GetProperties(bindingAttr).ToDictionary
            (
                propInfo => propInfo.Name,
                propInfo => propInfo.GetValue(source, null)
            );
        }
    }
}

5

反射可以通过遍历属性将对象转换为字典。

要反过来做,你需要在C#中使用动态的ExpandoObject(实际上已经继承了IDictionary,因此已经为您完成了此操作),除非您可以从字典条目的集合中推断出类型。

因此,如果您处于.NET 4.0环境中,请使用ExpandoObject,否则您需要做很多工作...


3
public class SimpleObjectDictionaryMapper<TObject>
{
    public static TObject GetObject(IDictionary<string, object> d)
    {
        PropertyInfo[] props = typeof(TObject).GetProperties();
        TObject res = Activator.CreateInstance<TObject>();
        for (int i = 0; i < props.Length; i++)
        {
            if (props[i].CanWrite && d.ContainsKey(props[i].Name))
            {
                props[i].SetValue(res, d[props[i].Name], null);
            }
        }
        return res;
    }

    public static IDictionary<string, object> GetDictionary(TObject o)
    {
        IDictionary<string, object> res = new Dictionary<string, object>();
        PropertyInfo[] props = typeof(TObject).GetProperties();
        for (int i = 0; i < props.Length; i++)
        {
            if (props[i].CanRead)
            {
                res.Add(props[i].Name, props[i].GetValue(o, null));
            }
        }
        return res;
    }
}

1
如果您正在使用Asp.Net MVC,请看一下:
public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

这是 System.Web.Mvc.HtmlHelper 类中的静态公共方法。


我不会使用它,因为它将“_”替换为“-”。 而且你需要MVC。使用纯Routing类: new RouteValueDictionary(objectHere); - regisbsb

0
    public Dictionary<string, object> ToDictionary<T>(string key, T value)
    {
        try
        {
            var payload = new Dictionary<string, object>
            {
                { key, value }
            }; 
        } catch (Exception e)
        {
            return null;
        }
    }

    public T FromDictionary<T>(Dictionary<string, object> payload, string key)
    {
        try
        {
            JObject jObject = (JObject) payload[key];
            T t = jObject.ToObject<T>();
            return (t);
        }
        catch(Exception e) {
            return default(T);
        }
    }

1
虽然这段代码可能回答了问题,但是提供关于为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - baikho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接