Mapowanie obiektu do słownika i odwrotnie

90

Czy jest jakiś elegancki, szybki sposób mapowania obiektu do słownika i odwrotnie?

Przykład:

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

staje się

SomeClass b = new SomeClass();
b.Id=1;
b.Name="Ahmad";
// ..........
Sawan
źródło
najszybszym sposobem byłoby generowanie kodu, jak protobuf ... Użyłem drzew wyrażeń, aby jak najczęściej unikać refleksji
Konrad
Wszystkie poniższe samodzielnie zakodowane odpowiedzi nie obsługują głębokiej konwersji i nie będą działać w rzeczywistych aplikacjach. Powinieneś użyć jakiejś biblioteki, takiej jak Newtonsoft, zgodnie z sugestią Earnerplates
Cesar

Odpowiedzi:

175

Możesz to osiągnąć, używając refleksji i typów ogólnych w dwóch metodach rozszerzających.

Owszem, inni zrobili w większości to samo rozwiązanie, ale wymaga to mniej refleksji, co jest bardziej wydajne i bardziej czytelne:

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
źródło
Podoba mi się implementacja, właściwie zacząłem z niej korzystać, ale czy masz jakieś uwagi na temat wydajności? Wiem, że refleksja nie jest najlepsza, jeśli chodzi o wykonanie? Masz jakieś komentarze?
nolimit
@nolimit Właściwie nie znam jego rzeczywistej wydajności, ale myślę, że nie jest tak źle (gorsze niż unikanie refleksji, oczywiście ...). Właściwie jaka jest alternatywa? W zależności od wymagań projektu, być może istnieją inne opcje, na przykład dynamiczne pisanie na klawiaturze, które jest znacznie szybsze niż odbicie ... Kto wie! :)
Matías Fidemraizer
cóż, w moim przypadku alternatywa jest tak prosta, jak zbudowanie obiektu przez przypisanie, ale szukam zgrabnego, ale niedrogiego sposobu na zbudowanie obiektu. Powiedziawszy to, znalazłem to, co dla mnie oznacza, że ​​użycie tego fragmentu kodu nie jest zbyt drogie.
nolimit
@nolimit Tak, wygląda na to, że nie ma problemu. Chodzi po prostu o użycie odpowiedniego narzędzia do wymaganego problemu: D W twoim przypadku powinno to działać jak urok. Wierzę, że mogę jeszcze coś dostroić w kodzie!
Matías Fidemraizer
@nolimit Sprawdź, czy w pierwszej metodzie GetType()nie jest już wywoływana someObjectdla każdej właściwości.
Matías Fidemraizer
30

Najpierw przekonwertuj słownik na ciąg JSON za pomocą Newtonsoft.

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

Następnie zdeserializuj ciąg JSON do swojego obiektu

var myobject = JsonConvert.DeserializeObject<AOCAdvancedSettings>(json);
learnerplates
źródło
1
Proste i kreatywne!
kamalpreet
1
Używaj tego podejścia z rozwagą. Spowoduje to spowolnienie aplikacji, jeśli będzie używane w wielu (setkach) słowników.
amackay11
12

Wydaje się, że tylko refleksja tutaj pomaga .. Zrobiłem mały przykład konwersji obiektu do słownika i 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;
}
Andrew Orsich
źródło
To nie obsługuje zagnieżdżania.
Cesar
10

Gorąco polecam Castle DictionaryAdapter , jeden z najlepiej strzeżonych sekretów tego projektu. Wystarczy zdefiniować interfejs z żądanymi właściwościami, aw jednym wierszu kodu adapter wygeneruje implementację, utworzy jej instancję i zsynchronizuje jej wartości z przekazanym słownikiem. Używam go do silnego wpisania moich ustawień AppSettings w projekt internetowy:

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

Zauważ, że nie musiałem tworzyć klasy, która implementuje IAppSettings - adapter robi to w locie. Ponadto, chociaż w tym przypadku tylko czytam, teoretycznie gdybym ustawiał wartości właściwości w appSettings, adapter utrzymywałby synchronizację bazowego słownika z tymi zmianami.

Todd Menier
źródło
2
Myślę, że „najlepiej strzeżony sekret” umarł samotną śmiercią. Powyższy link Castle DictionaryAdapter, jak również linki do pobierania „DictionaryAdapter” na stronie castleproject.org prowadzą do strony 404 :(
Shiva
1
Nie! Nadal jest w Castle.Core. Naprawiłem link.
Gaspar Nagy
Castle był wspaniałą biblioteką, którą jakoś przeoczono
bikeman868
5

Odbicie może przenieść Cię z obiektu do słownika poprzez iterację po właściwościach.

Aby przejść w drugą stronę, będziesz musiał użyć dynamicznego ExpandoObject (który w rzeczywistości dziedziczy już po IDictionary, a więc zrobił to za Ciebie) w C #, chyba że możesz wywnioskować typ z kolekcji wpisów w słownik jakoś.

Tak więc, jeśli jesteś w środowisku .NET 4.0, użyj ExpandoObject, w przeciwnym razie masz dużo do zrobienia ...

Masyw górski
źródło
4

Myślę, że powinieneś użyć refleksji. Coś takiego:

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

Pobiera słownik, przechodzi przez niego i ustawia wartości. Powinieneś to poprawić, ale to początek. Powinieneś to nazwać tak:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);
TurBas
źródło
Do czego chcesz użyć aktywatora, jeśli możesz użyć „nowego” ogólnego ograniczenia? :)
Matías Fidemraizer
@ Matías Fidemraizer Masz całkowitą rację. Mój błąd. Zmieniłem to.
TurBas
Dzięki, super, jeden problem, jeśli jakaś klasa nie ma niektórych właściwości, które są w słowniku. Powinien pasować tylko do właściwości, które istnieją w jakiejś klasie i pomijać inne, ponieważ teraz użyłem spróbować złapać lepszą opcję?
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
źródło
2

Opierając się na odpowiedzi Matíasa Fidemraizera, oto wersja obsługująca wiązanie z właściwościami obiektu innymi niż ciągi.

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
źródło
1

Jeśli używasz Asp.Net MVC, spójrz na:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

która jest statyczną metodą publiczną w klasie System.Web.Mvc.HtmlHelper.

magritte
źródło
Nie będę go używać, ponieważ zastępuje „_” przez „-”. Potrzebujesz MVC. Użyj czystej klasy 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);
        }
    }
Williams Abiola
źródło
Chociaż ten kod może odpowiedzieć na pytanie, zapewnia dodatkowy kontekst dotyczący tego, dlaczego i / lub jak ten kod odpowiada, poprawia jego długoterminową wartość.
baikho