A better way to use AutoMapper to flatten nested objects?
I much prefer avoiding the older Static methods and do it like this.
Place our mapping definitions into a Profile. We map the Root first, and then apply the mappings of the Nested afterwards. Note the use of the Context.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Root, Flattened>()
.AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
CreateMap<Nested, Flattened>();
}
}
The advantage of defining both the mapping from Root to Flattened and Nested to Flatterned is that you retain full control over the mapping of the properties, such as if the destination property name is different or you want to apply a transformation etc.
An XUnit test:
[Fact]
public void Mapping_root_to_flattened_should_include_nested_properties()
{
// ARRANGE
var myRoot = new Root
{
AParentProperty = "my AParentProperty",
TheNestedClass = new Nested
{
ANestedProperty = "my ANestedProperty"
}
};
// Manually create the mapper using the Profile
var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();
// ACT
var myFlattened = mapper.Map<Root, Flattened>(myRoot);
// ASSERT
Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);
Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty);
}
By adding AutoMapper's serviceCollection.AddAutoMapper() from the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package to your start up, the Profile will be picked up automatically, and you can simply inject IMapper into wherever you are applying the mapping.
automapper automapping deep object to flat object and back
Two ways you could accomplish this:
Define two mappings, one from
FlatObject --> Person
and another fromFlatObject --> Address
:Mapper.CreateMap<FlatObject, Address>();
Mapper.CreateMap<FlatObject, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(src => src));Define one mapping and create the
Address
object inside the mapping definition:Mapper.CreateMap<FlatObject, Person>()
.ForMember(
dest => dest.Address,
opt => opt.MapFrom(
src => new Address { City = src.City, State = src.State }));
Personally I'd go with option 1. This way if you add properties to FlatObject
, you won't have to worry about updating the mapping definition (you would if you used option #2).
However, @Raphaël is correct in pointing out the author's link that questions the validity of mapping to domain objects.
AutoMapper and flattening nested arrays
Try this mapper,
Mapper.CreateMap<Z, Destination>();
Mapper.CreateMap<Y, Destination>();
Mapper.CreateMap<X, Destination>()
.ForMember(destination => destination.A, options => options.MapFrom(source => source.A)).IgnoreAllNonExisting()
.ForMember(destination => destination.C, options => options.MapFrom(source => Mapper.Map<IEnumerable<Y>, IEnumerable<Destination>>(source.B).FirstOrDefault().C))
.ForMember(destination => destination.E, options => options.MapFrom(source => Mapper.Map<IEnumerable<Z>, IEnumerable<Destination>>(source.B.SelectMany(d => d.D)).FirstOrDefault().E))
.ForMember(destination => destination.F, options => options.MapFrom(source => Mapper.Map<IEnumerable<Z>, IEnumerable<Destination>>(source.B.SelectMany(d => d.D)).FirstOrDefault().F));
var result = Mapper.Map<IEnumerable<X>, IEnumerable<Destination>>(arrayOfX);
Flatten Nested List using AutoMapper
A far easier way is to write a simple extension method using some LINQ to do this projection yourself. It's easier and more transparent:
public static class MyConversionExtensions
{
public static IEnumerable<ShipmentDetailsDTO> ToShipmentDetails(this RootObject root)
{
return root.BaseOrderShipmentLineitem.Select(x => new ShipmentDetailsDTO() {
BaseOrderShipmentLineitemId = x.BaseOrderLineitem.Id,
BaseSupplierName = root.BaseSupplier.Name,
Sku = x.BaseOrderLineitem.ProductSku
});
}
}
Usage:
var shipmentDetails = myRootObject.ToShipmentDetails();
AutoMapper Flattening out nested json
You have created a Profile that tells Automapper how to convert a single JiraTicket
to a single JiraDto
, but I think the problem is that you are trying to map a IEnumerable<JiraTicket>
to a single JiraDto
, and Automapper doesn't know how to do that.
What you probably want to do (if I understand correctly) is to map a IEnumerable<JiraTicket>
to a IEnumerable<JiraDto>
. Then, you would have to do this:
public async Task<IEnumerable<JiraDto>> GetAll()
{
var tickets = _jiraRepo.GetAll().ToList();
var result = _mapper.Map<IEnumerable<JiraDto>>(tickets);
return result;
}
AutoMapper flattening of nested mappings asks for a custom resolver
I've done something similar before using .ForMember with an internal mapping for the VolumnInfo mapping:
public static class AutoMapperConfigurator
{
public static void Configure()
{
Mapper.CreateMap<Book, VolumeInfo>()
.ForMember(dto => dto.Authors, options => options.MapFrom(book => book.Author.Split(',')))
.ForMember(dto => dto.PublishedDate, options => options.MapFrom(book => book.Publication))
.ForMember(dto => dto.PageCount, options => options.MapFrom(book => book.Pages))
.ForMember(dto => dto.IndustryIdentifiers, options => options.Ignore());
Mapper.CreateMap<Book, BookDto>()
.ForMember(dto => dto.Id, options => options.Ignore())
.ForMember(dto => dto.Kind, options => options.Ignore())
.ForMember(dto => dto.VolumeInfo, options => options.MapFrom(book => Mapper.Map<Book, VolumeInfo>(book)));
}
}
Here are a couple of unit tests that verify the functionality:
[TestFixture]
public class MappingTests
{
[Test]
public void AutoMapper_Configuration_IsValid()
{
AutoMapperConfigurator.Configure();
Mapper.AssertConfigurationIsValid();
}
[Test]
public void AutoMapper_MapsAsExpected()
{
AutoMapperConfigurator.Configure();
Mapper.AssertConfigurationIsValid();
var book = new Book
{
Author = "Castle,Rocks",
Description = "Awesome TV",
InStock = true,
Isbn10 = "0123456789",
Isbn13 = "0123456789012",
Pages = 321321,
Publication = new DateTime(2012, 11, 01),
Publisher = "Unknown",
Title = "Why I Rock"
};
var dto = Mapper.Map<Book, BookDto>(book);
Assert.That(dto.Id, Is.Null);
Assert.That(dto.Kind, Is.Null);
Assert.That(dto.VolumeInfo, Is.Not.Null);
Assert.That(dto.VolumeInfo.Authors, Is.Not.Null);
Assert.That(dto.VolumeInfo.Authors.Count, Is.EqualTo(2));
Assert.That(dto.VolumeInfo.Authors[0], Is.EqualTo("Castle"));
Assert.That(dto.VolumeInfo.Authors[1], Is.EqualTo("Rocks"));
Assert.That(dto.VolumeInfo.Description, Is.EqualTo("Awesome TV"));
Assert.That(dto.VolumeInfo.IndustryIdentifiers, Is.Null);
Assert.That(dto.VolumeInfo.PageCount, Is.EqualTo(321321));
Assert.That(dto.VolumeInfo.PublishedDate, Is.EqualTo(new DateTime(2012, 11, 01).ToString()));
Assert.That(dto.VolumeInfo.Publisher, Is.EqualTo("Unknown"));
Assert.That(dto.VolumeInfo.Title, Is.EqualTo("Why I Rock"));
}
}
Related Topics
Reload Page After Jquery.Get to MVC Controller Action
What Is the Purpose of a Question Mark After a Type (For Example: Int Myvariable)
What Could Cause an Assignment to Not Work
How to Round Up the Time to the Nearest X Minutes
How to Trigger Event When a Variable'S Value Is Changed
C# Json Serialization of Dictionary into {Key:Value, ...} Instead of {Key:Key, Value:Value, ...}
Adding an Incremental Number to Duplicate String
Entity Framework Migrations Renaming Tables and Columns
Check If Datetime Is a Weekend or a Weekday
System.Collections.Generic.List Does Not Contain a Definition for 'Select'
Unexpected Character Encountered While Parsing Value
How to Close a File That Is Already Opened in a Directory
Deserialize Json in C# - How to Handle Null Return Values
How to Ignore First Two Columns of CSV File
Regex to Remove All Special Characters from String
Calling a Function from Another Class in C#/.Net MVC App
Remove the Escape Sequence '\' from String to Convert It to Xmldocument