Erric J Manderin
Erric J Manderin

Reputation: 1096

Mapping Dynamic ExpandoObject To Object

Recently I had to use ExpandoObject in my application, So I want to know how could I use my old Mapper to also map from Dynamic ExpandoOnjects, because some how it does not map the fields, and properties from an Expando.

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class Mapper
{
    private static readonly Dictionary<KeyValuePair<Type, Type>, object> Maps = new Dictionary<KeyValuePair<Type, Type>, object>();

    private static PropertyInfo[] _fromProperties;
    private static PropertyInfo[] _toProperties;
    private static FieldInfo[] _fromFields;
    private static FieldInfo[] _toFields;

    // Rules...
    private static readonly Func<PropertyInfo, PropertyInfo, bool> MatchingProps = (t1, t2) => t1.Name == t2.Name && t1.PropertyType.Name == t2.PropertyType.Name;
    private static readonly Func<FieldInfo, FieldInfo, bool> MatchingFields = (t1, t2) => t1.Name == t2.Name && t1.FieldType.Name == t2.FieldType.Name;
    private static readonly Func<PropertyInfo, FieldInfo, bool> MatchingPropertyToField = (t1, t2) => t1.Name == t2.Name && t1.PropertyType.Name == t2.FieldType.Name;
    private static readonly Func<FieldInfo, PropertyInfo, bool> MatchingFieldToProperty = (t1, t2) => t1.Name == t2.Name && t1.FieldType.Name == t2.PropertyType.Name;

    public static void AddMap<TFrom, TTo>(Action<TFrom, TTo> map = null)
            where TFrom : class
            where TTo : class { Maps.Add(new KeyValuePair<Type, Type>(typeof(TFrom), typeof(TTo)), map); }

    public static void Map<TFromType, TOType>(TFromType @from, TOType to)
    {
        var key = new KeyValuePair<Type, Type>(typeof(TFromType), typeof(TOType));
        var map = (Action<TFromType, TOType>)Maps[key];

        bool hasMapping = Maps.Any(x => x.Key.Equals(key));

        if (!hasMapping)
            throw new Exception(string.Format("No map defined for {0} => {1}", typeof(TFromType).Name, typeof(TOType).Name));

        Type tFrom = typeof(TFromType);
        Type tTo = typeof(TOType);

        _fromProperties = tFrom.GetProperties();
        _fromFields = tFrom.GetFields();
        _toProperties = tTo.GetProperties();
        _toFields = tTo.GetFields();

        SyncProperties(@from, to);
        SyncFields(@from, to);

        if (!Equals(map, null))
            map(@from, to);
    }

    private static void SyncProperties<TFromType, TOType>(TFromType objFrom, TOType objTo)
    {
        PropertyInfo[] fromProperties = _fromProperties;
        PropertyInfo[] toProperties = _toProperties;
        FieldInfo[] toFields = _toFields;

        if (fromProperties != null && fromProperties.Any()) {
            foreach (PropertyInfo fromProperty in fromProperties) {
                if (toProperties.Any(x => x.Name == fromProperty.Name)) {
                    PropertyInfo destinationProperty = toProperties.FirstOrDefault(x => x.Name == fromProperty.Name);

                    if (MatchingProps(fromProperty, destinationProperty)) {
                        object val = fromProperty.GetValue(objFrom, null);
                        if (Equals(val, null)) continue;
                        if (!Equals(destinationProperty, null)) destinationProperty.SetValue(objTo, Convert.ChangeType(val, fromProperty.PropertyType), null);
                    }
                }

                if (toFields.Any(x => x.Name == fromProperty.Name)) {
                    FieldInfo destinationField = toFields.FirstOrDefault(x => x.Name == fromProperty.Name);

                    if (MatchingPropertyToField(fromProperty, destinationField)) {
                        object val = fromProperty.GetValue(objFrom, null);
                        if (Equals(val, null)) continue;
                        if (!Equals(destinationField, null)) destinationField.SetValue(objTo, val);
                    }
                }
            }
        }
    }

    private static void SyncFields<TFromType, TOType>(TFromType objFrom, TOType objTo)
    {
        FieldInfo[] fromFields = _fromFields;
        FieldInfo[] toFields = _toFields;
        PropertyInfo[] toProperties = _toProperties;

        if (fromFields != null && fromFields.Any()) {
            foreach (FieldInfo fromField in fromFields) {
                if (toFields.Any(x => x.Name == fromField.Name)) {
                    FieldInfo destinationField = toFields.FirstOrDefault(x => x.Name == fromField.Name);

                    if (MatchingFields(fromField, destinationField)) {
                        object val = fromField.GetValue(objFrom);
                        if (Equals(val, null)) continue;
                        if (!Equals(destinationField, null)) destinationField.SetValue(objTo, val);
                    }
                }

                if (toProperties.Any(x => x.Name == fromField.Name)) {
                    PropertyInfo destinationProperty = toProperties.FirstOrDefault(x => x.Name == fromField.Name);

                    if (MatchingFieldToProperty(fromField, destinationProperty)) {
                        object val = fromField.GetValue(objFrom);
                        if (Equals(val, null)) continue;
                        if (!Equals(destinationProperty, null)) destinationProperty.SetValue(objTo, val, null);
                    }
                }
            }
        }
    }
}

Usage:

static void Main()
{
    dynamic o = new ExpandoObject();
    o.Name = "Pouce";
    o.Age = 42;
    o.Rank = new Rank
             {
                     Name = Ranks.Major
             };
    o.Guid = new Guid();

    Soldier soldier = new Soldier();
    Mapper.AddMap<ExpandoObject, Soldier>();
    Mapper.Map(o, soldier);

    Console.ReadLine();
}

public class Soldier
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Rank Rank { get; set; }
    public Guid Guid { get; set; }
}

public class Rank
{
    public Ranks Name { get; set; }
}

public enum Ranks
{
    Private,
    Specialist,
    Corporal,
    Sergeant,
    Captain,
    Major,
    Colonel,
    General
}

Upvotes: 2

Views: 6881

Answers (1)

YK1
YK1

Reputation: 7622

This is because the properties of ExpandoObject are not real .NET properties. By using ExpandoObject with dynamic keyword you are able to give any arbitrary properties to the object and this is handled by the DLR at runtime. You cannot use the regular static type's methods GetProperties() and GetFields() on the dynamic instance of ExpandoObject.

To extend your Mapper to consume ExpandObject you will have to consider it as a special case.

See my answer here it may help you.

EDIT: Reflection with ExpandoObject is not difficult. However, you wont get a set of PropertyInfo or FieldInfo from it. You just get KeyValuePair<string, object>. So, you may have to add an array of such KeyValuePair's to store the info.

In your Map() method, you can check for ExpandoObject as special case:

if (tFrom == typeof(ExpandoObject)) {
    _fromExpandoProperties = @from.Select(kvp => kvp).ToArray();
    // where _fromExpandoProperties is of type KeyValuePair<string, object>[]
} else {
    _fromProperties = tFrom.GetProperties();
}

To get the name and value of the property you can then use .Key and .Value instead of .PropertyType.Name and .GetValue().

You will have to factor this specialization in all your code.

Upvotes: 2

Related Questions