Lombok @Value/@Builder mapping fails when source/target has nested properties
This issue is somewhat involved to explain, so I took the lombok example from the mapstruct-examples repo and adapted a bit to create a repro which can be found here: https://github.com/dan-lind/mapstruct-lombok-example
What I have observed is that when annotating the target with @Value and @Builder, mapstruct is unable create a mappping when the source has properties on multiple levels, and the target has nested properties.
When building the above repro code with gradle clean build, I get these error messages
SourceTargetMapper.java:23: error: Property "testingToo" has no write accessor in NestedTarget for target name SourceTargetMapper.java:23: error: Property "testing" has no write accessor in NestedTarget for target name
The source looks like this
@Data
public class Source {
private String test;
private AnotherSource testToo;
}
@Data
public class AnotherSource {
private String test;
}
and target
@Value
@Builder
public class Target {
NestedTarget nestedTarget;
}
@Value
@Builder
public class NestedTarget {
Long testing;
Long testingToo;
}
Some observations: If I remove either of the source properties, i.e.
//private String test;
private AnotherSource testToo;
or
private String test;
//private AnotherSource testToo;
and make the appropriate adjustments to the mapper, then the problem goes away.
The problem also goes away if I move the properties of the NestedTarget down to Target, i.e.
@Value
@Builder
public class Target {
Long testing;
Long testingToo;
}}
Please let me know if you need more information.
I am fairly certain that this is linked to https://github.com/rzwitserloot/lombok/issues/1538. There is an interface that's implemented in MapStruct that it implemented in Lombok that should make all of this work. There are some known issues with the builder. Please vote on the Lombok issue
@filiphr This one is not lombok related, I attached a delomboked example that fails too. I noticed that it works if there is only one @Mapping in SourceTargetMapper. It seems like it only fails if one source is not nested and the other is and both targets are nested. Every other combination I tried seems to be okay.
Thanks for looking into this @Rawi01. Looking at the zip you shared it seems like the problem is the same as in #2322. I'll reopen this and have a better look soon
OK I had a better look at this and I know why this is happening. It is due to the nested target mapping.
When we have
@Mapping(target = "nestedTarget.testing", source = "test")
@Mapping(target = "nestedTarget.testingToo", source = "testToo.test")
Target toTarget(Source source);
MapStruct will create 2 update methods for mapping the NestedTarget as there are 2 different sources for that. However, NestedTarget has no setters and thus we can't update it.
We now have good support for constructor mapping, perhaps We can reuse that instead and create one method where we will get the target data and not create update methods.
I'll need to think a bit more about how to fix this. It will take some time.
@dan-lind if you can I will advise you to try for a solution that does not use nested target mapping. e.g.
@Mapping(target = "nestedTarget", source = "source")
Target toTarget(Source source);
@Mapping(target = "testing", source = "test")
@Mapping(target = "testingToo", source = "testToo.test")
NestedTarget toNestedTarget(Source source);
Having now written this example, perhaps we can generate such a method (a potential fix for this)
Thanks for looking in to this @filiphr. For now, I got it working by just using regular get/set methods (@ Data annotation). I'll probably leave like that for now and wait for a potential fix. I'll be happy to help you test when you think you have a solution!
Thanks for looking in to this @filiphr. For now, I got it working by just using regular get/set methods (@ Data annotation). I'll probably leave like that for now and wait for a potential fix. I'll be happy to help you test when you think you have a solution!
Your Lombok jar package (or Maven dependency) should be placed in front of mapstruct, so that Lombok can be executed first at compile time
Do you have some updates? When it will be possible to use this feature?
@fragaLY which feature are you talking about? This is a corner case bug which we will look into fixing for 1.5 (as shown with the linked milestone). There really is no need to ask for updates when all the available information is already in the issue
Thanks for looking in to this @filiphr. For now, I got it working by just using regular get/set methods (@ Data annotation). I'll probably leave like that for now and wait for a potential fix. I'll be happy to help you test when you think you have a solution!
Your Lombok jar package (or Maven dependency) should be placed in front of mapstruct, so that Lombok can be executed first at compile time
I tried your method,it worked.
Still reproduces :(
Still reproduces :(
@npwork, your comment does not bring a lot to the current discussion. Yes the original reported issue still reproduces, the fact that this issue is not closed should make it clear that the problem has not been solved.
Hey, @filiphr , I found a variation of the same issue,
https://bitbucket.org/rdllopes/mapstructnestederror/
If the target class contains a builder inner class, mapstruct will target that inner class instead of the outer class.
Thanks @random-goodguy, we'll have a look at your project in more details. What you are explaining is right though. Have a look at our Using builders documentation, when MapStruct finds builders in your class then it will use the builder to create the object and not the target class. You need to disable builders if you don't want to use them.
You can disable them by using
@BeanMapping(builder = @Builder(disableBuilder = true))
or
@Mapper(builder = @Builder(disableBuilder = true))
@filiphr Thank you. But I didn't expect that the inner builder would interfere on MapStruct behavior by default. Well, anyway your solution works. Thanks again.
This might also be tackled with #2941