HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Mapping ExpandoObject to another object type

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
expandoobjecttypeanothermappingobject

Problem

I am working on a helper method that maps properties from an ExpandoObject to a user supplied object and was wondering if the code could be cleaned up or made any more efficient. It currently has the correct behaviour from a simple test.

public static class Mapper
{
    public static void Map(ExpandoObject source, T destination)
    {
        IDictionary dict = source;
        var type = destination.GetType();

        foreach (var prop in type.GetProperties())
        {
            var lower = prop.Name.ToLower();
            var key = dict.Keys.SingleOrDefault(k => k.ToLower() == lower);

            if (key != null)
            {
                prop.SetValue(destination, dict[key], null);
            }
        }
    }
}


Full test can be seen here. There is currently no type checking. Would this be next to add?

Solution

I've come up with a few changes that should actually speed it up.

// By using a generic class we can take advantage
// of the fact that .NET will create a new generic type
// for each type T. This allows us to avoid creating
// a dictionary of Dictionary
// for each type T. We also avoid the need for the 
// lock statement with every call to Map.
public static class Mapper
    // We can only use reference types
    where T : class
{
    private static readonly Dictionary _propertyMap;

    static Mapper()
    {
        // At this point we can convert each
        // property name to lower case so we avoid 
        // creating a new string more than once.
        _propertyMap = 
            typeof(T)
            .GetProperties()
            .ToDictionary(
                p => p.Name.ToLower(), 
                p => p
            );
    }

    public static void Map(ExpandoObject source, T destination)
    {
        // Might as well take care of null references early.
        if (source == null)
            throw new ArgumentNullException("source");
        if (destination == null)
            throw new ArgumentNullException("destination");

        // By iterating the KeyValuePair of
        // source we can avoid manually searching the keys of
        // source as we see in your original code.
        foreach (var kv in source)
        {
            PropertyInfo p;
            if (_propertyMap.TryGetValue(kv.Key.ToLower(), out p))
            {
                var propType = p.PropertyType;
                if (kv.Value == null)
                {
                    if (!propType.IsByRef && propType.Name != "Nullable`1")
                    {
                        // Throw if type is a value type 
                        // but not Nullable<>
                        throw new ArgumentException("not nullable");
                    }
                }
                else if (kv.Value.GetType() != propType)
                {
                    // You could make this a bit less strict 
                    // but I don't recommend it.
                    throw new ArgumentException("type mismatch");
                }
                p.SetValue(destination, kv.Value, null);
            }
        }
    }
}

Code Snippets

// By using a generic class we can take advantage
// of the fact that .NET will create a new generic type
// for each type T. This allows us to avoid creating
// a dictionary of Dictionary<string, PropertyInfo>
// for each type T. We also avoid the need for the 
// lock statement with every call to Map.
public static class Mapper<T>
    // We can only use reference types
    where T : class
{
    private static readonly Dictionary<string, PropertyInfo> _propertyMap;

    static Mapper()
    {
        // At this point we can convert each
        // property name to lower case so we avoid 
        // creating a new string more than once.
        _propertyMap = 
            typeof(T)
            .GetProperties()
            .ToDictionary(
                p => p.Name.ToLower(), 
                p => p
            );
    }

    public static void Map(ExpandoObject source, T destination)
    {
        // Might as well take care of null references early.
        if (source == null)
            throw new ArgumentNullException("source");
        if (destination == null)
            throw new ArgumentNullException("destination");

        // By iterating the KeyValuePair<string, object> of
        // source we can avoid manually searching the keys of
        // source as we see in your original code.
        foreach (var kv in source)
        {
            PropertyInfo p;
            if (_propertyMap.TryGetValue(kv.Key.ToLower(), out p))
            {
                var propType = p.PropertyType;
                if (kv.Value == null)
                {
                    if (!propType.IsByRef && propType.Name != "Nullable`1")
                    {
                        // Throw if type is a value type 
                        // but not Nullable<>
                        throw new ArgumentException("not nullable");
                    }
                }
                else if (kv.Value.GetType() != propType)
                {
                    // You could make this a bit less strict 
                    // but I don't recommend it.
                    throw new ArgumentException("type mismatch");
                }
                p.SetValue(destination, kv.Value, null);
            }
        }
    }
}

Context

StackExchange Code Review Q#1002, answer score: 9

Revisions (0)

No revisions yet.