Make use of constructor arguments when instantiating mapping targets #73

Open
gunnarmorling opened this Issue Nov 24, 2013 · 25 comments

Projects

None yet
@gunnarmorling
Member

Some thoughts:

  • Need a way to select the constructor to use if there are several ones
  • Properties not covered by the chosen constructor should be populated by setters
  • Optionally, the mechanism should take parameterized factory methods into account
@agudian
Member
agudian commented Jan 2, 2014

What kind of arguments do you mean?

@gunnarmorling
Member

I'd like to add some kind of support for immutable target types, which take their attribute values through a constructor:

public class Order {
    private final long id;
    private final String name;

    public Order(long id, String name) { ... }
 }

The generated code would then have to invoke this constructor instead of relying on the default constructor and setters. Generally this should be possbible as we can access the parameter names in the annotation processor.

An open question is how to chose a constructor in case there are several. Optionally existing setters could be invoked for attributes not present in the invoked constructor.

@fcamblor

👍 on this

We could introduce a @Constructor({"id","name"}) annotation allowing to bind mapping to a dedicated constructor, using named parameters (not types which could be ambiguous and less obvious regarding API readability)

WDYT ?

@gunnarmorling
Member

@fcamblor, yes, that sounds very reasonable. I first thought @ConstructorProperties from the JDK could be re-used, but that lacks the required element type. +1 for using names.

One thing though: I assume it's a common case that there is a default constructor (no args) and one constructor taking the bean properties. It'd be nice to globally configure which one to use in this case without the need to specify @Constructor for each single method.

@gunnarmorling gunnarmorling modified the milestone: 1.0.0.RC1, 1.0.0.Beta3 Nov 25, 2014
@gunnarmorling gunnarmorling modified the milestone: 1.0.0.RC1, 1.0.0.Beta4 Mar 1, 2015
@pardom
Contributor
pardom commented Mar 7, 2015

I would also like to see this, specifically for immutable objects. Any update?

@gunnarmorling
Member

No update really, but it's one of the remaining things which I think should be done for 1.0. Would you be interested in giving this a try?

@pardom
Contributor
pardom commented Mar 8, 2015

I might be. Can you point me in the right direction?

@gunnarmorling
Member

Some helpful docs to get started are:

For this issue, you'd probably have to adapt PropertyMapping (describs the "link" between a source and target property, so this would somehow have to express that a property is propagated by a constructor argument rather than a setter call) and BeanMappingMethod (describes one mapping method between two bean types, so this would have to express which constructor of the target type to invoke).

Hope this helps for a start, let me know in case you run into any road blocks. Thanks!

@pardom
Contributor
pardom commented Mar 12, 2015

Hmm, not sure I have the time to spend on this right now, so I can't promise I'll get it done. This feature would satisfy my immutability requirement though, so kudos to whoever does it. 👍

@gunnarmorling gunnarmorling modified the milestone: 1.1-next, 1.0.0.CR1 May 29, 2015
@mozinrat

I would love to use this feature if its there, can anyone point me about whats being done.

@gunnarmorling
Member

@mozinrat no one found the time to implement it yet unfortunately. If you are interested and would like to help out with building it, let me know and I will help you to get running with Hacking on MapStruct.

@mozinrat

@gunnarmorling whats the plan for this, lets connect on some platform email etc and discuss the possibility. I can give it a week or so but its hard for me to give any more time. Forking the repo meanwhile...:)

@gunnarmorling
Member

@mozinrat It should be doable in a week :)

You can send a mail to mapstruct-users to get a discussion started on the feature and its implementation. To get started, you can checkout the information given here and the technical documentation hints given in the section after that.

I think the main issue to decide is how to select the constructor to be chosen in case there are several ones, e.g. a default (no-args) constructor and one or two constructors with arguments.

Many thanks for your help @mozinrat, looking forward to this feature very much! Release we are in a stabilization phase for the 1.0 Final release, this feature should go into 1.1 from my POV.

@pjean
pjean commented Feb 3, 2016

👍 I would like to be able to use immutable object.
I think you should take a look at the Jackson project which uses the constructor and some annotations for serialization and deserialization. It should be quite similar to map the target.
This issue is a good start for investigation.

@gunnarmorling
Member

@pjean, yes, support for this is very high on my wish list. It's basically a question of resources, so if someone from the community stepped up to give this a shot, this would be awesome. Maybe you'd be interested?

@pjean
pjean commented Feb 3, 2016

Need to go deep into mapstruct code. I will take a look if I find enough time and will keep you in touch.

@afdia
afdia commented Feb 7, 2016

There is an alternative solution to the "which constructor to use" problem: Provider-Methods, which are used by Guice and Dagger (DI frameworks) when they should create classes without an @Inject annotation (I guess CDI uses the same concept but it's called Producers).

Instead of annotating a constructor, you would write a provider method. e.g.:

class Car {
    int doors;
    Color color;
    public Car(int doors, Color color) {
        this.doors = doors;
        this.color = color;
    }
}

The provider method could be located in the mapper class and would look like

@Provides
public Car createCar(int doors, Color color) {
    return new Car(doors, color);
}

This approach introduces some overhead (because the user has to map the method parameters to the appropriate constructor field), but is much more flexible and non invasive to the class which should be mapped.

This could be an advantage when classes should not or cannot be modified (e.g. generated classes or classes of a 3rd party lib) and would also enable alternative ways to create a class (e.g. calling a static creator method)

The disadvantage is that MapStruct can only verify the correctness of the provider method arguments, but not the correctness of the method argument -> constructor argument mapping.

I guess the best solution would be letting the user choose between both ways (80% of the time the annotation would be sufficient, but otherwise the more flexible approach would be nice to have)

Just some food for thought :)

@hyungsul

I am currently investigating this issue. In my project, I modeled DTOs as immutable and have the same problem with this issue. Because I have a long list of parameters in DTOs, it is inefficient to list all the constructor parameters in the mapping annotation.

For some cases, it is good enough to have a boolean flag like "immutable" so that all the values are mapped via a constructor instead of setters without listing all the constructor parameters in the annotation.

@gunnarmorling
Member

@afdia Thanks for your input! We already have "factory methods", but they don't they take arguments either. But we could enhance those, making them just like producer methods in CDI. Qualifiers could be used to resolve ambiguities.

In the end I'd like to see support for both: parameterized constructors and parameterized factory/producer methods.

@gunnarmorling
Member

@hyungsul, agreed some means of simplified configuration would be great, so to select between e.g. the default constructor and a single parameterized one.

@cliedeman
Contributor

I have started work on this here #847

@sjaakd
Contributor
sjaakd commented Aug 8, 2016

👍 i'll take a look as soon as I'm back .

Op 8 aug. 2016 om 10:28 heeft Ciaran Liedeman notifications@github.com het volgende geschreven:

I have started work on this here #847


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.

@mattiasoamz

Would you consider adding support for Guava immutable collections as well? Not sure if it should be handled as part of this issue - it does relate to support immutable beans but from an implementation perspective it's a different thing.

@filiphr
Member
filiphr commented Dec 15, 2016

@mattiasoamz This can possibly done via some conversions. Can you perhaps create a new issue for this and maybe specify how would you use them (the use case)? I have not used the Guava immutable collections, and an example bean with them would be much appreciated 😄

@mattiasoamz

@filiphr I'm sorry for my late response - the holidays got in between. I have created #1025.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment