Skip to content

Commit 0e8d25b

Browse files
authored
Dictionary interface mapping (#38)
* Support for mapping to root IDictionary<,> * Test coverage for mapping to a nested IDictionary<,> * Updating to NetStandardPolyfills v1.1 / Completely removing all use of System.Reflection.TypeExtensions * Updating to NetStandardPolyfills v1.2, removing use of System.Reflection.TypeExtensions methods accessed via ReadableExpressions * Updating to ReadableExpressions v1.10 * Synchronising access to DerivedTypesCache's collection of assemblies in which to look for derived types * Support for mapping to new non-Dictionary<,> IDictionary<,> implementations * Support for mapping from complex type non-Dictionary<,> IDictionary<,> implementations / Removing state from ComplexTypeMappingExpressionFactory and using singleton expression factory instances * Support for mapping from a non-Dictionary<,> IDictionary<,> implementation to a new IDictionary<,> * Support for using a Dictionary clone constructor when appropriate when mapping from a non-Dictionary<,> IDictionary<,> implementation * Test coverage for mapping to a new, nested non-Dictionary<,> IDictionary<,> implementation * Test coverage for mapping over nested non-Dictionary<,> IDictionary<,> implementations * Test coverage for merging non-Dictionary<,> IDictionary<,> implementations * Organising source Dictionary mapping tests * Test coverage for merging a non-Dictionary<,> IDictionary<,> implementation on to a complex type array * Removing state from DictionaryDataSouceFactory, making DataSourceFinder a singleton * Replacing ISourceEnumerableAdapter.GetSourceValues() with ISourceEnumerableAdapter.GetSourceValue(), renaming the latter * Support for merging simple-value-type non-Dictionary<,> IDictionary<,> implementations to nested simple-type collections * Correctly filtering source dictionary entries when merging on to target nested, simple-type collections * Matching file name and type * Test coverage for overwriting from a non-Dictionary<,> IDictionary<,> implementation
1 parent 5833b0b commit 0e8d25b

File tree

76 files changed

+828
-418
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+828
-418
lines changed

AgileMapper.PerformanceTester/AgileMapper.PerformanceTester.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
<WarningLevel>4</WarningLevel>
3434
</PropertyGroup>
3535
<ItemGroup>
36-
<Reference Include="AgileObjects.NetStandardPolyfills, Version=1.0.0.0, Culture=neutral, PublicKeyToken=06131ac1c008ad4e, processorArchitecture=MSIL">
37-
<HintPath>..\packages\AgileObjects.NetStandardPolyfills.1.0.0\lib\net40\AgileObjects.NetStandardPolyfills.dll</HintPath>
36+
<Reference Include="AgileObjects.NetStandardPolyfills, Version=1.2.0.0, Culture=neutral, PublicKeyToken=06131ac1c008ad4e, processorArchitecture=MSIL">
37+
<HintPath>..\packages\AgileObjects.NetStandardPolyfills.1.2.0\lib\net40\AgileObjects.NetStandardPolyfills.dll</HintPath>
3838
</Reference>
39-
<Reference Include="AgileObjects.ReadableExpressions, Version=1.9.5.0, Culture=neutral, PublicKeyToken=9f54ad81db69da8e, processorArchitecture=MSIL">
40-
<HintPath>..\packages\AgileObjects.ReadableExpressions.1.9.5\lib\net40\AgileObjects.ReadableExpressions.dll</HintPath>
39+
<Reference Include="AgileObjects.ReadableExpressions, Version=1.10.0.0, Culture=neutral, PublicKeyToken=9f54ad81db69da8e, processorArchitecture=MSIL">
40+
<HintPath>..\packages\AgileObjects.ReadableExpressions.1.10.0\lib\net40\AgileObjects.ReadableExpressions.dll</HintPath>
4141
</Reference>
4242
<Reference Include="AutoMapper, Version=5.2.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
4343
<HintPath>..\packages\AutoMapper.5.2.0\lib\net45\AutoMapper.dll</HintPath>

AgileMapper.PerformanceTester/App.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
88
<dependentAssembly>
99
<assemblyIdentity name="AgileObjects.NetStandardPolyfills" publicKeyToken="06131ac1c008ad4e" culture="neutral" />
10-
<bindingRedirect oldVersion="0.0.0.0-0.2.1.0" newVersion="0.2.1.0" />
10+
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
1111
</dependentAssembly>
1212
</assemblyBinding>
1313
</runtime>

AgileMapper.PerformanceTester/packages.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3-
<package id="AgileObjects.NetStandardPolyfills" version="1.0.0" targetFramework="net452" />
4-
<package id="AgileObjects.ReadableExpressions" version="1.9.5" targetFramework="net452" />
3+
<package id="AgileObjects.NetStandardPolyfills" version="1.2.0" targetFramework="net452" />
4+
<package id="AgileObjects.ReadableExpressions" version="1.10.0" targetFramework="net452" />
55
<package id="AutoMapper" version="5.2.0" targetFramework="net452" />
66
<package id="Expressmapper" version="1.8.3" targetFramework="net452" />
77
<package id="Mapster" version="2.6.1" targetFramework="net452" />

AgileMapper.UnitTests.NonParallel/app.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
55
<dependentAssembly>
66
<assemblyIdentity name="AgileObjects.NetStandardPolyfills" publicKeyToken="06131ac1c008ad4e" culture="neutral" />
7-
<bindingRedirect oldVersion="0.0.0.0-0.2.1.0" newVersion="0.2.1.0" />
7+
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
88
</dependentAssembly>
99
</assemblyBinding>
1010
</runtime>

AgileMapper.UnitTests/AgileMapper.UnitTests.csproj

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343
<AssemblyOriginatorKeyFile>..\AgileMapper.snk</AssemblyOriginatorKeyFile>
4444
</PropertyGroup>
4545
<ItemGroup>
46-
<Reference Include="AgileObjects.NetStandardPolyfills, Version=1.0.0.0, Culture=neutral, PublicKeyToken=06131ac1c008ad4e, processorArchitecture=MSIL">
47-
<HintPath>..\packages\AgileObjects.NetStandardPolyfills.1.0.0\lib\net40\AgileObjects.NetStandardPolyfills.dll</HintPath>
46+
<Reference Include="AgileObjects.NetStandardPolyfills, Version=1.2.0.0, Culture=neutral, PublicKeyToken=06131ac1c008ad4e, processorArchitecture=MSIL">
47+
<HintPath>..\packages\AgileObjects.NetStandardPolyfills.1.2.0\lib\net40\AgileObjects.NetStandardPolyfills.dll</HintPath>
4848
</Reference>
49-
<Reference Include="AgileObjects.ReadableExpressions, Version=1.9.5.0, Culture=neutral, PublicKeyToken=9f54ad81db69da8e, processorArchitecture=MSIL">
50-
<HintPath>..\packages\AgileObjects.ReadableExpressions.1.9.5\lib\net40\AgileObjects.ReadableExpressions.dll</HintPath>
49+
<Reference Include="AgileObjects.ReadableExpressions, Version=1.10.0.0, Culture=neutral, PublicKeyToken=9f54ad81db69da8e, processorArchitecture=MSIL">
50+
<HintPath>..\packages\AgileObjects.ReadableExpressions.1.10.0\lib\net40\AgileObjects.ReadableExpressions.dll</HintPath>
5151
</Reference>
5252
<Reference Include="Shouldly, Version=2.8.3.0, Culture=neutral, PublicKeyToken=6042cbcb05cbc941, processorArchitecture=MSIL">
5353
<HintPath>..\packages\Shouldly.2.8.3\lib\net451\Shouldly.dll</HintPath>
@@ -101,6 +101,8 @@
101101
<Compile Include="Configuration\Inline\WhenValidatingMappingsInline.cs" />
102102
<Compile Include="Configuration\Inline\WhenViewingMappingPlans.cs" />
103103
<Compile Include="Configuration\WhenViewingMappingPlans.cs" />
104+
<Compile Include="Dictionaries\WhenMergingObjectsFromDictionaries.cs" />
105+
<Compile Include="Dictionaries\WhenOverwritingObjectsFromDictionaries.cs" />
104106
<Compile Include="Extensions\WhenEquatingExpressions.cs" />
105107
<Compile Include="MapperCloning\WhenCloningConstructorDataSources.cs" />
106108
<Compile Include="MapperCloning\WhenCloningMemberIgnores.cs" />
@@ -176,13 +178,14 @@
176178
<Compile Include="TestClasses\PublicTwoFields.cs" />
177179
<Compile Include="TestClasses\PublicTwoFieldsStruct.cs" />
178180
<Compile Include="TestClasses\PublicTwoParamCtor.cs" />
181+
<Compile Include="TestClasses\StringKeyedDictionary.cs" />
179182
<Compile Include="TestClasses\Wedding.cs" />
180183
<Compile Include="TestClasses\WeddingDto.cs" />
181184
<Compile Include="WhenValidatingMappings.cs" />
182185
<Compile Include="WhenAnalysingCollections.cs" />
183186
<Compile Include="MapperCloning\WhenCloningDataSources.cs" />
184187
<Compile Include="WhenFlatteningObjects.cs" />
185-
<Compile Include="Dictionaries\WhenMappingFromDictionaries.cs" />
188+
<Compile Include="Dictionaries\WhenMappingNewObjectsFromDictionaries.cs" />
186189
<Compile Include="Dictionaries\WhenMappingFromDictionaryMembers.cs" />
187190
<Compile Include="Dictionaries\WhenMappingOnToDictionaries.cs" />
188191
<Compile Include="Dictionaries\WhenMappingOnToDictionaryMembers.cs" />

AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionaries.cs renamed to AgileMapper.UnitTests/Dictionaries/WhenMappingNewObjectsFromDictionaries.cs

Lines changed: 1 addition & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using TestClasses;
99
using Xunit;
1010

11-
public class WhenMappingFromDictionaries
11+
public class WhenMappingNewObjectsFromDictionaries
1212
{
1313
[Fact]
1414
public void ShouldPopulateAnIntMemberFromATypedEntry()
@@ -38,30 +38,6 @@ public void ShouldPopulateAStringSetMethodFromATypedEntry()
3838
result.Value.ShouldBe("Goodbye");
3939
}
4040

41-
[Fact]
42-
public void ShouldPopulateAStringMemberFromANullableTypedEntry()
43-
{
44-
var guid = Guid.NewGuid();
45-
46-
var source = new Dictionary<string, Guid?> { ["Value"] = guid };
47-
var target = new PublicProperty<string>();
48-
var result = Mapper.Map(source).OnTo(target);
49-
50-
result.Value.ShouldBe(guid.ToString());
51-
}
52-
53-
[Fact]
54-
public void ShouldPopulateADateTimeMemberFromAnUntypedEntry()
55-
{
56-
var now = DateTime.Now.ToCurrentCultureString();
57-
58-
var source = new Dictionary<string, object> { ["Value"] = now };
59-
var target = new PublicProperty<DateTime> { Value = DateTime.Now.AddHours(1) };
60-
var result = Mapper.Map(source).Over(target);
61-
62-
result.Value.ToCurrentCultureString().ShouldBe(now);
63-
}
64-
6541
[Fact]
6642
public void ShouldPopulateANestedStringMemberFromTypedDottedEntries()
6743
{
@@ -450,52 +426,6 @@ public void ShouldPopulateDeepNestedComplexTypeMembersFromUntypedDottedEntries()
450426
result.Value.Second().Value.Value.First().Address.Line2.ShouldBeDefault();
451427
}
452428

453-
[Fact]
454-
public void ShouldReuseAnExistingListIfNoEntriesMatch()
455-
{
456-
var source = new Dictionary<string, object>();
457-
var target = new PublicProperty<ICollection<string>> { Value = new List<string>() };
458-
var originalList = target.Value;
459-
var result = Mapper.Map(source).OnTo(target);
460-
461-
result.Value.ShouldBeSameAs(originalList);
462-
}
463-
464-
[Fact]
465-
public void ShouldOverwriteAStringPropertyToNullFromATypedEntry()
466-
{
467-
var source = new Dictionary<string, string> { ["Value"] = null };
468-
var target = new PublicField<string> { Value = "To be overwritten..." };
469-
var result = Mapper.Map(source).Over(target);
470-
471-
result.Value.ShouldBeNull();
472-
}
473-
474-
[Fact]
475-
public void ShouldOverwriteAnIntPropertyToDefaultFromATypedEntry()
476-
{
477-
var source = new Dictionary<string, string> { ["Value"] = null };
478-
var target = new PublicField<int> { Value = 6473 };
479-
var result = Mapper.Map(source).Over(target);
480-
481-
result.Value.ShouldBeDefault();
482-
}
483-
484-
[Fact]
485-
public void ShouldOverwriteAComplexTypePropertyToNull()
486-
{
487-
var source = new Dictionary<string, object>
488-
{
489-
["Name"] = "Frank",
490-
["Address"] = default(Address)
491-
};
492-
var target = new Customer { Name = "Charlie", Address = new Address { Line1 = "Cat Lane" } };
493-
var result = Mapper.Map(source).Over(target);
494-
495-
result.Name.ShouldBe("Frank");
496-
result.Address.ShouldBeNull();
497-
}
498-
499429
[Fact]
500430
public void ShouldIgnoreANonStringKeyedDictionary()
501431
{

AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaries.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,29 @@ public void ShouldMapBetweenDifferentSimpleKeyAndValueTypeDictionaries()
7474
target[4].ShouldBe(44);
7575
target[44].ShouldBe(444);
7676
}
77+
78+
[Fact]
79+
public void ShouldMapBetweenDictionaryImplementations()
80+
{
81+
var source = new StringKeyedDictionary<Product>
82+
{
83+
["One"] = new Product { ProductId = "One!", Price = 9.99 },
84+
["Two"] = new Product { ProductId = "Two!", Price = 10.00 }
85+
};
86+
var target = new StringKeyedDictionary<ProductDto>
87+
{
88+
["One"] = new ProductDto { ProductId = "One!", Price = 99.99m }
89+
};
90+
91+
var result = Mapper.Map(source).OnTo(target);
92+
93+
result.Count.ShouldBe(2);
94+
95+
result["One"].ProductId.ShouldBe("One!");
96+
result["One"].Price.ShouldBe(9.99m);
97+
98+
result["Two"].ProductId.ShouldBe("Two!");
99+
result["Two"].Price.ShouldBe(10.00m);
100+
}
77101
}
78102
}

AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaries.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Dictionaries
22
{
3+
using System;
34
using System.Collections.Generic;
45
using System.Collections.ObjectModel;
6+
using AgileMapper.Extensions;
57
using Shouldly;
68
using TestClasses;
79
using Xunit;
@@ -138,5 +140,25 @@ public void ShouldOverwriteAComplexTypeListToADifferentComplexTypeIDictionary()
138140
result["[1]"].Name.ShouldBe("Clark");
139141
result["[1]"].AddressLine1.ShouldBe("Daily Planet");
140142
}
143+
144+
[Fact]
145+
public void ShouldOverwriteASimpleTypeArrayToADictionaryImplementation()
146+
{
147+
var source = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
148+
var target = new StringKeyedDictionary<string>
149+
{
150+
["[0]"] = source.First().ToString(),
151+
["[1]"] = source.Third().ToString(),
152+
["[4]"] = Guid.NewGuid().ToString()
153+
};
154+
var preMapping4 = target["[4]"];
155+
var result = Mapper.Map(source).Over(target);
156+
157+
result.Count.ShouldBe(4);
158+
result["[0]"].ShouldBe(source.First().ToString());
159+
result["[1]"].ShouldBe(source.Second().ToString());
160+
result["[2]"].ShouldBe(source.Third().ToString());
161+
result["[4]"].ShouldBe(preMapping4);
162+
}
141163
}
142164
}

AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public void ShouldMapNestedComplexAndSimpleTypeEnumerablesToAnUntypedDictionary(
144144
public void ShouldMapBetweenSameSimpleValueTypedDictionaries()
145145
{
146146
var source = new Dictionary<string, int> { ["One"] = 1, ["Two"] = 2 };
147-
var result = Mapper.Map(source).ToANew<Dictionary<string, int>>();
147+
var result = Mapper.Map(source).ToANew<IDictionary<string, int>>();
148148

149149
result.ShouldNotBeSameAs(source);
150150
result.Count.ShouldBe(2);
@@ -242,6 +242,89 @@ public void ShouldMapTypePairsBetweenDifferentComplexValueTypedDictionaries()
242242
((Customer)result["456"]).Discount.ShouldBe(0.25m);
243243
}
244244

245+
[Fact]
246+
public void ShouldMapToASimpleTypeDictionaryImplementation()
247+
{
248+
var source = new[] { "Hello", "Goodbye", "See ya" };
249+
var result = Mapper.Map(source).ToANew<StringKeyedDictionary<string>>();
250+
251+
result.Count.ShouldBe(3);
252+
253+
result.ContainsKey("[0]").ShouldBeTrue();
254+
result["[0]"].ShouldBe("Hello");
255+
result.ContainsKey("[1]").ShouldBeTrue();
256+
result["[1]"].ShouldBe("Goodbye");
257+
result.ContainsKey("[2]").ShouldBeTrue();
258+
result["[2]"].ShouldBe("See ya");
259+
}
260+
261+
[Fact]
262+
public void ShouldMapFromASimpleTypeDictionaryImplementationToAnIDictionary()
263+
{
264+
var source = new StringKeyedDictionary<string>
265+
{
266+
["One"] = "One!",
267+
["Two"] = "Two!",
268+
["Three"] = "Three!",
269+
};
270+
var result = Mapper.Map(source).ToANew<IDictionary<string, string>>();
271+
272+
result.Count.ShouldBe(3);
273+
274+
result.ContainsKey("One").ShouldBeTrue();
275+
result["One"].ShouldBe("One!");
276+
277+
result.ContainsKey("Two").ShouldBeTrue();
278+
result["Two"].ShouldBe("Two!");
279+
280+
result.ContainsKey("Three").ShouldBeTrue();
281+
result["Three"].ShouldBe("Three!");
282+
}
283+
284+
[Fact]
285+
public void ShouldMapBetweenSameDeclaredSimpleTypeIDictionaries()
286+
{
287+
IDictionary<string, string> source = new StringKeyedDictionary<string>
288+
{
289+
["Hello"] = "Bonjour",
290+
["Yes"] = "Oui"
291+
};
292+
var result = Mapper.Map(source).ToANew<IDictionary<string, string>>();
293+
294+
result.Count.ShouldBe(2);
295+
296+
result.ContainsKey("Hello").ShouldBeTrue();
297+
result["Hello"].ShouldBe("Bonjour");
298+
299+
result.ContainsKey("Yes").ShouldBeTrue();
300+
result["Yes"].ShouldBe("Oui");
301+
}
302+
303+
[Fact]
304+
public void ShouldMapBetweenSameComplexTypeDictionaryImplementations()
305+
{
306+
var source = new StringKeyedDictionary<Address>
307+
{
308+
["One"] = new Address { Line1 = "1.1", Line2 = "1.2" },
309+
["Two"] = new Address { Line1 = "2.1", Line2 = "2.2" },
310+
["Three"] = default(Address),
311+
};
312+
var result = Mapper.Map(source).ToANew<StringKeyedDictionary<Address>>();
313+
314+
result.Count.ShouldBe(3);
315+
316+
result.ContainsKey("One").ShouldBeTrue();
317+
result["One"].Line1.ShouldBe("1.1");
318+
result["One"].Line2.ShouldBe("1.2");
319+
320+
result.ContainsKey("Two").ShouldBeTrue();
321+
result["Two"].Line1.ShouldBe("2.1");
322+
result["Two"].Line2.ShouldBe("2.2");
323+
324+
result.ContainsKey("Three").ShouldBeTrue();
325+
result["Three"].ShouldBeNull();
326+
}
327+
245328
[Fact]
246329
public void ShouldHandleANullComplexTypeMember()
247330
{

0 commit comments

Comments
 (0)