Skip to content

Commit c6c0b75

Browse files
authored
Flatten to dynamic (#40)
* Support for flattening single-member nested complex types to an untyped dictionary * Support for mapping from non-generic IDictionary<,> implementations, e.g. ExpandoObject * Test coverage for conversion of dynamic object simple-type member values during mapping * Organising source Dictionary mapping tests * Removing need to cast IDictionary<,> source objects when accessing keys * Test coverage for mapping a null string dynamic member * Support for mapping untyped dictionary complex type entries to complex type members / Organising source dictionary tests / Test coverage for mapping from a dynamic to nested members * Renaming dictionary key matchers for clarity / Using efficient string.FirstOrDefault() extension method instead of string.Left(numberOfCharacters) when converting strings to character * Supporting mapping from dynamic members with flattened names to nested target members * Supporting flattened source Dictionary keys by default, without configuration * Test coverage for mapping from a dynamic to a simple type enumerable member * Support for mapping dynamic non-identifiable complex type collection members to enumerable collection members / Removing double .Map() call from Dictionary entry -> complex type enumerable mapping / Adding Type.IsClosedTypeOf extension method * Support for mapping from dynamic, underscored-index enumerable element members to complex type enumerable members * Organising Dictionary classes * Test coverage for mapping from flattened dynamic members to nested complex type enumerable members * Test coverage for mapping from dynamics to new root enumerables * Short-circuiting mappings from dictionaries to recursive members / Including dictionary implementation type name in MappingExceptions instead of interface name / Support for id-aware mapping from: - IDictionary<,>-implementation identifiable complex type collection entries ...over + onto: - Identifiable complex type collections * Test coverage for mapping from a dynamic null complex type collection member over a complex type collection member * Support for mapping from object-valued IDictionary<,> IDictionary<,> entries to nested complex type members * Test coverage for namming ExpandoObject (not IDictionary<,>) in thrown MappingExceptions * Support for mapping from nested dynamic members to nested complex type members / Using constants for root source and target member names * Extending test coverage for mapping from nested dynamics to nested complex types * Support for mapping to ValueType-valued IDictionary<,>s / Using Shouldy ShouldContainKey() and ShouldNotContainKey() * Support for IDictionary<,> -> complex type enumerable mapping short-circuits for target Collection<>s / Test coverage updates / Removing unused code * Test coverage for mapping over root complex type members * Extending enum -> enum mapping test coverage * Start of mapping to dynamic test coverage * Extending map-to-dynamic test coverage * Start of test coverage for mapping over dynamics / Skipping 'assign to itself' target variable populations * Extending coverage for mapping over dynamic root members * Start of merging to dynamics support * Restricting WhenMapping.DictionariesWithValueType<TValue> mappings to dictionaries of that value type / Applying WhenMapping.Dictionaries to all dictionaries of all value types / Applying source dictionary configuration to dictionaries, not ExpandoObjects * Extending member matching test coverage * Selecting flattened source members from parent contexts to map to nested dynamics (and dictionaries) * Support for mapping flattened members from parent contexts to nested dynamics (and dictionaries) * Fixing broken tests / Moving internal extension methods into dedicated namespace * Extending test coverage for mapping to nested dynamic members * Test coverage for mapping to root enumerable<dynamic> * Test coverage for mapping derived complex type collection elements to dynamics * Separating global dictionary configuration from source dictionary configuration * Support for mapping nested members to flattened, underscore-separated target dynamic members * Support for mapping over nested ExpandoObject members * Beginning of dynamic configuration API / Ensuring dynamic configuration isn't applied to dictionaries * Support for configuring creation-mapping-specific custom dynamic source members * Test coverage for mapping onto a nested dynamic from a nested Dictionary * Support for mapping from complex type enumerables onto nested target dynamics / Fixing infinite loop when mapping from a derived type in a collection to a dynamic (or dictionary) * Support for configuring custom member names for source dynamic mappings * Support for mapping from dynamics with configured member name parts / Properly handling flattened member names in StringExtensions.KeyMatches overloads * Support for using custom member name separators for source dynamics / Tidying JoiningNameFactory / Tidying documentation * Erroring if redundant element key patterns are configured / Organising dictionary configuration classes * Test coverage for configuration error when configuring a redundant element key pattern for source dynamic mapping * Erroring is redundant member name separator globally configured for dynamics * Differentiating between global and source-only dictionary (and dynamic) configuration * Differentiating between source-only and global dynamic configuration * Support for differentiating global and source-specific dynamic member name separators * Fixing dynamic mapping separator selection * Support for mapping from a collection of structs to a target dynamic or dictionary * Test coverage for mapping over a dynamic from an unmappable struct type collection * Start of dynamic over enumerable mapping * Extending dynamic over enumerables test coverage * Separating dictionary and dynamic configurator classes / Derived dynamic configurator from FullMappingConfigurator with source type as IDictionary<string, object> * Test coverage for mapping from dynamics to derived types based on the presence of particular dynamic members * Start of target dynamic mapping configuration tests / Support for Mapper.Map(source).ToANew<dynamic>(), defaulting the target to ExpandoObject * Adding target dynamic configuration API classes / Start of target dynamic configuration tests * Support for custom, source-type-specific dynamic enumerable element patterns * Support for conditional custom mapping to custom ExpandoObject target members * Test coverage for various conditional mappings to custom dynamic target members * Ensuring target dictionary configuration is not applied to target dynamics * Test coverage ensuring target dynamic config isn't applied to target dictionaries * Test coverage for non-conflict of target dictionary and dynamic configuration * Flattening objects using ToANew<dynamic>() * Tidying * Fixing flattening test * Handling null entries in complex type collections being flattened or mapped to dictionaries or dynamics
1 parent 5cc4e77 commit c6c0b75

File tree

228 files changed

+5459
-2060
lines changed

Some content is hidden

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

228 files changed

+5459
-2060
lines changed

AgileMapper.UnitTests/AgileMapper.UnitTests.csproj

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,30 @@
9696
<Compile Include="Configuration\Inline\WhenValidatingMappingsInline.cs" />
9797
<Compile Include="Configuration\Inline\WhenViewingMappingPlans.cs" />
9898
<Compile Include="Configuration\WhenViewingMappingPlans.cs" />
99-
<Compile Include="Dictionaries\WhenMergingObjectsFromDictionaries.cs" />
100-
<Compile Include="Dictionaries\WhenOverwritingObjectsFromDictionaries.cs" />
99+
<Compile Include="Dictionaries\WhenMappingFromDictionariesOnToComplexTypes.cs" />
100+
<Compile Include="Dictionaries\WhenMappingFromDictionariesOnToEnumerableMembers.cs" />
101+
<Compile Include="Dictionaries\WhenMappingFromDictionariesOverComplexTypes.cs" />
102+
<Compile Include="Dictionaries\WhenMappingFromDictionariesToNewComplexTypeMembers.cs" />
103+
<Compile Include="Dictionaries\WhenMappingFromDictionariesToNewEnumerableMembers.cs" />
104+
<Compile Include="Dictionaries\WhenMappingFromDictionariesToNewEnumerables.cs" />
105+
<Compile Include="Dynamics\Configuration\WhenConfiguringDynamicMappingIncorrectly.cs" />
106+
<Compile Include="Dynamics\Configuration\WhenConfiguringSourceDynamicMapping.cs" />
107+
<Compile Include="Dynamics\Configuration\WhenConfiguringTargetDynamicMapping.cs" />
108+
<Compile Include="Dynamics\WhenMappingFromDynamicsOverComplexTypeMembers.cs" />
109+
<Compile Include="Dynamics\WhenMappingFromDynamicsOverComplexTypes.cs" />
110+
<Compile Include="Dynamics\WhenMappingFromDynamicsOverEnumerableMembers.cs" />
111+
<Compile Include="Dynamics\WhenMappingFromDynamicsOverEnumerables.cs" />
112+
<Compile Include="Dynamics\WhenMappingFromDynamicsToNewComplexTypeMembers.cs" />
113+
<Compile Include="Dynamics\WhenMappingFromDynamicsToNewComplexTypes.cs" />
114+
<Compile Include="Dynamics\WhenMappingFromDynamicsToNewEnumerableMembers.cs" />
115+
<Compile Include="Dynamics\WhenMappingFromDynamicsToNewEnumerables.cs" />
116+
<Compile Include="Dynamics\WhenMappingOnToDynamics.cs" />
117+
<Compile Include="Dynamics\WhenMappingOnToDynamicMembers.cs" />
118+
<Compile Include="Dynamics\WhenMappingOverDynamicMembers.cs" />
119+
<Compile Include="Dynamics\WhenMappingOverDynamics.cs" />
120+
<Compile Include="Dynamics\WhenMappingToNewEnumerablesOfDynamic.cs" />
121+
<Compile Include="Dynamics\WhenMappingToNewDynamicMembers.cs" />
122+
<Compile Include="Dynamics\WhenMappingToNewDynamics.cs" />
101123
<Compile Include="Extensions\WhenEquatingExpressions.cs" />
102124
<Compile Include="MapperCloning\WhenCloningConstructorDataSources.cs" />
103125
<Compile Include="MapperCloning\WhenCloningMemberIgnores.cs" />
@@ -180,7 +202,7 @@
180202
<Compile Include="WhenAnalysingCollections.cs" />
181203
<Compile Include="MapperCloning\WhenCloningDataSources.cs" />
182204
<Compile Include="WhenFlatteningObjects.cs" />
183-
<Compile Include="Dictionaries\WhenMappingNewObjectsFromDictionaries.cs" />
205+
<Compile Include="Dictionaries\WhenMappingFromDictionariesToNewComplexTypes.cs" />
184206
<Compile Include="Dictionaries\WhenMappingFromDictionaryMembers.cs" />
185207
<Compile Include="Dictionaries\WhenMappingOnToDictionaries.cs" />
186208
<Compile Include="Dictionaries\WhenMappingOnToDictionaryMembers.cs" />

AgileMapper.UnitTests/Configuration/WhenConfiguringNameMatching.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using AgileMapper.Configuration;
6-
using AgileMapper.Extensions;
6+
using AgileMapper.Extensions.Internal;
77
using Shouldly;
88
using TestClasses;
99
using Xunit;

AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Configuration
22
{
33
using System;
4+
using AgileMapper.Extensions.Internal;
45
using TestClasses;
56
using Xunit;
67

@@ -331,8 +332,7 @@ public void ShouldIgnoreMembersBySourceTypeTargetTypeAndPathMatch()
331332
mapper.WhenMapping
332333
.From<PublicField<Address>>()
333334
.To<PublicProperty<Address>>()
334-
.IgnoreTargetMembersWhere(member =>
335-
member.Path.Equals("Value.Line2", StringComparison.OrdinalIgnoreCase));
335+
.IgnoreTargetMembersWhere(member => member.Path.EqualsIgnoreCase("Value.Line2"));
336336

337337
var matchingSource = new PublicField<Address> { Value = new Address { Line1 = "Here", Line2 = "Here!" } };
338338
var nonMatchingSource = new { Value = new Address { Line1 = "There", Line2 = "There!" } };

AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Configuration
22
{
33
using System;
4-
using System.Linq;
4+
using AgileMapper.Extensions.Internal;
55
using Shouldly;
66
using TestClasses;
77
using Xunit;
@@ -209,8 +209,7 @@ public void ShouldIgnoreMembersByPathMatch()
209209
var source = new { Address = new Address { Line1 = "ONE!", Line2 = "TWO!" } };
210210

211211
mapper.WhenMapping
212-
.IgnoreTargetMembersWhere(member =>
213-
member.Path.Equals("Value.Line1", StringComparison.OrdinalIgnoreCase));
212+
.IgnoreTargetMembersWhere(member => member.Path.EqualsIgnoreCase("Value.Line1"));
214213

215214
mapper.WhenMapping
216215
.From(source)

AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public void ShouldErrorIfCustomMemberKeyIsNull()
1414
var configEx = Should.Throw<MappingConfigurationException>(() =>
1515
{
1616
Mapper.WhenMapping
17-
.Dictionaries
17+
.FromDictionaries
1818
.To<PublicField<string>>()
1919
.MapFullKey(null)
2020
.To(pf => pf.Value);
@@ -29,7 +29,7 @@ public void ShouldErrorIfCustomMemberNameIsNull()
2929
var configEx = Should.Throw<MappingConfigurationException>(() =>
3030
{
3131
Mapper.WhenMapping
32-
.Dictionaries
32+
.FromDictionaries
3333
.To<PublicField<string>>()
3434
.MapMemberNameKey(null)
3535
.To(pf => pf.Value);
@@ -46,12 +46,12 @@ public void ShouldErrorIfIgnoredMemberIsGivenCustomMemberKey()
4646
using (var mapper = Mapper.CreateNew())
4747
{
4848
mapper.WhenMapping
49-
.Dictionaries
49+
.FromDictionaries
5050
.To<Person>()
5151
.Ignore(p => p.Id);
5252

5353
mapper.WhenMapping
54-
.Dictionaries
54+
.FromDictionaries
5555
.To<Person>()
5656
.MapFullKey("PersonId")
5757
.To(p => p.Id);
@@ -69,12 +69,12 @@ public void ShouldErrorIfIgnoredMemberIsGivenCustomMemberName()
6969
using (var mapper = Mapper.CreateNew())
7070
{
7171
mapper.WhenMapping
72-
.Dictionaries
72+
.FromDictionaries
7373
.To<PublicField<string>>()
7474
.Ignore(pf => pf.Value);
7575

7676
mapper.WhenMapping
77-
.Dictionaries
77+
.FromDictionaries
7878
.To<PublicField<string>>()
7979
.MapMemberNameKey("ValueValue")
8080
.To(pf => pf.Value);
@@ -92,13 +92,13 @@ public void ShouldErrorIfCustomDataSourceMemberIsGivenCustomMemberKey()
9292
using (var mapper = Mapper.CreateNew())
9393
{
9494
mapper.WhenMapping
95-
.Dictionaries
95+
.FromDictionaries
9696
.To<Person>()
9797
.Map((d, p) => d.Count)
9898
.To(p => p.Name);
9999

100100
mapper.WhenMapping
101-
.Dictionaries
101+
.FromDictionaries
102102
.To<Person>()
103103
.MapFullKey("PersonName")
104104
.To(p => p.Name);
@@ -116,13 +116,13 @@ public void ShouldErrorIfCustomDataSourceMemberIsGivenCustomMemberName()
116116
using (var mapper = Mapper.CreateNew())
117117
{
118118
mapper.WhenMapping
119-
.Dictionaries
119+
.FromDictionaries
120120
.To<Person>()
121121
.Map((d, p) => d.Count)
122122
.To(p => p.Name);
123123

124124
mapper.WhenMapping
125-
.Dictionaries
125+
.FromDictionaries
126126
.To<Person>()
127127
.MapMemberNameKey("PersonName")
128128
.To(p => p.Name);
@@ -159,77 +159,76 @@ public void ShouldErrorIfAnUnreadableSourceMemberIsSpecified()
159159
}
160160

161161
[Fact]
162-
public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedGlobally()
162+
public void ShouldErrorIfRedundantSourceSeparatorIsConfigured()
163163
{
164164
var configEx = Should.Throw<MappingConfigurationException>(() =>
165165
{
166166
using (var mapper = Mapper.CreateNew())
167167
{
168168
mapper.WhenMapping
169-
.Dictionaries
170-
.UseFlattenedMemberNames()
171-
.UseMemberNameSeparator("+");
169+
.FromDictionaries
170+
.UseMemberNameSeparator(".");
172171
}
173172
});
174173

174+
configEx.Message.ShouldContain("already");
175175
configEx.Message.ShouldContain("global");
176-
configEx.Message.ShouldContain("flattened");
176+
configEx.Message.ShouldContain("'.'");
177177
}
178178

179179
[Fact]
180-
public void ShouldErrorIfMemberNamesAreSeparatedAndFlattenedGlobally()
180+
public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedGlobally()
181181
{
182182
var configEx = Should.Throw<MappingConfigurationException>(() =>
183183
{
184184
using (var mapper = Mapper.CreateNew())
185185
{
186186
mapper.WhenMapping
187-
.Dictionaries
188-
.UseMemberNameSeparator("+")
189-
.UseFlattenedMemberNames();
187+
.FromDictionaries
188+
.UseFlattenedTargetMemberNames()
189+
.UseMemberNameSeparator("+");
190190
}
191191
});
192192

193193
configEx.Message.ShouldContain("global");
194-
configEx.Message.ShouldContain("separated with '+'");
194+
configEx.Message.ShouldContain("flattened");
195195
}
196196

197197
[Fact]
198-
public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedForASpecificTargetType()
198+
public void ShouldErrorIfMemberNamesAreSeparatedAndFlattenedGlobally()
199199
{
200200
var configEx = Should.Throw<MappingConfigurationException>(() =>
201201
{
202202
using (var mapper = Mapper.CreateNew())
203203
{
204204
mapper.WhenMapping
205205
.Dictionaries
206-
.To<PublicField<PublicProperty<string>>>()
207-
.UseFlattenedMemberNames()
208-
.UseMemberNameSeparator("_");
206+
.UseMemberNameSeparator("+")
207+
.UseFlattenedTargetMemberNames();
209208
}
210209
});
211210

212-
configEx.Message.ShouldContain("PublicField<PublicProperty<string>>");
213-
configEx.Message.ShouldContain("flattened");
211+
configEx.Message.ShouldContain("global");
212+
configEx.Message.ShouldContain("separated with '+'");
214213
}
215214

216215
[Fact]
217-
public void ShouldErrorIfMemberNamesAreSeparatedAndFlattenedForASpecificTargetType()
216+
public void ShouldErrorIfDifferentSeparatorsSpecifiedForASpecificTargetType()
218217
{
219218
var configEx = Should.Throw<MappingConfigurationException>(() =>
220219
{
221220
using (var mapper = Mapper.CreateNew())
222221
{
223222
mapper.WhenMapping
224-
.Dictionaries
225-
.To<PublicProperty<PublicField<int>>>()
226-
.UseMemberNameSeparator("+")
227-
.UseFlattenedMemberNames();
223+
.FromDictionaries
224+
.To<PublicField<PublicProperty<string>>>()
225+
.UseMemberNameSeparator("-")
226+
.UseMemberNameSeparator("_");
228227
}
229228
});
230229

231-
configEx.Message.ShouldContain("PublicProperty<PublicField<int>>");
232-
configEx.Message.ShouldContain("separated with '+'");
230+
configEx.Message.ShouldContain("PublicField<PublicProperty<string>>");
231+
configEx.Message.ShouldContain("separated with '-'");
233232
}
234233

235234
[Fact]
@@ -280,6 +279,24 @@ public void ShouldErrorIfAnElementKeyPartHasMultipleIndexPlaceholders()
280279
"pattern must contain a single 'i' character as a placeholder for the enumerable index");
281280
}
282281

282+
[Fact]
283+
public void ShouldErrorIfRedundantGlobalElementKeyPartIsConfigured()
284+
{
285+
var configEx = Should.Throw<MappingConfigurationException>(() =>
286+
{
287+
using (var mapper = Mapper.CreateNew())
288+
{
289+
mapper.WhenMapping
290+
.Dictionaries
291+
.UseElementKeyPattern("[i]");
292+
}
293+
});
294+
295+
configEx.Message.ShouldContain("already");
296+
configEx.Message.ShouldContain("global");
297+
configEx.Message.ShouldContain("[i]");
298+
}
299+
283300
[Fact]
284301
public void ShouldErrorIfCustomTargetMemberKeyIsNotAConstant()
285302
{

0 commit comments

Comments
 (0)