Зіставлення об'єкта зі словником і навпаки


90

Чи є якийсь елегантний швидкий спосіб зіставити об’єкт зі словником і навпаки?

Приклад:

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

стає

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

найшвидший спосіб - це генерація коду, як протобуф ... Я використовував дерева виразів, щоб якомога частіше уникати відображення
Конрад

Усі самокодовані відповіді нижче не підтримують глибоке перетворення і не будуть працювати в реальних програмах. Вам слід скористатися такою бібліотекою, як Newtonsoft, як запропонував earnerplates
Цезар,

Відповіді:


175

Використовуючи деякі роздуми та загальні засоби у двох методах розширення, ви можете цього досягти.

Правильно, інші зробили в основному те саме рішення, але тут використовується менше роздумів, що є більш продуктивним і набагато читабельнішим:

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

Мені подобається реалізація, я насправді почав користуватися нею, але чи маєте Ви відгук про ефективність? Я знаю, що роздуми - це не найбільше, коли справа стосується продуктивності? у вас є коментарі?
nolimit

@nolimit Насправді я не знаю його фактичної ефективності, але, мабуть, це не так вже й погано (найгірше, ніж уникати роздумів, звичайно ...). Насправді, яка альтернатива? Залежно від вимог проектів, можливо, є інші варіанти, наприклад, використання динамічного набору тексту, який набагато швидший, ніж відображення ... Хто знає! :)
Matías Fidemraizer

ну в моєму випадку альтернатива така проста, як побудова об'єкта за призначенням, але я шукаю акуратний, але недорогий спосіб побудови об'єкта. Сказавши це, я знайшов це, що означає для мене, що використовувати цей фрагмент коду не надто дорого.
nolimit

@nolimit Так, здається, немає проблем. Йдеться лише про використання відповідного інструменту для вирішення необхідної проблеми: D У вашому випадку це має спрацювати як шарм. Я вважаю, що я ще можу щось налаштувати в коді!
Matías Fidemraizer

@nolimit Переконайтеся, що в першому методі зараз GetType()більше не викликається someObjectкожна властивість.
Matías Fidemraizer

30

Перетворіть Словник у рядок JSON спочатку за допомогою Newtonsoft.

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

Потім десеріалізуйте рядок JSON для вашого об’єкта

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

1
Просто і креативно!
kamalpreet

1
Використовуйте цей підхід з обережністю. Це застосує вашу програму, якщо вона використовується для багатьох (сотень) словників.
amackay11

12

Здається, роздуми тут лише допомагають .. Я зробив невеликий приклад перетворення об’єкта в словник і навпаки:

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

Це не підтримує вкладеність.
Сезар

10

Я настійно рекомендую Castle DictionaryAdapter , який є одним із найкраще зберігаються секретів цього проекту. Вам потрібно лише визначити інтерфейс із властивостями, які ви хочете, і в одному рядку коду адаптер згенерує реалізацію, створить екземпляр та синхронізує її значення зі словником, який ви вводите. Я використовую його, щоб сильно ввести мої AppSettings в веб-проект:

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

Зверніть увагу, що мені не потрібно було створювати клас, який реалізує IAppSettings - адаптер робить це на льоту. Крім того, хоча в цьому випадку я лише читаю, теоретично, якби я встановлював значення властивостей на appSettings, адаптер буде синхронізувати базовий словник із цими змінами.


2
Гадаю, "найкраще зберігається таємниця" померла самотньою смертю. Посилання Castle DictionaryAdapter вище, а також посилання для завантаження "DictionaryAdapter" на castleproject.org переходять на сторінку 404 :(
Шива,

1
Немає! Це все ще в Замку. Ядро. Я зафіксував посилання.
Гаспар Надь

Замок був чудовою бібліотекою, яку якось не
помічали

5

Відображення може перенести вас від об’єкта до словника, перебираючи властивості.

Щоб піти іншим шляхом, вам доведеться використовувати динамічний ExpandoObject (який насправді вже успадковується від IDictionary, і це зробив за вас) у C #, якщо ви не можете зробити висновок про тип із колекції записів у словник якось.

Отже, якщо ви перебуваєте у .NET 4.0, використовуйте ExpandoObject, інакше у вас буде багато роботи ...


4

Думаю, вам слід використовувати роздуми. Щось на зразок цього:

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

Для чого ви хочете використовувати активатор, якщо ви можете використовувати "нове" загальне обмеження? :)
Matías Fidemraizer

@ Matías Fidemraizer Ви абсолютно праві. Моя помилка. Змінив його.
TurBas

Дякую, чудово, одне питання, якщо якийсь клас не має деяких властивостей, які є у словнику. Він повинен відповідати лише властивостям, які існують у якомусь класі, і пропускати інші, оскільки зараз я використовував спробувати зловити якийсь кращий варіант для цього?
Суніл Чаудхарі,

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

Спираючись на відповідь 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);


                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

Якщо ви використовуєте Asp.Net MVC, тоді погляньте на:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

що є статичним загальнодоступним методом у класі System.Web.Mvc.HtmlHelper.


Я не буду використовувати його, оскільки він замінює "_" на "-". А вам потрібен MVC. Використовуйте чистий клас маршрутизації: 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);
        }
    }

Хоча цей код може відповісти на питання, надаючи додатковий контекст щодо того, чому та / або як цей код відповідає на питання, покращує його довгострокове значення.
baikho
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.