Skip to content

Commit 262708f

Browse files
committed
Support for supplying the CreateNew mapping result type as a type object, re: issue #66
1 parent 36fc7c0 commit 262708f

File tree

5 files changed

+80
-0
lines changed

5 files changed

+80
-0
lines changed

AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,17 @@ public void ShouldFlattenToValueTypes()
374374
anonResult["ComplexList[1].Value2.Value"].ShouldBe(789);
375375
}
376376

377+
// See https://github.com/agileobjects/AgileMapper/issues/66
378+
[Fact]
379+
public void ShouldMapToAGivenDictionaryTypeObject()
380+
{
381+
var source = new PublicProperty<int> { Value = 6473 };
382+
var result = Mapper.Map(source).ToANew(typeof(Dictionary<string, string>));
383+
384+
result.ShouldBeOfType<Dictionary<string, string>>();
385+
((Dictionary<string, string>)result)["Value"].ShouldBe(6473);
386+
}
387+
377388
[Fact]
378389
public void ShouldHandleANullComplexTypeMember()
379390
{

AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,17 @@ public void ShouldMapNestedMembersToAnExpandoObject()
4343
((string)result.Address_Line1).ShouldBe("One!");
4444
((string)result.Address_Line2).ShouldBe("Two!");
4545
}
46+
47+
// See https://github.com/agileobjects/AgileMapper/issues/66
48+
[Fact]
49+
public void ShouldMapToExpandoObjectIfGivenAsAType()
50+
{
51+
var source = new PublicProperty<int> { Value = 6473 };
52+
var result = Mapper.Map(source).ToANew(typeof(ExpandoObject));
53+
54+
result.ShouldNotBeNull();
55+
dynamic dynamicResult = result;
56+
((int)dynamicResult.Value).ShouldBe(6473);
57+
}
4658
}
4759
}

AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ public void ShouldMapFromAnInterface()
8585
result.Value.ShouldBe("Interfaces!");
8686
}
8787

88+
// See https://github.com/agileobjects/AgileMapper/issues/66
89+
[Fact]
90+
public void ShouldMapToAGivenTypeObject()
91+
{
92+
var source = new PublicProperty<string>
93+
{
94+
Value = "kjubfelkjnds;lkmm"
95+
};
96+
var result = Mapper.Map(source).ToANew(typeof(PublicField<string>));
97+
98+
result.ShouldBeOfType<PublicField<string>>();
99+
((PublicField<string>)result).Value.ShouldBe("kjubfelkjnds;lkmm");
100+
}
101+
88102
[Fact]
89103
public void ShouldHandleAnUnconstructableRootTargetType()
90104
{

AgileMapper/Api/ITargetSelector.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
/// <typeparam name="TSource">The type of source object from which mapping is being performed.</typeparam>
1111
public interface ITargetSelector<TSource>
1212
{
13+
/// <summary>
14+
/// Create an instance of the given <paramref name="resultType"/> from the specified source object.
15+
/// </summary>
16+
/// <param name="resultType">The type of object to create from the specified source object.</param>
17+
/// <returns>The result of the new object mapping.</returns>
18+
object ToANew(Type resultType);
19+
1320
/// <summary>
1421
/// Create an instance of <typeparamref name="TResult"/> from the specified source object.
1522
/// </summary>

AgileMapper/MappingExecutor.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,45 @@
55
using System.Dynamic;
66
using System.Linq;
77
using System.Linq.Expressions;
8+
using System.Reflection;
89
using Api;
910
using Api.Configuration;
11+
using Caching;
1012
using Extensions.Internal;
13+
using NetStandardPolyfills;
1114
using ObjectPopulation;
1215

16+
internal static class MappingExecutorBridge<TSource>
17+
{
18+
private static readonly ParameterExpression _selectorParameter =
19+
Parameters.Create(typeof(ITargetSelector<TSource>));
20+
21+
private static readonly MethodInfo _typedToANewMethod = typeof(ITargetSelector<TSource>)
22+
.GetPublicInstanceMethods("ToANew")
23+
.First(m => m.IsGenericMethod && m.GetParameters().None());
24+
25+
private static readonly ICache<Type, Func<ITargetSelector<TSource>, object>> _createNewCallersByTargetType =
26+
GlobalContext.Instance.Cache.CreateScoped<Type, Func<ITargetSelector<TSource>, object>>();
27+
28+
public static object CreateNew(Type resultType, ITargetSelector<TSource> selector)
29+
{
30+
var typedCaller = _createNewCallersByTargetType.GetOrAdd(resultType, rt =>
31+
{
32+
var typedCreateNewCall = Expression.Call(
33+
_selectorParameter,
34+
_typedToANewMethod.MakeGenericMethod(rt));
35+
36+
var createNewCaller = Expression.Lambda<Func<ITargetSelector<TSource>, object>>(
37+
typedCreateNewCall,
38+
_selectorParameter);
39+
40+
return createNewCaller.Compile();
41+
});
42+
43+
return typedCaller.Invoke(selector);
44+
}
45+
}
46+
1347
internal class MappingExecutor<TSource> :
1448
ITargetSelector<TSource>,
1549
IFlatteningSelector<TSource>,
@@ -33,6 +67,8 @@ public MappingExecutor(TSource source, MapperContext mapperContext)
3367

3468
#region ToANew Overloads
3569

70+
public object ToANew(Type resultType) => MappingExecutorBridge<TSource>.CreateNew(resultType, this);
71+
3672
public TResult ToANew<TResult>() => PerformMapping(MapperContext.RuleSets.CreateNew, default(TResult));
3773

3874
public TResult ToANew<TResult>(Expression<Action<IFullMappingInlineConfigurator<TSource, TResult>>> configuration)

0 commit comments

Comments
 (0)