mapperly icon indicating copy to clipboard operation
mapperly copied to clipboard

Reverse mappings

Open InspiringCode opened this issue 1 year ago • 6 comments

Are there any plans to support reverse mappings like they are supported in AutoMapper?

In some of our projects we use this functionality quite extensively but we want to slowly migrate to Mapperly and don't want to duplicate all our mappings for both directions.

I would really love to hear your plans/opinions about this.

InspiringCode avatar Apr 24 '24 09:04 InspiringCode

How would you like to define such a reverse mapping in your mapper definition? Currently Mapperly only ever provides the implementation to user implemented methods.

latonz avatar Apr 25 '24 21:04 latonz

Here is my first idea about a possible API with a (bit contrived) example:

Let's assume we have the following classes:

public class ProductDTO {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public decimal PriceInEuro { get; set; }
    public decimal Discount { get; set ;}
    public string Category { get; set; }
}

public class Product {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public decimal DiscountScheme Discounts { get; } = new();
    public ProductCategory Category { get; set; }
    public User CreatedBy { get; set; }
}

public class UpdateDiscount {
    public Guid Id { get; set; }
    public DiscountScheme Discounts { get; } = new ();
}

Given the following mapper:

public static partial class Mapper {
    [MapProperty(nameof(Product.Price), nameof(ProductDTO.PriceInEuro))]
    [MapProperty(nameof(@Product.Discounts.EndUsers), nameof(Discount))]
    [MapProperty(nameof(Product.Category), nameof(ProductDTO.Category), Use=nameof(CategoryToString))]
    public static partial ProductDTO ToDTO(this Product source);

    [MapReverse(nameof(ToDTO))]
    [MapProperty(nameof(ProductDTO.Categor), nameof(Product.Category), Use=nameof(StringToCategory))]
    public static partial void ToProduct(this ProductDTO source, Product target);

    [MapReverse(nameof(ToDTO))]
    public static partial void ToUpdateDiscount(this ProductDTO source, UpdateDiscount target);
}

Then we could perform the following mappings:

ProductDTO dto = new() { Name = "test", PriceInEuro = 42, Discount = 15, Category = "Tech" };
Product p = new Product(); // TODO: load actual product here
dto.ToProduct(p);
p.Name.Should().Be("test");
p.Price.Should().Be(42);
p.Category.Should().Be(new ProductCategory("Tech"));
p.DiscountSchme.EndUsers.Should().Be(15);

UpdateDiscount u = new(); // TODO: It would make sense, to preinit the DiscountScheme here
dto.ToUpdateProduct(u);
u.Id.Should().Be(dto.Id);
u.DiscountScheme.EndUsers.Should().Be(15);

Remarks:

  • For the Discount property, an unflattening is automatically performance inverse to the original MapProperty.
  • Renames done via MapProperty are reversed.
  • Mappings can be manually overwritten.

For MapProperty attributes that have set a custom converter, I see to possible API variants:

  1. Specify MapProperty on each reverse mapping as in the example above.
  2. Specify by the inverse converter in the original mapping. E.g. `[MapProperty(..., Use = nameof(CategoryToString), UseReverse=nameof(StringToCategory)]

The second approach would have the advantage, that I don't have to duplicate the inverse mapping (StringToCategory) on each reverse map that includes the Category property. On the downside it adds one additional property to the API.

I think this would be a big step to closing the gap between Mapperly and AutoMapper. When I first presented Mapperly within my company, the very first question asked by colleagues was, whether Mapperly supports inverse mappings or not. They said that they use this feature a lot with AutoMapper.

What do you think?

InspiringCode avatar Apr 26 '24 09:04 InspiringCode

Rel. https://github.com/riok/mapperly/issues/513 Can you elaborate on

Mappings can be manually overwritten.

How would that work? Based on what it is decided whether this is an additional configuration or should overwrite an existing one?

I'm not a fan of UseReverse as this would bloat the API (for every configuration a *Reverse would be needed. We could probably just assume the same configuration as the original MapProperty had. If this does not work (which probably would often be the case as the Use methods only work one way etc.), the user needs to specify an explicit MapProperty attribute with a new configuration (overwrite the existing one).

latonz avatar May 16 '24 02:05 latonz

Mappings can be manually overwritten.

By that I meant exactly what you described in your answer:

If this does not work (which probably would often be the case as the Use methods only work one way etc.), the user needs to specify an explicit MapProperty attribute with a new configuration (overwrite the existing one).

Are there any plans to implement reverse mappings?

InspiringCode avatar Jun 05 '24 11:06 InspiringCode

I like the idea of supporting this, but our resources are limited and this is feature is not a top priority at the moment. However, we are happy to accept community PR's though!

latonz avatar Jun 05 '24 23:06 latonz

Instead of a new attribute this could work by implementing https://github.com/riok/mapperly/issues/513 IncludeMappingConfiguration with an additional property Reverse, [IncludeMappingConfiguration(nameof(ToDTO), Reverse = true)].

latonz avatar Jun 05 '24 23:06 latonz