patterncsharpMinor
Mapping ExpandoObject to another object type
Viewed 0 times
expandoobjecttypeanothermappingobject
Problem
I am working on a helper method that maps properties from an
Full test can be seen here. There is currently no type checking. Would this be next to add?
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.