mapstruct / mapstruct/mapstruct
Config param to generate getters for injected mappers
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 7.7k
- Forks
- 1.1k
- PR merge metrics
- No merged PRs in 30d
Description
Use case
Here's my slightly-modified version of the example in that question, which better reflects my use case:
@Mapper(componentModel = "spring",
injectionStrategy = InjectionStrategy.CONSTRUCTOR,
uses = { MapperB.class }
)
public interface MapperA {
MapperB getMapperB();
default List<Model1> onlyMapFirstItemInList(List<Model2> source) {
return Optional.ofNullable(source)
.filter(CollectionUtils::isNotEmpty)
.map(list -> list.get(0))
.map(model2 -> getMapperB().model2ToModel1Mapper(model2))
.map(Collections::singletonList)
.orElse(null);
}
Where MapperB contains a (Mapstruct-generated) method for mapping a single Model2 to a single Model1 that I want to use in MapperA. (And which in my specific use-case, I really want to keep in a separate Mapper class because it needs to be re-used in several other mappers besides MapperA, and in those other mappers I DO want to map every element in the source List into the target List, not just the first one.)
Currently, the generated MapperAImpl class from the example will have:
private final MapperB mapperB;
@Autowired
public MapperAImpl(MapperB mapperB) {
this.mapperB = mapperB;
}
But... the getMapperB() method I specified in my MapperA interface is not implemented, so I have no way of using the MapperB instance from the generated MapperAImpl in my default method implemented in the MapperA interface.
Basically, the idea is it should be possible to configure MapStruct (or in my mind, this should even be the default behavior!) so that if the interface (or abstract class) declares but doesn't implement a getter method for one of the injected mappers, the generated implementation of that mapper includes an implementation of the getter to return the injected mapper.
Generated Code
Basically, I would just want the generated MapperAImpl class snippet above to be:
private final MapperB mapperB;
@Autowired
public MapperAImpl(MapperB mapperB) {
this.mapperB = mapperB;
}
@Override
public MapperB getMapperB() {
return mapperB;
}
Possible workarounds
There's an answer posted to the StackOverflow question I mentioned in the Use case (https://stackoverflow.com/questions/75296353/use-a-custom-mapper-inside-another-custom-mapper-with-mapstruct-in-default-meth) which works, but I find it a bit hacky, because:
-
it results in both the abstract class AND the Mapstruct-generated implementation of that abstract class having private properties with the same injected mapper in them;
-
initialization of unit tests around a mapper implemented this way are a bit clunky because the same MapperB instance has to be injected both into the constructor AND into the setter:
class MapperATest {
private static final MapperB mapperB = Mappers.getMapper(MapperB.class);
private static final MapperA sut = new MapperAImpl(mapperB);
@BeforeAll
static void beforeAll() {
sut.setMapperB(mapperB);
}
I could get around this by making it a SpringBootTest, but then it's really an integration test, not a unit test, and it's incredibly slow to run since it requires the Spring context to be initialized.
MapStruct Version
1.5.5.Final
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the MapperA/MapperB example and the generated MapperAImpl shown in the issue, focusing on how constructor-injected mappers are exposed to default methods. Determine the configuration behavior for an unimplemented getter; done means the generated implementation returns the injected MapperB while preserving the shown injection setup.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100