Skip to content

Instantly share code, notes, and snippets.

@mahelbir
Last active February 19, 2025 13:09
Show Gist options
  • Save mahelbir/b06badad137a145549a9d00c8074ad4e to your computer and use it in GitHub Desktop.
Save mahelbir/b06badad137a145549a9d00c8074ad4e to your computer and use it in GitHub Desktop.
.NET Object Mapper
using System.Collections;
using System.Reflection;
namespace Common;
public static class Mapper
{
public static TDestination Map<TSource, TDestination>(TSource source)
where TSource : class
where TDestination : class, new()
{
ArgumentNullException.ThrowIfNull(source);
var result = (TDestination?)MapObject(source, typeof(TSource), typeof(TDestination), null, includeNulls: true);
if (result == null)
throw new InvalidOperationException($"Cannot map {typeof(TSource)} to {typeof(TDestination)}");
return result;
}
public static void Update<TSource, TDestination>(TSource source, TDestination destination)
where TSource : class
where TDestination : class
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(destination);
MapObject(source, typeof(TSource), typeof(TDestination), destination, includeNulls: true);
}
public static void UpdateNonNull<TSource, TDestination>(TSource source, TDestination destination)
where TSource : class
where TDestination : class
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(destination);
MapObject(source, typeof(TSource), typeof(TDestination), destination, includeNulls: false);
}
private static object? MapObject(object? source, Type srcType, Type dstType, object? existingInstance, bool includeNulls)
{
if (source == null)
return null;
if (dstType == srcType)
return source;
if (IsSimpleType(srcType) && IsSimpleType(dstType))
{
if (IsNullableType(dstType))
{
var underlying = Nullable.GetUnderlyingType(dstType);
if (underlying != null)
{
return Convert.ChangeType(source, underlying);
}
}
return Convert.ChangeType(source, dstType);
}
if (IsDictionary(srcType) && IsDictionary(dstType))
{
return MapDictionary(source, srcType, dstType, existingInstance, includeNulls);
}
if (IsEnumerableButNotString(srcType) && IsEnumerableButNotString(dstType))
{
return MapEnumerable(source, srcType, dstType, existingInstance, includeNulls);
}
return MapByReflection(source, srcType, dstType, existingInstance, includeNulls);
}
private static object? MapDictionary(object source, Type srcType, Type dstType, object? existingInstance, bool includeNulls)
{
var srcGenericArgs = srcType.GetGenericArguments();
var dstGenericArgs = dstType.GetGenericArguments();
var dictInstance = existingInstance ?? Activator.CreateInstance(dstType);
if (dictInstance == null) return null;
var srcEnumerable = (IEnumerable)source;
var addMethod = dstType.GetMethod("Add", new[] { dstGenericArgs[0], dstGenericArgs[1] });
if (addMethod == null)
return null;
foreach (var item in srcEnumerable)
{
var kvpType = item.GetType();
var keyProp = kvpType.GetProperty("Key");
var valueProp = kvpType.GetProperty("Value");
if (keyProp == null || valueProp == null) continue;
var srcKey = keyProp.GetValue(item);
var srcValue = valueProp.GetValue(item);
if (srcKey == null || (srcValue == null && !includeNulls)) continue;
var dstKey = MapObject(srcKey, srcGenericArgs[0], dstGenericArgs[0], null, includeNulls);
var dstValue = MapObject(srcValue, srcGenericArgs[1], dstGenericArgs[1], null, includeNulls);
if (dstKey == null || dstValue == null) continue;
addMethod.Invoke(dictInstance, new[] { dstKey, dstValue });
}
return dictInstance;
}
private static object? MapEnumerable(object source, Type srcType, Type dstType, object? existingInstance, bool includeNulls)
{
var srcArg = srcType.IsArray
? srcType.GetElementType()
: srcType.GetGenericArguments().FirstOrDefault();
var dstArg = dstType.IsArray
? dstType.GetElementType()
: dstType.GetGenericArguments().FirstOrDefault();
if (srcArg == null || dstArg == null)
return null;
var srcEnumerable = (IEnumerable)source;
var listInstance = existingInstance ?? Activator.CreateInstance(dstType);
if (listInstance == null) return null;
var addMethod = dstType.GetMethod("Add");
if (addMethod == null)
return null;
foreach (var item in srcEnumerable)
{
var mappedItem = MapObject(item, srcArg, dstArg, null, includeNulls);
addMethod.Invoke(listInstance, new[] { mappedItem });
}
return listInstance;
}
private static object? MapByReflection(object source, Type srcType, Type dstType, object? existingInstance, bool includeNulls)
{
var obj = existingInstance ?? Activator.CreateInstance(dstType);
if (obj == null) return null;
var srcProps = srcType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var dstProps = dstType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite)
.ToList();
foreach (var sProp in srcProps)
{
var dProp = dstProps.FirstOrDefault(dp => dp.Name == sProp.Name);
if (dProp == null)
continue;
var srcVal = sProp.GetValue(source);
if (srcVal == null && !includeNulls)
continue;
var mappedVal = MapObject(srcVal, sProp.PropertyType, dProp.PropertyType, dProp.GetValue(obj), includeNulls);
dProp.SetValue(obj, mappedVal);
}
return obj;
}
private static bool IsSimpleType(Type type)
{
if (IsNullableType(type))
{
var underlying = Nullable.GetUnderlyingType(type);
if (underlying != null)
type = underlying;
}
return
type.IsPrimitive ||
type.IsEnum ||
type == typeof(string) ||
type == typeof(decimal) ||
type == typeof(DateTime) ||
type == typeof(Guid) ||
type == typeof(TimeSpan) ||
type == typeof(DateTimeOffset);
}
private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static bool IsDictionary(Type t)
{
if (!t.IsGenericType) return false;
var genericTypeDef = t.GetGenericTypeDefinition();
return genericTypeDef == typeof(Dictionary<,>);
}
private static bool IsEnumerableButNotString(Type t)
{
return t != typeof(string) && typeof(IEnumerable).IsAssignableFrom(t);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment