A Better Way to Use Automapper to Flatten Nested Objects

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:

  1. Define two mappings, one from FlatObject --> Person and another from FlatObject --> Address:

    Mapper.CreateMap<FlatObject, Address>();

    Mapper.CreateMap<FlatObject, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom(src => src));
  2. 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



Leave a reply



Submit