Mapeando objeto para dicionário e vice-versa


94

Existe alguma maneira elegante e rápida de mapear um objeto para um dicionário e vice-versa?

Exemplo:

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

torna-se

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

a maneira mais rápida seria por geração de código como protobuf ... Usei árvores de expressão para evitar reflexão o mais rápido possível
Konrad

Todas as respostas codificadas a seguir não suportam conversão profunda e não funcionarão em aplicativos da vida real. Você deve usar alguma biblioteca como a Newtonsoft, conforme sugerido por earnerplates
Cesar

Respostas:


178

Usando alguma reflexão e genéricos em dois métodos de extensão, você pode conseguir isso.

Certo, outros fizeram basicamente a mesma solução, mas isso usa menos reflexão, o que é mais eficiente em termos de desempenho e muito mais legível:

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();
    }
}

Eu gosto da implementação, na verdade comecei a usá-la, mas você tem algum feedback sobre o desempenho? Eu sei que a reflexão não é a maior quando se trata de performance? Você tem algum comentário?
nolimit

@nolimit Na verdade, não sei seu desempenho real, mas acho que não é tão ruim (pior do que evitar a reflexão, é claro ...). Na verdade, qual é a alternativa? Dependendo dos requisitos do projeto, talvez haja outras opções, como usar a digitação dinâmica que é muito mais rápida do que a reflexão ... Quem sabe! :)
Matías Fidemraizer

bem, no meu caso, a alternativa é tão simples quanto construir o objeto por atribuição, mas estou procurando uma maneira bacana, porém barata, de construir um objeto. Dito isso, descobri que isso significa para mim que não é muito caro usar esse pedaço de código.
nolimit

@nolimit Sim, parece que não há problema. Trata-se apenas de usar a ferramenta certa para o problema necessário: D No seu caso, deve funcionar perfeitamente. Acredito que ainda posso ajustar algo no código!
Matías Fidemraizer

@nolimit Verifique se agora GetType()não é mais chamado someObjectpara cada propriedade no primeiro método.
Matías Fidemraizer

34

Converta o Dicionário para string JSON primeiro com Newtonsoft.

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

Em seguida, desserialize a string JSON para o seu objeto

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

1
Simples e criativo!
kamalpreet

1
Use essa abordagem com cuidado. Seu aplicativo ficará paralisado se for usado para muitas (centenas) de dicionários.
amackay11

12

Parece que a reflexão só ajuda aqui. Fiz um pequeno exemplo de conversão de objeto em dicionário e vice-versa:

[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;
}

Isso não suporta aninhamento.
Cesar

10

Eu recomendo fortemente o Castle DictionaryAdapter , facilmente um dos segredos mais bem guardados desse projeto. Você só precisa definir uma interface com as propriedades desejadas e, em uma linha de código, o adaptador irá gerar uma implementação, instanciá-la e sincronizar seus valores com um dicionário que você passar. Eu o uso para digitar fortemente meu AppSettings em um projeto web:

var appSettings =
  new DictionaryAdapterFactory().GetAdapter<IAppSettings>(ConfigurationManager.AppSettings);

Observe que não precisei criar uma classe que implemente IAppSettings - o adaptador faz isso instantaneamente. Além disso, embora neste caso eu esteja apenas lendo, em teoria, se eu estivesse definindo valores de propriedade em appSettings, o adaptador manteria o dicionário subjacente em sincronia com essas alterações.


2
Acho que o "segredo mais bem guardado" teve uma morte solitária. O link Castle DictionaryAdapter acima, bem como os links de download para "DictionaryAdapter" em castleproject.org, todos vão para uma página 404 :(
Shiva

1
Não! Ainda está no Castle.Core. Eu consertei o link.
Gaspar Nagy

Castle era uma ótima biblioteca que de alguma forma foi esquecida
bikeman868

5

O reflexo pode levá-lo de um objeto a um dicionário, iterando sobre as propriedades.

Para fazer o contrário, você terá que usar um ExpandoObject dinâmico (que, na verdade, já herda de IDictionary e, portanto, fez isso para você) em C #, a menos que você possa inferir o tipo da coleção de entradas no dicionário de alguma forma.

Então, se você está no terreno do .NET 4.0, use um ExpandoObject, caso contrário, você terá muito trabalho a fazer ...


4

Eu acho que você deve usar reflexão. Algo assim:

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;
}

Ele pega seu dicionário, faz um loop e define os valores. Você deveria fazer melhor, mas é um começo. Você deve chamá-lo assim:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);

Para o que você deseja usar um ativador, se puder usar a "nova" restrição genérica? :)
Matías Fidemraizer

@ Matías Fidemraizer Você está absolutamente certo. Meu erro. Mudou.
TurBas de

Obrigado, é incrível, um problema se alguma classe não tem algumas das propriedades que estão no dicionário. Deve corresponder apenas às propriedades existentes em alguma classe e pular outras, a partir de agora usei try catch alguma opção melhor para isso?
Sunil Chaudhary

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;
    }
}

3

Com base na resposta de Matías Fidemraizer, aqui está uma versão que suporta vinculação a propriedades de objetos diferentes de strings.

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);


                if (targetProperty.PropertyType == typeof (string))
                {
                    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)
            );
        }
    }
}

1

Se você estiver usando o Asp.Net MVC, dê uma olhada em:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

que é um método público estático na classe System.Web.Mvc.HtmlHelper.


Não vou usá-lo porque ele substitui "_" por "-". E você precisa de MVC. Use a classe Routing pura: 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
Embora este código possa responder à pergunta, fornecer contexto adicional sobre por que e / ou como este código responde à pergunta melhora seu valor a longo prazo.
baikho
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.