Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make use of constructor arguments when instantiating mapping targets #73

Closed
gunnarmorling opened this issue Nov 24, 2013 · 48 comments
Closed
Labels
Milestone

Comments

@gunnarmorling
Copy link
Member

@gunnarmorling gunnarmorling commented Nov 24, 2013

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
Copy link
Member

@agudian agudian commented Jan 2, 2014

What kind of arguments do you mean?

@gunnarmorling
Copy link
Member Author

@gunnarmorling gunnarmorling commented Jan 3, 2014

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
Copy link

@fcamblor fcamblor commented Nov 18, 2014

👍 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
Copy link
Member Author

@gunnarmorling gunnarmorling commented Nov 18, 2014

@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 milestones: 1.0.0.RC1, 1.0.0.Beta3 Nov 25, 2014
@gunnarmorling gunnarmorling modified the milestones: 1.0.0.RC1, 1.0.0.Beta4 Mar 1, 2015
@pardom-zz
Copy link
Contributor

@pardom-zz pardom-zz commented Mar 7, 2015

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

@gunnarmorling
Copy link
Member Author

@gunnarmorling gunnarmorling commented Mar 8, 2015

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-zz
Copy link
Contributor

@pardom-zz pardom-zz commented Mar 8, 2015

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

@gunnarmorling
Copy link
Member Author

@gunnarmorling gunnarmorling commented Mar 9, 2015

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-zz
Copy link
Contributor

@pardom-zz pardom-zz 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 milestones: 1.1-next, 1.0.0.CR1 May 29, 2015
@mozinrat
Copy link

@mozinrat mozinrat commented Aug 14, 2015

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

@gunnarmorling
Copy link
Member Author

@gunnarmorling gunnarmorling commented Aug 14, 2015

@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
Copy link

@mozinrat mozinrat commented Aug 16, 2015

@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
Copy link
Member Author

@gunnarmorling gunnarmorling commented Aug 17, 2015

@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
Copy link

@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
Copy link
Member Author

@gunnarmorling gunnarmorling commented Feb 3, 2016

@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
Copy link

@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
Copy link

@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
Copy link

@hyungsul hyungsul commented Feb 10, 2016

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.

@mohamnag
Copy link

@mohamnag mohamnag commented Mar 1, 2018

I think I'm a bit too late to the party, however I wanted to ask why you think the @ConstructorProperties can not be reused? If by type you mean the constructor's parameter type, then it should be available by reflection from method signature right?

The thing is that there are other tools outside generating and using this like lombok for generation and jackson for deserialization. This means if it can be reused, it will just fall into place even for existing libs.

@almothafar
Copy link

@almothafar almothafar commented Apr 30, 2018

@gunnarmorling I was thinking about a solution for this like:

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

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

Or as what @mohamnag suggested:

@Mapper(config = CentralConfig.class)
public interface OrderMapper {
    public static OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

    @ConstructorProperties(source = "name", target = "name")
    Order map(OrderDTO orderVO);

    @InheritInverseConfiguration
    OrderDTO map(Order order);
}
@ade90036
Copy link

@ade90036 ade90036 commented Jul 8, 2018

Any update on this?

Feature requrst opened since 2013 and no concrete solution as yet.

I'm on the same boat as others. Need to work with immutable objects. Hence mapping via constructor is a must.

Regards

@sjaakd
Copy link
Contributor

@sjaakd sjaakd commented Jul 8, 2018

A possible constructor approach is not as simple as it seems. However support for immutables comes in the form of builders in the upcoming 1.3

@ade90036
Copy link

@ade90036 ade90036 commented Jul 8, 2018

Hi, thanks for the prompt reply.

I have guess that. 😀

The constructor approach nicely fits in with other libraries such as lombok and Jackson.

Would you be so kind to point me to PR or Issue number to see if this is something that would work in my scenario?

I'm currently using mapstruct with lombok for standard bean / bean mapping but for entity beans (database) objects where IDs are immutable I'm using custom converter.

Regards

@sjaakd
Copy link
Contributor

@sjaakd sjaakd commented Jul 8, 2018

There's an old PR pending. We had much internal discussion on that one. The problem is that you need to relate constructor arguments with source accessors. Remember: they can have the same type, so you must have a (compile safe) mechanism to point which arg goes where... Also selecting such constructor is not easy.

So.. this particular PR kept on dangling. Its hard to think of all situations here. So be careful were you put your teeth in 😄. I can't guarantee it will be merged.

Lombok supports a builder pattern by the way.. Not sure about Jackson though.

@eximius313
Copy link

@eximius313 eximius313 commented Aug 21, 2018

I see that version 1.3.0 of Mapstruct focuses on immutable objects - wich is great.
Unfortunately, currently only builders are supported.
I think that utilizing all-argument constructors (or the ones created by Lombok's @AllArgsConstructors) is vital for Immutables.

@piteron
Copy link

@piteron piteron commented Sep 28, 2018

It would be great to have such a constructor parameters injection support. Imagine this working with Kotlin's immutable data classes.

@hazzyeer
Copy link

@hazzyeer hazzyeer commented Sep 29, 2018

Domain Driven Design users really need this feature.

@ahulyk
Copy link

@ahulyk ahulyk commented Jan 24, 2019

any info on support Kotlin data classes?

@dsgoers
Copy link

@dsgoers dsgoers commented Oct 31, 2019

My team had this problem because there is a field that can only be set in the constructor; no setter. Here is what I did:

public abstract class Mapper {
    abstract Target toTarget(Source source, @MappingTarget Target target);

    public final Target toTarget(final Source source) {
        return toTarget(source, new Target(source.someGetter()));
    }
}
@marceloverdijk
Copy link
Contributor

@marceloverdijk marceloverdijk commented Dec 13, 2019

Besides using constructor arguments for mapping Kotlin Data classes, this will also get some interest from Java developers as with Java Records coming in JDK14 as part of JEP 359.

filiphr added a commit to filiphr/mapstruct that referenced this issue Apr 26, 2020
…iating mapping targets

By default the constructor argument names are used to extract the target properties.
If a constructor is annotated with an annotation named `@ConstructorProperties` (from any package) then it would be used to extract the target properties.

If a mapping target has a parameterless empty constructor it would be used to instantiate the target.
When there are multiple constructors then an annotation named `@Default` (from any package) can be used to mark a constructor that should be used by default when instantiating the target.

Supports mapping into Java 14 Records and Kotlin data classes out of the box
filiphr added a commit that referenced this issue Apr 26, 2020
…pping targets

By default the constructor argument names are used to extract the target properties.
If a constructor is annotated with an annotation named `@ConstructorProperties` (from any package) then it would be used to extract the target properties.

If a mapping target has a parameterless empty constructor it would be used to instantiate the target.
When there are multiple constructors then an annotation named `@Default` (from any package) can be used to mark a constructor that should be used by default when instantiating the target.

Supports mapping into Java 14 Records and Kotlin data classes out of the box
@filiphr filiphr modified the milestones: Future planning, 1.4.0 Apr 26, 2020
@filiphr
Copy link
Member

@filiphr filiphr commented Apr 26, 2020

After almost 7 years we have finally added support for using constructors when instantiating mapping targets.

This would be part of our next 1.4 release. I hope to have a beta as soon as possible.

@sjaakd
Copy link
Contributor

@sjaakd sjaakd commented Apr 26, 2020

Hey Filip.. Congrats!

@filiphr
Copy link
Member

@filiphr filiphr commented Apr 26, 2020

Forgot to add. This supports mapping into Java 14 records and Kotlin data classes

@ahulyk
Copy link

@ahulyk ahulyk commented Apr 26, 2020

Cool :) Thanks!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

You can’t perform that action at this time.