Skip to content

Commit 379abb7

Browse files
authored
Merge pull request #7 from gkn06/main
added support for deep object mapping
2 parents 0d7c95c + 213770d commit 379abb7

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

ValueMapper/ValueMapperCore/ValueMapper.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,18 @@ internal static Action<TSource, TDestination, ISet<string>> BuildMap<TSource, TD
312312
}
313313
}
314314

315+
// Check if this is a complex object that needs deep mapping
316+
if (IsComplexType(srcType) && IsComplexType(dstType) && srcType != dstType)
317+
{
318+
// Create deep mapping for complex objects
319+
var deepMapping = CreateDeepMapping<TSource, TDestination>(dst.Name, src, dst, srcType, dstType);
320+
if (deepMapping.Getter != null && deepMapping.Setter != null)
321+
{
322+
mappings.Add(deepMapping);
323+
continue;
324+
}
325+
}
326+
315327
// Normal property mapping
316328
var converter = CreateConverter(srcType, dstType);
317329
if (converter == null)
@@ -859,6 +871,103 @@ private static Type GetElementType(Type collectionType)
859871

860872
return typeof(object);
861873
}
874+
875+
// Check if a type is a complex object that needs deep mapping
876+
private static bool IsComplexType(Type type)
877+
{
878+
if (type == null) return false;
879+
880+
// Handle nullable types
881+
Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
882+
883+
// Skip primitive types, strings, and enums
884+
if (underlyingType.IsPrimitive ||
885+
underlyingType == typeof(string) ||
886+
underlyingType == typeof(DateTime) ||
887+
underlyingType == typeof(TimeSpan) ||
888+
underlyingType == typeof(DateTimeOffset) ||
889+
underlyingType == typeof(Guid) ||
890+
underlyingType == typeof(decimal) ||
891+
underlyingType.IsEnum)
892+
{
893+
return false;
894+
}
895+
896+
// Skip collections
897+
if (IsCollection(type))
898+
{
899+
return false;
900+
}
901+
902+
// Check if it's a class with properties (complex object)
903+
return underlyingType.IsClass &&
904+
underlyingType != typeof(object) &&
905+
GetTypeProperties(underlyingType).Length > 0;
906+
}
907+
908+
// Create deep mapping for complex nested objects
909+
private static MappingEntry<TSource, TDestination> CreateDeepMapping<TSource, TDestination>(
910+
string name, PropertyInfo srcProp, PropertyInfo dstProp, Type srcType, Type dstType)
911+
{
912+
// Create a getter that retrieves the source object
913+
var srcParam = Expression.Parameter(typeof(TSource), "s");
914+
var srcPropExpr = Expression.Property(srcParam, srcProp);
915+
916+
// Create the deep mapping method call
917+
var mapMethod = typeof(ValueMapper).GetMethod(nameof(Map), new[] { srcType, typeof(ISet<string>) })
918+
?? typeof(ValueMapper).GetMethods()
919+
.Where(m => m.Name == nameof(Map) && m.GetGenericArguments().Length == 2)
920+
.FirstOrDefault()?.MakeGenericMethod(srcType, dstType);
921+
922+
if (mapMethod == null)
923+
{
924+
// Fallback: try to get the generic Map method and make it generic
925+
mapMethod = typeof(ValueMapper).GetMethods(BindingFlags.Public | BindingFlags.Static)
926+
.Where(m => m.Name == nameof(Map) &&
927+
m.IsGenericMethodDefinition &&
928+
m.GetGenericArguments().Length == 2 &&
929+
m.GetParameters().Length == 2)
930+
.FirstOrDefault()?.MakeGenericMethod(srcType, dstType);
931+
}
932+
933+
if (mapMethod == null)
934+
{
935+
return new MappingEntry<TSource, TDestination>(name, null, null, dstType, true);
936+
}
937+
938+
// Create getter that performs deep mapping
939+
Func<TSource, object> getter = source =>
940+
{
941+
try
942+
{
943+
var sourceValue = srcProp.GetValue(source);
944+
if (sourceValue == null)
945+
return null;
946+
947+
// Use reflection to call the generic Map method
948+
return mapMethod.Invoke(null, new object[] { sourceValue, null });
949+
}
950+
catch
951+
{
952+
return null;
953+
}
954+
};
955+
956+
// Create setter for the destination property
957+
var dstParam = Expression.Parameter(typeof(TDestination), "d");
958+
var valueParam = Expression.Parameter(typeof(object), "v");
959+
var setterExpr = Expression.Lambda<Action<TDestination, object>>(
960+
Expression.Call(
961+
dstParam,
962+
dstProp.GetSetMethod(true),
963+
Expression.Convert(valueParam, dstType)
964+
),
965+
dstParam, valueParam
966+
);
967+
var setter = setterExpr.Compile();
968+
969+
return new MappingEntry<TSource, TDestination>(name, getter, setter, dstType, true);
970+
}
862971
}
863972
}
864973
// Internal implementation detail - hidden from API

0 commit comments

Comments
 (0)