Mappage d'un objet au dictionnaire et vice versa


91

Existe-t-il un moyen rapide et élégant de mapper un objet à un dictionnaire et vice versa?

Exemple:

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

devient

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

Le moyen le plus rapide serait de générer du code comme protobuf ... J'ai utilisé des arbres d'expression pour éviter la réflexion aussi souvent que possible
Konrad

Toutes les réponses auto-codées ci-dessous ne prennent pas en charge la conversion profonde et ne fonctionneront pas dans les applications réelles. Vous devriez utiliser une bibliothèque comme Newtonsoft comme suggéré par les plaques d'oreille
Cesar

Réponses:


175

En utilisant une réflexion et des génériques dans deux méthodes d'extension, vous pouvez y parvenir.

D'accord, d'autres ont pour la plupart fait la même solution, mais cela utilise moins de réflexion, ce qui est plus performant et bien plus lisible:

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

J'aime l'implémentation, j'ai commencé à l'utiliser, mais avez-vous des commentaires sur les performances? Je sais que la réflexion n'est pas la meilleure en matière de performance? Avez-vous des commentaires?
nolimit le

@nolimit En fait, je ne connais pas ses performances réelles, mais je suppose que ce n'est pas si mal (pire que d'éviter la réflexion, bien sûr ...). En fait, quelle est l'alternative? Selon les exigences des projets, il existe peut-être d'autres options, comme l'utilisation du typage dynamique qui est beaucoup plus rapide que la réflexion ... Qui sait! :)
Matías Fidemraizer

Eh bien, dans mon cas, l'alternative est aussi simple que de construire l'objet par affectation, mais je recherche un moyen soigné mais peu coûteux de construire un objet. Cela dit, j'ai trouvé cela, ce qui signifie pour moi qu'il n'est pas trop cher d'utiliser ce morceau de code.
nolimit le

@nolimit Ouais, il semble qu'il n'y ait pas de problème. Il s'agit simplement d'utiliser le bon outil pour le problème requis: D Dans votre cas, cela devrait fonctionner comme un charme. Je crois que je peux encore régler quelque chose dans le code!
Matías Fidemraizer

@nolimit Vérifie que maintenant GetType()n'est plus appelée someObjectpour chaque propriété dans la première méthode.
Matías Fidemraizer

31

Convertissez d'abord le dictionnaire en chaîne JSON avec Newtonsoft.

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

Désérialisez ensuite la chaîne JSON en votre objet

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

1
Simple et créatif!
kamalpreet

1
Utilisez cette approche avec prudence. Il enlèvera votre application s'il est utilisé pour plusieurs (centaines) de dictionnaires.
amackay11

12

La réflexion ne semble aider ici. J'ai fait un petit exemple de conversion d'objet en dictionnaire et 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;
}

Cela ne prend pas en charge l'imbrication.
Cesar le

10

Je recommande vivement le Castle DictionaryAdapter , facilement l'un des secrets les mieux gardés de ce projet. Il vous suffit de définir une interface avec les propriétés souhaitées, et en une seule ligne de code, l'adaptateur générera une implémentation, l'instanciera et synchronisera ses valeurs avec un dictionnaire que vous transmettez. Je l'utilise pour taper fortement mes AppSettings dans un projet web:

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

Notez que je n'avais pas besoin de créer une classe qui implémente IAppSettings - l'adaptateur le fait à la volée. De plus, même si dans ce cas je ne fais que lire, en théorie, si je définissais des valeurs de propriété sur appSettings, l'adaptateur maintiendrait le dictionnaire sous-jacent synchronisé avec ces modifications.


2
Je suppose que le «secret le mieux gardé» est mort d'une mort solitaire. Le lien Castle DictionaryAdapter ci-dessus, ainsi que les liens de téléchargement pour "DictionaryAdapter" sur castleproject.org vont tous à une page 404 :(
Shiva

1
Non! Il est toujours dans le château. J'ai corrigé le lien.
Gaspar Nagy

Castle était une grande bibliothèque qui a été négligée d'une manière ou d'une autre
bikeman868

5

La réflexion peut vous faire passer d'un objet à un dictionnaire en itérant sur les propriétés.

Pour aller dans l'autre sens, vous devrez utiliser un ExpandoObject dynamique (qui, en fait, hérite déjà de IDictionary, et a donc fait cela pour vous) en C #, à moins que vous ne puissiez déduire le type de la collection d'entrées dans le dictionnaire en quelque sorte.

Donc, si vous êtes dans .NET 4.0, utilisez un ExpandoObject, sinon vous avez beaucoup de travail à faire ...


4

Je pense que vous devriez utiliser la réflexion. Quelque chose comme ça:

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

Il prend votre dictionnaire, le parcourt en boucle et définit les valeurs. Vous devriez l'améliorer, mais c'est un début. Vous devriez l'appeler comme ceci:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);

Pour quoi vous voulez utiliser un activateur si vous pouvez utiliser la "nouvelle" contrainte générique? :)
Matías Fidemraizer

@ Matías Fidemraizer Vous avez absolument raison. Mon erreur. Ça a changé.
TurBas

Merci ur génial, un problème si une classe n'a pas certaines des propriétés qui sont dans le dictionnaire. Il devrait correspondre uniquement aux propriétés existantes dans certaines classes et en ignorer d'autres, à partir de maintenant, j'ai utilisé essayer d'attraper une meilleure option pour cela?
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

S'appuyant sur la réponse de Matías Fidemraizer, voici une version qui prend en charge la liaison aux propriétés d'objet autres que les chaînes.

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

Si vous utilisez Asp.Net MVC, jetez un œil à:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

qui est une méthode publique statique sur la classe System.Web.Mvc.HtmlHelper.


Je ne vais pas l'utiliser car il remplace "_" par "-". Et vous avez besoin de MVC. Utilisez la classe de routage pure: 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);
        }
    }

Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant la raison et / ou la manière dont ce code répond à la question améliore sa valeur à long terme.
baikho
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.