mapstruct / mapstruct/mapstruct

using @MappingTarget with @Default constructor annotated member class

Open
#2,178 3 comments 0 reactions 1 assignee View on GitHub

@sjaakd is already working on this.

Since Aug 21, 2020.

feature for:team-discussion
Dominant language
Java
Stars
7.7k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

Mapping method with @MappingTarget generates code that tries to alter immutable member instead of setting a new instance.

Example:
If I have a class with a constructor injected member:
```
public class DemonstratorTarget {
private DemonstratorTargetEmbedded embedded;
@Default
public DemonstratorTarget(DemonstratorTargetEmbedded embedded) { this.embedded = embedded; }
public DemonstratorTargetEmbedded getEmbedded() { return embedded; }
public void setEmbedded(DemonstratorTargetEmbedded embedded) { this.embedded = embedded; }
}
```
with a supposedly immutable member class:
```
public class DemonstratorTargetEmbedded {
private String version;
private Map metadata = new HashMap<>();
public DemonstratorTargetEmbedded() { }
@Default
public DemonstratorTargetEmbedded(String version, Map metadata) {
this.version = version;
this.metadata = metadata;
}
public String getVersion() { return version; }
public Map getMetadata() { return metadata; }
}
```
and use it in a mapper as `@MappingTarget`:
```
@Mapper
public interface DemonstratorMapperBroken {
void updateTarget(@MappingTarget DemonstratorTarget target, DemonstratorSource source);
}
```
The resulting mapper will not call `target.setEmbedded([newly created instance])` but tries to alter the member:
```
mappingTarget.getMetadata().clear();
Map map = demonstratorSourceEmbedded.getMetadata();
if ( map != null ) {
mappingTarget.getMetadata().putAll( map );
}
```
This of course fails in the example above, because there is no setter for `version`.

Now if I change the mapper to look like this (added a mapping method, did not change the original one):
```
@Mapper
public interface DemonstratorMapperWorking {
void updateTarget(@MappingTarget DemonstratorTarget target, DemonstratorSource source);
DemonstratorTargetEmbedded toTargetEmbedded(DemonstratorSourceEmbedded gender);
}
```
...the mapper works as expected:
```
target.setEmbedded( toTargetEmbedded( source.getEmbedded() ) );
[...]
DemonstratorTargetEmbedded demonstratorTargetEmbedded = new DemonstratorTargetEmbedded( version, metadata );
```

I am unsure if this is expected behaviour. Is there a way to circumvent this without adding a method like `toTargetEmbedded`?

I created a demonstrator:
1. `git clone -b mapstruct-constructor-mapping https://github.com/mickroll/demonstrator.git`
2. `cd demonstrator`
3. `mvn clean install`

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.