Skip to content

Commit 1567b00

Browse files
authored
Bugs/203 (#206)
* Support for mapping between complex-type enumerable Dictionaries, re: #203 * Tidying
1 parent d4eb33c commit 1567b00

File tree

6 files changed

+93
-41
lines changed

6 files changed

+93
-41
lines changed

AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public void ShouldFlattenAComplexTypeCollectionToANestedObjectDictionaryImplemen
341341

342342
// See https://github.com/agileobjects/AgileMapper/issues/200
343343
[Fact]
344-
public void ShouldMapListDictionaries()
344+
public void ShouldMapBetweenListDictionaries()
345345
{
346346
var source = new PublicField<Dictionary<string, List<string>>>
347347
{
@@ -364,7 +364,7 @@ public void ShouldMapListDictionaries()
364364
}
365365

366366
[Fact]
367-
public void ShouldMapArrayDictionaries()
367+
public void ShouldMapBetweenArrayDictionaries()
368368
{
369369
var source = new PublicField<Dictionary<string, int[]>>
370370
{
@@ -384,7 +384,7 @@ public void ShouldMapArrayDictionaries()
384384
.Value
385385
.ShouldNotBeNull();
386386

387-
resultDictionary.Count.ShouldBe(3); ;
387+
resultDictionary.Count.ShouldBe(3);
388388
resultDictionary["1"].ShouldHaveSingleItem().ShouldBe(1L);
389389

390390
resultDictionary["2"].Length.ShouldBe(2);
@@ -397,6 +397,46 @@ public void ShouldMapArrayDictionaries()
397397
resultDictionary["3"][2].ShouldBe(3L);
398398
}
399399

400+
// See https://github.com/agileobjects/AgileMapper/issues/203
401+
[Fact]
402+
public void ShouldMapBetweenComplexTypeArrayDictionaries()
403+
{
404+
var source = new PublicField<Dictionary<string, Address[]>>
405+
{
406+
Value = new Dictionary<string, Address[]>
407+
{
408+
["1"] = new[] { new Address { Line1 = "1.1.1" } },
409+
["2"] = new[]
410+
{
411+
new Address { Line1 = "2.1.1" },
412+
new Address { Line1 = "2.2.1", Line2 = "2.2.2" }
413+
}
414+
}
415+
};
416+
417+
var result = Mapper.Map(source)
418+
.ToANew<PublicProperty<Dictionary<string, Address[]>>>();
419+
420+
var resultDictionary = result
421+
.ShouldNotBeNull()
422+
.Value
423+
.ShouldNotBeNull();
424+
425+
resultDictionary.Count.ShouldBe(2);
426+
var address11 = resultDictionary["1"].ShouldHaveSingleItem().ShouldNotBeNull();
427+
address11.Line1.ShouldBe("1.1.1");
428+
address11.Line2.ShouldBeNull();
429+
430+
resultDictionary["2"].Length.ShouldBe(2);
431+
var address21 = resultDictionary["2"][0].ShouldNotBeNull();
432+
address21.Line1.ShouldBe("2.1.1");
433+
address21.Line2.ShouldBeNull();
434+
435+
var address22 = resultDictionary["2"][1].ShouldNotBeNull();
436+
address22.Line1.ShouldBe("2.2.1");
437+
address22.Line2.ShouldBe("2.2.2");
438+
}
439+
400440
#region Helper Members
401441

402442
private static class Issue97

AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
{
33
using System.Collections.Generic;
44
using System.Collections.ObjectModel;
5+
#if !NETCOREAPP1_0
56
using System.ComponentModel;
7+
#endif
68
using System.Linq;
79
using Common;
810
using TestClasses;

AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ private Expression GetKey(IMemberMapperData mapperData)
190190

191191
private Expression GetDictionaryAccess(IMemberMapperData mapperData)
192192
{
193-
var parentContextAccess = mapperData.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type);
193+
var parentContextAccess = mapperData
194+
.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type);
194195

195196
if (parentContextAccess.NodeType != Parameter)
196197
{
@@ -207,7 +208,8 @@ private Expression GetDictionaryAccess(IMemberMapperData mapperData)
207208
return dictionaryMapperData.TargetInstance;
208209
}
209210

210-
public override bool CheckExistingElementValue => !HasObjectEntries && !HasSimpleEntries;
211+
public override bool CheckExistingElementValue
212+
=> !(HasObjectEntries || HasSimpleEntries || HasEnumerableEntries);
211213

212214
public override Expression GetHasDefaultValueCheck(IMemberMapperData mapperData)
213215
{
@@ -346,23 +348,22 @@ public DictionaryTargetMember WithTypeOf(Member sourceMember)
346348

347349
public override void MapCreating(Type sourceType)
348350
{
349-
if (CreateNonDictionaryChildMembers(sourceType))
351+
if (DoNotFlattenSourceObjects(sourceType))
350352
{
351353
_createDictionaryChildMembers = false;
352354
}
353355

354356
base.MapCreating(sourceType);
355357
}
356358

357-
private bool CreateNonDictionaryChildMembers(Type sourceType)
359+
private bool DoNotFlattenSourceObjects(Type sourceType)
358360
{
359-
// If this DictionaryTargetMember represents an object-typed dictionary
360-
// entry and we're mapping from a source of type object, we switch from
361-
// mapping to flattened entries to mapping entire objects:
362-
return HasObjectEntries &&
363-
this.IsEnumerableElement() &&
364-
(MemberChain[Depth - 2] == _rootDictionaryMember.LeafMember) &&
365-
(sourceType == typeof(object));
361+
// If this target Dictionary member's type matches the type of source
362+
// objects being mapped into it, we switch from flattening source
363+
// objects into Dictionary entries to mapping entire objects:
364+
return this.IsEnumerableElement() &&
365+
(ValueType == sourceType) &&
366+
(MemberChain[Depth - 2] == _rootDictionaryMember.LeafMember);
366367
}
367368

368369
#region ExcludeFromCodeCoverage

AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private Expression AssignDictionaryEntry(
124124
return GetPopulation(loopData, dictionaryEntryMember, mappingData);
125125
}
126126

127-
mappingData = GetMappingData(mappingData);
127+
mappingData = GetEntryMappingData(mappingData);
128128

129129
if (dictionaryEntryMember.HasComplexEntries)
130130
{
@@ -275,14 +275,12 @@ private Expression GetPopulation(
275275
return populationExpression;
276276
}
277277

278-
private IObjectMappingData GetMappingData(IObjectMappingData mappingData)
278+
private IObjectMappingData GetEntryMappingData(IObjectMappingData mappingData)
279279
{
280280
var sourceElementType = _wrappedBuilder.Context.SourceElementType;
281281
var targetElementType = _targetDictionaryMember.GetElementType(sourceElementType);
282282

283-
mappingData = ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData);
284-
285-
return mappingData;
283+
return ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData);
286284
}
287285

288286
private static Expression GetDerivedTypeMapping(

AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,15 @@ private void CreateSourceTypeHelper(Expression sourceValue)
321321

322322
#region Target Variable Population
323323

324-
public void PopulateTargetVariableFromSourceObjectOnly(IObjectMappingData mappingData = null)
325-
=> AssignTargetVariableTo(GetSourceOnlyReturnValue(mappingData));
324+
public void PopulateTargetVariableFromSourceObjectOnly(IObjectMappingData enumerableMappingData = null)
325+
=> AssignTargetVariableTo(GetSourceOnlyReturnValue(enumerableMappingData));
326326

327-
private Expression GetSourceOnlyReturnValue(IObjectMappingData mappingData)
327+
private Expression GetSourceOnlyReturnValue(IObjectMappingData enumerableMappingData)
328328
{
329-
var convertedSourceItems = _sourceItemsSelector.SourceItemsProjectedToTargetType(mappingData).GetResult();
329+
var convertedSourceItems = _sourceItemsSelector
330+
.SourceItemsProjectedToTargetType(enumerableMappingData)
331+
.GetResult();
332+
330333
var returnValue = ConvertForReturnValue(convertedSourceItems);
331334

332335
return returnValue;
@@ -510,15 +513,15 @@ public void RemoveAllTargetItems()
510513
_populationExpressions.Add(GetTargetMethodCall("Clear"));
511514
}
512515

513-
public void AddNewItemsToTargetVariable(IObjectMappingData mappingData)
516+
public void AddNewItemsToTargetVariable(IObjectMappingData enumerableMappingData)
514517
{
515518
if (TargetElementsAreSimple && Context.ElementTypesAreTheSame && TargetTypeHelper.IsList)
516519
{
517520
_populationExpressions.Add(GetTargetMethodCall("AddRange", _sourceVariable));
518521
return;
519522
}
520523

521-
BuildPopulationLoop(GetElementPopulation, mappingData);
524+
BuildPopulationLoop(GetElementPopulation, enumerableMappingData);
522525
}
523526

524527
public void BuildPopulationLoop(
@@ -535,9 +538,11 @@ public void BuildPopulationLoop(
535538
_populationExpressions.AddUnlessNullOrEmpty(populationLoop);
536539
}
537540

538-
private Expression GetElementPopulation(IPopulationLoopData loopData, IObjectMappingData mappingData)
541+
private Expression GetElementPopulation(
542+
IPopulationLoopData loopData,
543+
IObjectMappingData enumerableMappingData)
539544
{
540-
var elementMapping = loopData.GetElementMapping(mappingData);
545+
var elementMapping = loopData.GetElementMapping(enumerableMappingData);
541546

542547
if (elementMapping == Constants.EmptyExpression)
543548
{
@@ -568,24 +573,26 @@ private bool InsertSourceObjectElementNullCheck(IPopulationLoopData loopData, ou
568573
return sourceElement.Type == typeof(object);
569574
}
570575

571-
public Expression GetElementConversion(Expression sourceElement, IObjectMappingData mappingData)
576+
public Expression GetElementConversion(
577+
Expression sourceElement,
578+
IObjectMappingData enumerableMappingData)
572579
{
573580
if (TargetElementsAreSimple)
574581
{
575582
return GetSimpleElementConversion(sourceElement);
576583
}
577584

578-
var targetMember = mappingData.MapperData.TargetMember;
585+
var targetMember = enumerableMappingData.MapperData.TargetMember;
579586

580587
Expression existingElementValue;
581588

582-
if (targetMember.CheckExistingElementValue && mappingData.MapperData.TargetCouldBePopulated())
589+
if (targetMember.CheckExistingElementValue && enumerableMappingData.MapperData.TargetCouldBePopulated())
583590
{
584-
var existingElementValueCheck = targetMember.GetAccessChecked(mappingData.MapperData);
591+
var existingElementValueCheck = targetMember.GetAccessChecked(enumerableMappingData.MapperData);
585592

586593
if (existingElementValueCheck.Variables.Any())
587594
{
588-
return GetValueCheckedElementMapping(sourceElement, existingElementValueCheck, mappingData);
595+
return GetValueCheckedElementMapping(sourceElement, existingElementValueCheck, enumerableMappingData);
589596
}
590597

591598
existingElementValue = existingElementValueCheck;
@@ -595,7 +602,7 @@ public Expression GetElementConversion(Expression sourceElement, IObjectMappingD
595602
existingElementValue = Context.TargetElementType.ToDefaultExpression();
596603
}
597604

598-
return GetElementMapping(sourceElement, existingElementValue, mappingData);
605+
return GetElementMapping(sourceElement, existingElementValue, enumerableMappingData);
599606
}
600607

601608
private static Expression GetValueCheckedElementMapping(
@@ -797,7 +804,8 @@ internal SourceItemsSelector(EnumerablePopulationBuilder builder)
797804
_builder = builder;
798805
}
799806

800-
public SourceItemsSelector SourceItemsProjectedToTargetType(IObjectMappingData mappingData = null)
807+
public SourceItemsSelector SourceItemsProjectedToTargetType(
808+
IObjectMappingData enumerableMappingData = null)
801809
{
802810
var context = _builder.Context;
803811
var sourceEnumerableValue = _builder._sourceAdapter.GetSourceValues();
@@ -811,7 +819,7 @@ public SourceItemsSelector SourceItemsProjectedToTargetType(IObjectMappingData m
811819

812820
_result = _builder.GetSourceItemsProjection(
813821
sourceEnumerableValue,
814-
sourceElement => _builder.GetElementConversion(sourceElement, mappingData));
822+
sourceElement => _builder.GetElementConversion(sourceElement, enumerableMappingData));
815823

816824
return this;
817825
}

AgileMapper/ObjectPopulation/MappingFactory.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
using System.Linq.Expressions;
77
#endif
88
using Caching.Dictionaries;
9-
using Extensions;
109
using Extensions.Internal;
1110
using Members;
1211
using Members.MemberExtensions;
12+
using NetStandardPolyfills;
1313

1414
internal static class MappingFactory
1515
{
@@ -88,7 +88,7 @@ public static Expression GetElementMapping(
8888
{
8989
var mapperData = mappingData.MapperData;
9090

91-
if (CreateElementMappingDataFor(mapperData))
91+
if (CreateElementMappingDataFor(mapperData, sourceElementValue))
9292
{
9393
mappingData = ObjectMappingDataFactory.ForElement(mappingData);
9494
}
@@ -103,16 +103,19 @@ public static Expression GetElementMapping(
103103
return GetElementMapping(mappingData, sourceElementValue, targetElementValue);
104104
}
105105

106-
private static bool CreateElementMappingDataFor(IQualifiedMemberContext context)
106+
private static bool CreateElementMappingDataFor(
107+
ObjectMapperData mapperData,
108+
Expression sourceElementValue)
107109
{
108-
if (!context.TargetMemberIsEnumerableElement())
110+
if (!mapperData.TargetMemberIsEnumerableElement())
109111
{
110112
return true;
111113
}
112114

113-
if (context.TargetMember.IsEnumerable)
115+
if (mapperData.TargetMember.IsEnumerable)
114116
{
115-
return !context.TargetMember.ElementType.IsSimple();
117+
return !mapperData.EnumerablePopulationBuilder.TargetElementsAreSimple &&
118+
sourceElementValue.Type.IsAssignableTo(mapperData.SourceMember.ElementType);
116119
}
117120

118121
return false;

0 commit comments

Comments
 (0)