Mappatura dell'oggetto al dizionario e viceversa


90

Esiste un modo rapido ed elegante per mappare un oggetto su un dizionario e viceversa?

Esempio:

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

diventa

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

il modo più veloce sarebbe la generazione di codice come protobuf ... Ho usato gli alberi delle espressioni per evitare il riflesso il più spesso possibile
Konrad

Tutte le risposte auto-codificate di seguito non supportano la conversione profonda e non funzioneranno nelle applicazioni della vita reale. Dovresti usare una libreria come Newtonsoft come suggerito da earnerplates
Cesar

Risposte:


175

Usando un po 'di riflessione e generici in due metodi di estensione puoi farlo.

Giusto, altri hanno fatto per lo più la stessa soluzione, ma questo utilizza meno riflessione che è più saggio in termini di prestazioni e molto più leggibile:

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

Mi piace l'implementazione, ho effettivamente iniziato a usarlo, ma hai qualche feedback sulle prestazioni? So che la riflessione non è la massima quando si parla di performance? Vuoi aggiungere un commento?
nolimit

@nolimit In realtà non conosco le sue prestazioni effettive, ma immagino non sia poi così male (peggio che evitare la riflessione, ovviamente ...). Qual è infatti l'alternativa? A seconda dei requisiti del progetto, forse ci sono altre opzioni, come usare la digitazione dinamica che è molto più veloce della riflessione ... Chi lo sa! :)
Matías Fidemraizer

beh nel mio caso l'alternativa è semplice come costruire l'oggetto per assegnazione, ma sto cercando un modo pulito ma poco costoso per costruire un oggetto. Detto questo, ho scoperto che questo significa per me che non è troppo costoso usare quel pezzo di codice.
nolimit

@nolimit Sì, sembra che non ci siano problemi. Si tratta solo di utilizzare lo strumento giusto per il problema richiesto: D Nel tuo caso, dovrebbe funzionare a meraviglia. Credo di poter ancora mettere a punto qualcosa nel codice!
Matías Fidemraizer

@nolimit Controlla che ora GetType()non sia più richiamato someObjectper ogni proprietà nel primo metodo.
Matías Fidemraizer

30

Converti prima il dizionario in stringa JSON con Newtonsoft.

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

Quindi deserializza la stringa JSON nel tuo oggetto

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

1
Semplice e creativo!
kamalpreet

1
Usa questo approccio con cautela. Impantanerà la tua applicazione se usata per molti (centinaia) di dizionari.
amackay11

12

Sembra che la riflessione aiuti solo qui .. Ho fatto un piccolo esempio di conversione di oggetti in dizionario e 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;
}

Questo non supporta l'annidamento.
Cesar

10

Consiglio vivamente il Castle DictionaryAdapter , facilmente uno dei segreti meglio custoditi di quel progetto. Devi solo definire un'interfaccia con le proprietà che desideri e in una riga di codice l'adattatore genererà un'implementazione, la istanzerà e sincronizzerà i suoi valori con un dizionario che passi. Lo uso per digitare fortemente le mie AppSettings in un progetto web:

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

Si noti che non era necessario creare una classe che implementasse IAppSettings: l'adattatore lo fa al volo. Inoltre, sebbene in questo caso sto solo leggendo, in teoria se stessi impostando i valori delle proprietà su appSettings, l'adattatore manterrebbe il dizionario sottostante sincronizzato con tali modifiche.


2
Immagino che il "segreto meglio custodito" sia morto di solitudine. Il link Castle DictionaryAdapter sopra, così come i link per il download di "DictionaryAdapter" su castleproject.org vanno tutti a una pagina 404 :(
Shiva

1
No! È ancora nel castello. Core. Ho corretto il collegamento.
Gaspar Nagy

Il castello era una grande biblioteca che in qualche modo è stata trascurata
bikeman868 il

5

La riflessione può portarti da un oggetto a un dizionario iterando sulle proprietà.

Per andare dall'altra parte, dovrai usare un ExpandoObject dinamico (che, in effetti, eredita già da IDictionary, e quindi lo ha fatto per te) in C #, a meno che tu non possa dedurre il tipo dalla raccolta di voci nel dizionario in qualche modo.

Quindi, se sei in .NET 4.0, usa un ExpandoObject, altrimenti hai molto lavoro da fare ...


4

Penso che dovresti usare la riflessione. Qualcosa come questo:

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

Prende il dizionario e lo scorre e imposta i valori. Dovresti migliorarlo, ma è un inizio. Dovresti chiamarlo così:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);

Per cosa vuoi usare un attivatore se puoi usare il "nuovo" vincolo generico? :)
Matías Fidemraizer

@ Matías Fidemraizer Hai assolutamente ragione. Errore mio. L'ho cambiato.
TurBas

Grazie sei fantastico, un problema se qualche classe non ha alcune delle proprietà che sono nel dizionario. Dovrebbe corrispondere solo alle proprietà esistenti in una certa classe e saltare altre, a partire da ora ho usato per provare a catturare un'opzione migliore per questo?
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;
    }
}

2

Basandosi sulla risposta di Matías Fidemraizer, ecco una versione che supporta l'associazione a proprietà degli oggetti diverse dalle stringhe.

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 stai usando Asp.Net MVC, dai un'occhiata a:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

che è un metodo pubblico statico nella classe System.Web.Mvc.HtmlHelper.


Non lo userò perché sostituisce "_" con "-". E hai bisogno di MVC. Usa la pura classe 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);
        }
    }

Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo sul perché e / o come questo codice risponde alla domanda ne migliora il valore a lungo termine.
baikho
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.