Asignación de objeto a diccionario y viceversa

90

¿Existe alguna forma elegante y rápida de asignar un objeto a un diccionario y viceversa?

Ejemplo:

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

se convierte en

SomeClass b = new SomeClass();
b.Id=1;
b.Name="Ahmad";
// ..........
Sawan
fuente
La forma más rápida sería mediante la generación de código como protobuf ... Usé árboles de expresión para evitar la reflexión con la mayor frecuencia posible
Konrad
Todas las respuestas autocodificadas a continuación no admiten la conversión profunda y no funcionarán en aplicaciones de la vida real. Debería usar alguna biblioteca como Newtonsoft como lo sugiere earnerplates
Cesar

Respuestas:

175

Usando algo de reflexión y genéricos en dos métodos de extensión, puede lograrlo.

Correcto, otros hicieron casi la misma solución, pero esto usa menos reflexión, lo que es más en cuanto al rendimiento y mucho más legible:

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();
    }
}
Matías Fidemraizer
fuente
Me gusta la implementación, de hecho comencé a usarla, pero ¿tienes algún comentario sobre el rendimiento? Sé que la reflexión no es la mejor cuando se trata de rendimiento. ¿Tienes algún comentario?
nolimit
@nolimit En realidad, no sé su rendimiento real, pero supongo que no es tan malo (peor que evitar la reflexión, por supuesto ...). De hecho, ¿cuál es la alternativa? Dependiendo de los requisitos del proyecto, tal vez haya otras opciones, como usar la escritura dinámica que es mucho más rápida que la reflexión ... ¡Quién sabe! :)
Matías Fidemraizer
Bueno, en mi caso, la alternativa es tan simple como construir el objeto por asignación, pero estoy buscando una forma ordenada pero económica de construir un objeto. Habiendo dicho eso, he encontrado esto, lo que para mí significa que no es demasiado caro usar ese fragmento de código.
nolimit
@nolimit Sí, parece que no hay problema. Se trata simplemente de usar la herramienta adecuada para el problema requerido: D En su caso, debería funcionar a la perfección. ¡Creo que todavía puedo ajustar algo en el código!
Matías Fidemraizer
@nolimit Compruebe que ahora GetType()ya no se invoca someObjectpara cada propiedad en el primer método.
Matías Fidemraizer
30

Primero convierta el diccionario a una cadena JSON con Newtonsoft.

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

Luego deserialice la cadena JSON a su objeto

var myobject = JsonConvert.DeserializeObject<AOCAdvancedSettings>(json);
placas de aprendizaje
fuente
1
¡Simple y creativo!
kamalpreet
1
Utilice este enfoque con precaución. Atascará su aplicación si se usa para muchos (cientos) de diccionarios.
amackay11
12

Parece que la reflexión solo ayuda aquí ... He hecho un pequeño ejemplo de conversión de objeto a diccionario y viceversa:

[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;
}
Andrew Orsich
fuente
Esto no admite el anidamiento.
Cesar
10

Recomiendo encarecidamente Castle DictionaryAdapter , fácilmente uno de los secretos mejor guardados de ese proyecto. Solo necesita definir una interfaz con las propiedades que desea, y en una línea de código, el adaptador generará una implementación, la instanciará y sincronizará sus valores con un diccionario que ingrese. Lo uso para escribir fuertemente mis AppSettings en un proyecto web:

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

Tenga en cuenta que no necesitaba crear una clase que implemente IAppSettings; el adaptador lo hace sobre la marcha. Además, aunque en este caso solo estoy leyendo, en teoría si estuviera configurando valores de propiedad en appSettings, el adaptador mantendría el diccionario subyacente sincronizado con esos cambios.

Todd Menier
fuente
2
Supongo que el "secreto mejor guardado" tuvo una muerte solitaria. El enlace Castle DictionaryAdapter anterior, así como los enlaces de descarga para "DictionaryAdapter" en castleproject.org, van a una página 404 :(
Shiva
1
¡No! Todavía está en el Castle.Core. He arreglado el enlace.
Gaspar Nagy
Castle era una gran biblioteca que se pasó por alto de alguna manera
bikeman868
5

La reflexión puede llevarlo de un objeto a un diccionario iterando sobre las propiedades.

Para ir al revés, tendrá que usar un ExpandoObject dinámico (que, de hecho, ya hereda de IDictionary, y por lo tanto lo ha hecho por usted) en C #, a menos que pueda inferir el tipo de la colección de entradas en el diccionario de alguna manera.

Entonces, si está en la tierra de .NET 4.0, use un ExpandoObject, de lo contrario, tiene mucho trabajo por hacer ...

Macizo
fuente
4

Creo que deberías usar la reflexión. Algo como esto:

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

Toma su diccionario, lo recorre y establece los valores. Deberías mejorarlo, pero es un comienzo. Deberías llamarlo así:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);
TurBas
fuente
¿Para qué quieres usar un activador si puedes usar la "nueva" restricción genérica? :)
Matías Fidemraizer
@ Matías Fidemraizer Tienes toda la razón. Mi error. Lo cambié.
TurBas
Gracias, es increíble, un problema si alguna clase no tiene algunas de las propiedades que están en el diccionario. Debería coincidir solo con las propiedades que existen en alguna clase y omitir otras, a partir de ahora usé intentar capturar alguna opción mejor para esto
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;
    }
}
Sawan
fuente
2

Sobre la base de la respuesta de Matías Fidemraizer, aquí hay una versión que admite la vinculación a propiedades de objetos que no sean cadenas.

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)
            );
        }
    }
}
Daniel Lewis
fuente
1

Si está utilizando Asp.Net MVC, eche un vistazo a:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

que es un método público estático en la clase System.Web.Mvc.HtmlHelper.

magritte
fuente
No lo usaré ya que reemplaza "_" por "-". Y necesitas MVC. Utilice la clase de enrutamiento 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);
        }
    }
Williams Abiola
fuente
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional sobre por qué y / o cómo este código responde a la pregunta mejora su valor a largo plazo.
baikho