Memetakan objek ke kamus dan sebaliknya

91

Adakah cara cepat yang elegan untuk memetakan objek ke kamus dan sebaliknya?

Contoh:

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

menjadi

SomeClass b = new SomeClass();
b.Id=1;
b.Name="Ahmad";
// ..........
Sawan
sumber
cara tercepat adalah dengan pembuatan kode seperti protobuf ... Saya menggunakan pohon ekspresi untuk menghindari refleksi sesering mungkin
Konrad
Semua jawaban yang dikodekan sendiri di bawah ini tidak mendukung konversi mendalam dan tidak akan berfungsi dalam aplikasi kehidupan nyata. Anda harus menggunakan beberapa perpustakaan seperti Newtonsoft seperti yang disarankan oleh earnerplates
Cesar

Jawaban:

175

Menggunakan beberapa refleksi dan generik dalam dua metode ekstensi, Anda dapat mencapainya.

Benar, sebagian besar orang lain melakukan solusi yang sama, tetapi ini menggunakan lebih sedikit refleksi yang lebih bijak dalam kinerja dan jauh lebih mudah dibaca:

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
sumber
Saya suka penerapannya, saya sebenarnya mulai menggunakannya, tetapi apakah Anda memiliki umpan balik tentang kinerja? Saya tahu bahwa refleksi bukanlah yang terbesar dalam hal kinerja? apakah kamu punya komentar
nolimit
@nolimit Sebenarnya saya tidak tahu kinerja sebenarnya, tapi saya rasa tidak seburuk itu (lebih buruk dari menghindari refleksi, tentu saja ...). Sebenarnya, apa alternatifnya? Bergantung pada kebutuhan proyek, mungkin ada opsi lain, seperti menggunakan pengetikan dinamis yang jauh lebih cepat daripada refleksi ... Siapa tahu! :)
Matías Fidemraizer
baik dalam kasus saya alternatifnya adalah yang sederhana seperti membangun objek dengan tugas, tapi saya sedang mencari cara yang rapi namun murah untuk membangun sebuah objek. Karena itu, saya telah menemukan ini yang berarti bagi saya bahwa tidak terlalu mahal untuk menggunakan potongan kode itu.
nolimit
@nolimit Ya, sepertinya tidak ada masalah. Ini hanya tentang menggunakan alat yang tepat untuk masalah yang diperlukan: D Dalam kasus Anda, alat ini akan bekerja dengan baik. Saya yakin saya masih bisa menyetel sesuatu di kode!
Matías Fidemraizer
@nolimit Periksa bahwa sekarang GetType()tidak dipanggil someObjectuntuk setiap properti lagi di metode pertama.
Matías Fidemraizer
31

Konversikan Dictionary ke string JSON terlebih dahulu dengan Newtonsoft.

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

Kemudian deserialisasi string JSON ke objek Anda

var myobject = JsonConvert.DeserializeObject<AOCAdvancedSettings>(json);
pelat pelajar
sumber
1
Sederhana dan kreatif!
kamalpreet
1
Gunakan pendekatan ini dengan hati-hati. Ini akan menghambat aplikasi Anda jika digunakan untuk banyak (ratusan) kamus.
amackay11
12

Sepertinya refleksi hanya membantu di sini .. Saya telah melakukan contoh kecil untuk mengubah objek ke kamus dan sebaliknya:

[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
sumber
Ini tidak mendukung pembuatan sarang.
Cesar
10

Saya sangat merekomendasikan Castle DictionaryAdapter , salah satu rahasia proyek yang paling terjaga. Anda hanya perlu mendefinisikan antarmuka dengan properti yang Anda inginkan, dan dalam satu baris kode, adaptor akan menghasilkan implementasi, membuat instance-nya, dan menyinkronkan nilainya dengan kamus yang Anda berikan. Saya menggunakannya untuk mengetik AppSettings saya dengan kuat di sebuah proyek web:

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

Perhatikan bahwa saya tidak perlu membuat kelas yang mengimplementasikan IAppSettings - adaptor melakukannya dengan cepat. Juga, meskipun dalam kasus ini saya hanya membaca, secara teori jika saya menyetel nilai properti di appSettings, adaptor akan menjaga kamus yang mendasari tetap sinkron dengan perubahan tersebut.

Todd Menier
sumber
2
Kurasa "rahasia yang paling dijaga" mati karena kesepian. Tautan Castle DictionaryAdapter di atas, serta tautan Unduh untuk "DictionaryAdapter" di castleproject.org semuanya masuk ke Halaman 404 :(
Shiva
1
Tidak! Itu masih di Castle.Core. Saya telah memperbaiki tautannya.
Gaspar Nagy
Castle adalah perpustakaan besar yang entah bagaimana diabaikan
bikeman868
5

Refleksi dapat membawa Anda dari sebuah objek ke kamus dengan melakukan iterasi pada properti.

Untuk pergi ke arah lain, Anda harus menggunakan ExpandoObject dinamis (yang sebenarnya sudah mewarisi dari IDictionary, dan telah melakukannya untuk Anda) di C #, kecuali Anda dapat menyimpulkan jenis dari kumpulan entri di kamus entah bagaimana.

Jadi, jika Anda berada di lahan .NET 4.0, gunakan ExpandoObject, jika tidak, Anda memiliki banyak pekerjaan yang harus dilakukan ...

Massif
sumber
4

Saya pikir Anda harus menggunakan refleksi. Sesuatu seperti ini:

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

Itu mengambil kamus Anda dan mengulanginya dan menetapkan nilainya. Anda harus membuatnya lebih baik tetapi ini adalah permulaan. Anda harus menyebutnya seperti ini:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);
TurBas
sumber
Untuk apa Anda ingin menggunakan aktivator jika Anda dapat menggunakan batasan umum "baru"? :)
Matías Fidemraizer
@ Matías Fidemraizer Anda benar sekali. Kesalahanku. Berubah.
TurBas
Terima kasih Anda luar biasa, satu masalah jika beberapa kelas tidak memiliki beberapa properti yang ada dalam kamus. Ini harus cocok hanya dengan properti yang ada di beberapa kelas dan melewati yang lain, seperti yang sekarang saya gunakan coba tangkap opsi yang lebih baik untuk ini?
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
sumber
3

Berdasarkan jawaban Matías Fidemraizer, berikut adalah versi yang mendukung pengikatan ke properti objek selain string.

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
sumber
1

Jika Anda menggunakan Asp.Net MVC, lihat:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

yang merupakan metode publik statis di kelas System.Web.Mvc.HtmlHelper.

magritte.dll
sumber
Saya tidak akan menggunakannya karena mengganti "_" dengan "-". Dan Anda membutuhkan MVC. Gunakan kelas Routing murni: 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
sumber
Meskipun kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang mengapa dan / atau bagaimana kode ini menjawab pertanyaan tersebut meningkatkan nilai jangka panjangnya.
baikho