การแมปวัตถุกับพจนานุกรมและในทางกลับกัน


94

มีวิธีที่รวดเร็วในการแมปวัตถุกับพจนานุกรมหรือไม่และในทางกลับกัน?

ตัวอย่าง:

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

กลายเป็น

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

วิธีที่เร็วที่สุดคือการสร้างรหัสเช่น protobuf ... ฉันใช้ต้นไม้แสดงออกเพื่อหลีกเลี่ยงการสะท้อนให้บ่อยที่สุด
Konrad

คำตอบที่เข้ารหัสด้วยตนเองทั้งหมดด้านล่างนี้ไม่รองรับการแปลงเชิงลึกและจะไม่สามารถใช้งานได้ในชีวิตจริง คุณควรใช้ไลบรารีเช่น Newtonsoft ตามคำแนะนำของ earnerplates
Cesar

คำตอบ:


177

การใช้การสะท้อนและข้อมูลทั่วไปในสองวิธีการขยายคุณสามารถทำได้

ถูกต้องคนอื่น ๆ ส่วนใหญ่ใช้วิธีแก้ปัญหาเดียวกัน แต่สิ่งนี้ใช้การสะท้อนน้อยกว่าซึ่งฉลาดกว่าและอ่านง่ายกว่า:

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

34

แปลง Dictionary เป็นสตริง 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;
}

สิ่งนี้ไม่สนับสนุนการทำรัง
Cesar

10

ฉันขอแนะนำCastle DictionaryAdapterซึ่งเป็นหนึ่งในความลับที่ดีที่สุดของโครงการนั้นอย่างง่ายดาย คุณต้องกำหนดอินเทอร์เฟซที่มีคุณสมบัติที่คุณต้องการเท่านั้นและในโค้ดหนึ่งบรรทัดอะแดปเตอร์จะสร้างการใช้งานสร้างอินสแตนซ์และซิงโครไนซ์ค่ากับพจนานุกรมที่คุณส่งผ่านฉันใช้มันเพื่อพิมพ์ AppSettings ของฉันอย่างรุนแรงใน โครงการเว็บ:

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

โปรดทราบว่าฉันไม่จำเป็นต้องสร้างคลาสที่ใช้ IAppSettings - อะแด็ปเตอร์ทำแบบนั้นได้ทันที นอกจากนี้แม้ว่าในกรณีนี้ฉันกำลังอ่านอยู่ แต่ในทางทฤษฎีหากฉันตั้งค่าคุณสมบัติใน appSettings อแด็ปเตอร์จะทำให้พจนานุกรมที่อยู่ภายใต้ซิงค์กับการเปลี่ยนแปลงเหล่านั้น


2
ฉันเดาว่า "ความลับที่ดีที่สุด" เสียชีวิตอย่างโดดเดี่ยว ลิงก์ Castle DictionaryAdapter ด้านบนรวมถึงลิงก์ดาวน์โหลดสำหรับ "DictionaryAdapter" ในcastleproject.orgทั้งหมดไปที่หน้า 404 :(
Shiva

1
ไม่! มันยังอยู่ใน Castle.Core ฉันได้แก้ไขลิงค์แล้ว
Gaspar Nagy

Castle เป็นห้องสมุดชั้นเยี่ยมที่ถูกมองข้ามไป
bikeman868

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

ขอบคุณเยี่ยมมากปัญหาหนึ่งถ้า someclass ไม่มีคุณสมบัติบางอย่างที่อยู่ในพจนานุกรม ควรจับคู่คุณสมบัติที่มีอยู่ใน someclass เท่านั้นและข้ามสิ่งอื่น ๆ ณ ตอนนี้ฉันใช้ลองจับตัวเลือกที่ดีกว่านี้ไหม
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;
    }
}

3

จากคำตอบของ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 ใช้คลาส Routing บริสุทธิ์: 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);
        }
    }

1
แม้ว่ารหัสนี้อาจตอบคำถาม แต่การให้บริบทเพิ่มเติมเกี่ยวกับสาเหตุและ / หรือวิธีที่รหัสนี้ตอบคำถามช่วยเพิ่มมูลค่าในระยะยาว
baikho
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.