i have an issue with my mapping definition. I hope you can give me a hint how to fix it.
My initial situation is, that I have classes with nested classes. Most of the attributes of the Input-nested class could be directly mapped to the target-nested class. But one attribute of the input-nested class must be mapped to the taget class directly.
This is my code:
public class Input {
public String aa;
public NestedClass nestedMember;
public static class NestedClass {
public String ab;
public String ac;
public String ad;
public String ae;
}
}
public class Target {
public NestedClass nestedMember;
public static class NestedClass {
public String ba;
public String bb;
public String bc;
public String bd;
}
}
#Mapping(target = "nestedMember.ba", source = "aa")
#Mapping(target = "nestedMember", source = "nestedMember")
Target map(Input input);
#Mapping(target = "bb", source = "ab")
#Mapping(target = "bc", source = "ac")
#Mapping(target = "bd", source = "ad")
#Mapping(target = "ba", ignore = true)
NestedClass mappingNested(test.Input.NestedClass nestedClass);
My assumption was, that mapstruct will use the mappingNested method for mapping the nested class and then a direct attribute mapping for aa to nestedMember.ba.
But the generated class looks like this:
public class MyMapperImpl implements MyMapper {
#Override
public Target map(Input input) {
if ( input == null ) {
return null;
}
Target target = new Target();
if ( input.nestedMember != null ) {
if ( target.nestedMember == null ) {
target.nestedMember = new NestedClass();
}
nestedClassToNestedClass( input.nestedMember, target.nestedMember );
}
if ( target.nestedMember == null ) {
target.nestedMember = new NestedClass();
}
inputToNestedClass( input, target.nestedMember );
return target;
}
#Override
public NestedClass mappingNested(test.Input.NestedClass nestedClass) {
if ( nestedClass == null ) {
return null;
}
NestedClass nestedClass1 = new NestedClass();
nestedClass1.bb = nestedClass.ab;
nestedClass1.bc = nestedClass.ac;
nestedClass1.bd = nestedClass.ad;
return nestedClass1;
}
protected void nestedClassToNestedClass(test.Input.NestedClass nestedClass, NestedClass mappingTarget) {
if ( nestedClass == null ) {
return;
}
}
protected void inputToNestedClass(Input input, NestedClass mappingTarget) {
if ( input == null ) {
return;
}
mappingTarget.ba = input.aa;
}
}
So mapstruct gernerates the mappingNested method correctly, but doesn't use it to map the nestedClasses. Mapstuct generates a method (nestedClassToNestedClass) without any mappings. If I remove the explicit aa mapping, everithing works fine. But then I have the problem how I should mapp the aa mapping? I don't what to map every attribute of the nestedClass in the map-method.
Does anybody has an Idea?
Best regards.
I have found a solution:
#Mapping(target = "nestedMember", source = ".")
Target map(Input input);
#Mapping(target = "bb", source = "nestedClass.ab")
#Mapping(target = "bc", source = "nestedClass.ac")
#Mapping(target = "bd", source = "nestedClass.ad")
#Mapping(target = "ba", ignore = true)
NestedClass mappingNested(test.Input.NestedClass nestedClass);
default NestedClass mappingNested(Input input) {
NestedClass nestedClass = mappingNested(input.nestedMember);
nestedClass.ba = input.aa;
return nestedClass;
}
Related
Sample Objects
public class Source {
String fromPersonFirstName;
String fromPersonLastName;
String toPersonFirstName;
String toPersonLastName;
String message;
}
public class Target {
Person from;
Person to;
String message;
}
public class Person {
String firstName;
String lastName;
}
#Mapper(componentModel = "spring")
public interface PersonMapper {
Person toPerson(String firstName, String lastName);
}
The Question
Now, what would be a clean way of employing as much as mapstruct as possible? Without using expressions?
Ideally I'd tell mapstruct to do the following
#Mapper(componentModel = "spring", uses = PersonMapper.class)
public interface ExampleMapper {
#Mapping(target = "from", source = "fromPersonFirstName, fromPersonLastName")
#Mapping(target = "to", source = "toPersonFirstName, toPersonLastName")
Target toTarget(Source s);
}
Alternatively I could annotate PersonMapper and toPerson, then for the toTarget method qualifiedByName = "TheClassNameValue, TheMethodNameValue". But I still don't know how to tell the method which source fields to use.
What would be another option? I could use a local method toPerson, qualifiedByName. But then I'd have to import the other mapper in some way. Maybe change the ExampleMapper to abstract and autowire the other mapper. But I still can't specify multiple fields.
I know that expression is probably meant to fill in this gap. But ... I would rather never use it if possible. Unless the alternative is considerably more complicated.
Solution 1, Mapping method selection based on qualifiers
#Mapper
public interface ExampleMapper {
#Mapping(target = "from", source = "source", qualifiedByName = "fromPersonMapping")
#Mapping(target = "to", source = "source", qualifiedByName = "toPersonMapping")
Target toTarget(Source source);
#Named("fromPersonMapping")
#Mapping(target = "firstName", source = "fromPersonFirstName")
#Mapping(target = "lastName", source = "fromPersonLastName")
Person fromPersonMapping(Source source);
#Named("toPersonMapping")
#Mapping(target = "firstName", source = "toPersonFirstName")
#Mapping(target = "lastName", source = "toPersonLastName")
Person toPersonMapping(Source source);
}
Generated code:
public class ExampleMapperImpl implements ExampleMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.from = fromPersonMapping( source );
target.to = toPersonMapping( source );
target.message = source.message;
return target;
}
#Override
public Person fromPersonMapping(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.fromPersonFirstName;
person.lastName = source.fromPersonLastName;
return person;
}
#Override
public Person toPersonMapping(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.toPersonFirstName;
person.lastName = source.toPersonLastName;
return person;
}
}
Alternative with custom methods:
#Mapper
public interface ExampleMapper {
#Mapping(target = "from", source = "source", qualifiedByName = "fromFieldsMapping")
#Mapping(target = "to", source = "source", qualifiedByName = "toFieldsMapping")
Target toTarget(Source source);
Person toPerson(String firstName, String lastName);
#Named("fromFieldsMapping")
default Person fromFieldsMapping(Source source) {
return toPerson(source.fromPersonFirstName, source.fromPersonLastName);
}
#Named("toFieldsMapping")
default Person toFieldsMapping(Source source) {
return toPerson(source.toPersonFirstName, source.toPersonLastName);
}
}
Generated code:
public class ExampleMapperImpl implements ExampleMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.from = fromFieldsMapping( source );
target.to = toFieldsMapping( source );
target.message = source.message;
return target;
}
#Override
public Person toPerson(String firstName, String lastName) {
if ( firstName == null && lastName == null ) {
return null;
}
Person person = new Person();
if ( firstName != null ) {
person.firstName = firstName;
}
if ( lastName != null ) {
person.lastName = lastName;
}
return person;
}
}
With separate mapper:
#Mapper(uses = PersonMapper.class)
public interface ExampleMapper1 {
#Mapping(target = "from", source = "source", qualifiedBy = FromAnnotation.class)
#Mapping(target = "to", source = "source", qualifiedBy = ToAnnotation.class)
Target toTarget(Source source);
}
#Mapper
public interface PersonMapper {
Person toPerson(String firstName, String lastName);
#FromAnnotation
default Person fromFieldsMapping(Source source) {
return toPerson(source.fromPersonFirstName, source.fromPersonLastName);
}
#ToAnnotation
default Person toFieldsMapping(Source source) {
return toPerson(source.toPersonFirstName, source.toPersonLastName);
}
}
#Qualifier
#java.lang.annotation.Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface ToAnnotation {
}
#Qualifier
#java.lang.annotation.Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface FromAnnotation {
}
Solution 2, Expression
#Mapper
public interface ExampleMapper {
#Mapping(target = "from", expression = "java(toPerson(source.fromPersonFirstName, source.fromPersonLastName))")
#Mapping(target = "to", expression = "java(toPerson(source.toPersonFirstName, source.toPersonLastName))")
Target toTarget(Source source);
Person toPerson(String firstName, String lastName);
}
Generated code:
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.message = source.message;
target.from = toPerson(source.fromPersonFirstName, source.fromPersonLastName);
target.to = toPerson(source.toPersonFirstName, source.toPersonLastName);
return target;
}
#Override
public Person toPerson(String firstName, String lastName) {
if ( firstName == null && lastName == null ) {
return null;
}
Person person = new Person();
if ( firstName != null ) {
person.firstName = firstName;
}
if ( lastName != null ) {
person.lastName = lastName;
}
return person;
}
Solution 3, regular mapping
#Mapper
public interface ExampleMapper {
#Mapping(target = "from.firstName", source = "source.fromPersonFirstName")
#Mapping(target = "from.lastName", source = "source.fromPersonLastName")
#Mapping(target = "to.firstName", source = "source.toPersonFirstName")
#Mapping(target = "to.lastName", source = "source.toPersonLastName")
Target toTarget(Source source);
}
Generated code:
public class ExampleMapperImpl implements ExampleMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.from = sourceToPerson( source );
target.to = sourceToPerson1( source );
target.message = source.message;
return target;
}
protected Person sourceToPerson(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.fromPersonFirstName;
person.lastName = source.fromPersonLastName;
return person;
}
protected Person sourceToPerson1(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.toPersonFirstName;
person.lastName = source.toPersonLastName;
return person;
}
}
I have these objects:
Class of domain
public class Partecipation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToMany(mappedBy = "partecipation")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<DesignatedCompany> designatedCompanies = new HashSet<>();
...
}
My DTO
public class PartecipationDTO implements Serializable {
private Long id;
...
private Set<DesignatedCompanyDTO> designatedCompanies = new HashSet<>();
...
}
Connected DTO
public class DesignatedCompanyDTO implements Serializable {
private Long id;
...
private PartecipationDTO partecipation;
...
}
And I have this mapper:
public interface PartecipationMapper extends EntityMapper<PartecipationDTO, Partecipation> {
...
PartecipationDTO toDto(Partecipation partecipation);
}
Correctly the code goes in error, because it enters a cyclical condition and in the details here:
public class PartecipationMapperImpl implements PartecipationMapper {
...
protected DesignatedCompanyDTO designatedCompanyToDesignatedCompanyDTO(DesignatedCompany designatedCompany) {
if ( designatedCompany == null ) {
return null;
}
DesignatedCompanyDTO designatedCompanyDTO = new DesignatedCompanyDTO();
designatedCompanyDTO.setId( designatedCompany.getId() );
designatedCompanyDTO.setCompanyEopooCode( designatedCompany.getCompanyEopooCode() );
designatedCompanyDTO.setNote( designatedCompany.getNote() );
designatedCompanyDTO.setPartecipation( toDto( designatedCompany.getPartecipation() ) ); // <--- this line cause the error
return designatedCompanyDTO;
}
...
}
Is it possible to set in the mapper an exclusion for the property of child object in lists? For example, like this:
#Mapping(target = "designatedCompanies[].partecipation", ignore = true)
i have solved the problem, tks to filiphr (https://github.com/mapstruct/mapstruct/issues/933#issuecomment-265952166)
working in the Mappers of the 2 DTO, here the code:
#Mapper(componentModel = "spring", uses = {DesignatedCompanyMapper.class})
public interface PartecipationMapper extends EntityMapper<PartecipationDTO, Partecipation> {
...
#Mapping(target = "designatedCompanies", qualifiedByName="NoPartecipation")
PartecipationDTO toDto(Partecipation partecipation);
...
}
And the Mapper of Iterable Object
public interface DesignatedCompanyMapper extends EntityMapper<DesignatedCompanyDTO, DesignatedCompany> {
#Mapping(source = "partecipation.id", target = "partecipationId")
DesignatedCompanyDTO toDto(DesignatedCompany designatedCompany);
...
#Named("NoPartecipation")
#Mapping(source = "partecipation.id", target = "partecipationId")
#Mapping(target = "partecipation", ignore = true)
DesignatedCompanyDTO toDtoNoPartecipation(DesignatedCompany designatedCompany);
}
We have a POJO class with Optional getter fields. The mapstruct 1.3.0.Final generating wrong code for the Optional collection field.
We have ProgramAggregate POJO which contains Collection of Program (Collection) and it is an Optional getter type.
When we run with mapstruct 1.2.0.Final, we are seeing proper code generation.
But the same code with 1.3.0.Final generating wrong code. It is not generating the Collection mapping method for Optional Collection getter methods.
Generated code image
#Data
#NoArgsConstructor
#ToString(callSuper = true)
#FieldDefaults(makeFinal = false, level = AccessLevel.PRIVATE)
public class ProgramAggregate {
public static final long serialVersionUID = 1L;
Collection<Program> programs;
public Optional<Collection<Program>> getPrograms() {
return Optional.ofNullable(programs);
}
}
#Data
#NoArgsConstructor
#FieldDefaults(makeFinal = false, level = AccessLevel.PRIVATE)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "_type")
public class Program {
String name;
String number;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public Optional<String> getNumber() {
return Optional.ofNullable(number);
}
}
#Data
#NoArgsConstructor
#FieldDefaults(makeFinal = false, level = AccessLevel.PRIVATE)
public class ProgramResponseDto {
Collection<ProgramDto> programs;
public Optional<Collection<ProgramDto>> getPrograms() {
return Optional.ofNullable(programs);
}
}
#Data
#NoArgsConstructor
#FieldDefaults(makeFinal = false, level = AccessLevel.PRIVATE)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "_type")
public class ProgramDto {
String name;
String number;
String oldNumber;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public Optional<String> getNumber() {
return Optional.ofNullable(number);
}
public Optional<String> getOldNumber() {
return Optional.ofNullable(oldNumber);
}
}
#Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, unmappedTargetPolicy = ReportingPolicy.WARN,
collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE)
public interface IProgramMapper extends IOptionalMapper, IDefaultMapper {
ProgramResponseDto map(ProgramAggregate programAggregate);
ProgramDto map(Program sourceProgramDto);
Collection<ProgramDto> mapPrograms(Collection<Program> sourcePrograms);
}
Result:
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-10-12T13:10:32+0530",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_231 (Oracle Corporation)"
)
public class IProgramMapperImpl implements IProgramMapper {
#Override
public ProgramResponseDto map(ProgramAggregate programAggregate) {
if ( programAggregate == null ) {
return null;
}
ProgramResponseDto programResponseDto = new ProgramResponseDto();
**Collection<ProgramDto> collection = fromOptional( programAggregate.getPrograms() );**
if ( collection != null ) {
programResponseDto.setPrograms( collection );
}
return programResponseDto;
}
#Override
public ProgramDto map(Program sourceProgramDto) {
if ( sourceProgramDto == null ) {
return null;
}
ProgramDto programDto = new ProgramDto();
if ( sourceProgramDto.getName() != null ) {
programDto.setName( fromOptional( sourceProgramDto.getName() ) );
}
if ( sourceProgramDto.getNumber() != null ) {
programDto.setNumber( fromOptional( sourceProgramDto.getNumber() ) );
}
return programDto;
}
#Override
public Collection<ProgramDto> mapPrograms(Collection<Program> sourcePrograms) {
if ( sourcePrograms == null ) {
return null;
}
Collection<ProgramDto> collection = new ArrayList<ProgramDto>( sourcePrograms.size() );
for ( Program program : sourcePrograms ) {
collection.add( map( program ) );
}
return collection;
}
}
Below is the error after maven build.
Error:
[ERROR] //mapstruct_latest_example/target/generated-sources/annotations/IProgramMapperImpl.java:[21,57] incompatible types: inference variable T has incompatible bounds
[ERROR] equality constraints: java.util.Collection
[ERROR] upper bounds: java.util.Collection,java.lang.Object
Issue:
Allow generic of generics in types when matching
e.g. method signature such as
<T> T fromOptional(Optional<T> optional)
when T resolves to another generic class such as Collection
This might be resolved in mapstruct 1.4.2. version.
I'm trying to set a defaultValue for a boolean field using MapStruct, but the generated code simply ignores it.
My code:
public class CreateEventRequest {
#NotNull
#JsonProperty
private Boolean isPrivate;
#JsonProperty
private Boolean friendCanInviteFriends;
#JsonProperty
private boolean showGuestList;
public boolean isPrivate() {
return isPrivate;
}
public String getDescription() {
return description;
}
public boolean isFriendCanInviteFriends() {
return friendCanInviteFriends;
}
public boolean isShowGuestList() {
return showGuestList;
}
}
My mapper:
#Mapper(componentModel = "spring")
public interface CreateEventRequestMapper {
#Mapping(target = "showGuestList", source = "showGuestList", defaultExpression = "java(true)")
#Mapping(target = "friendCanInviteFriends", source = "friendCanInviteFriends", defaultValue = "true")
Event map(CreateEventRequest request);
}
The generated code:
public class CreateEventRequestMapperImpl implements CreateEventRequestMapper {
#Override
public Event map(CreateEventRequest request) {
if ( request == null ) {
return null;
}
Event event = new Event();
event.setShowGuestList( request.isShowGuestList() );
event.setFriendCanInviteFriends( request.isFriendCanInviteFriends() );
event.setPrivate( request.isPrivate() );
return event;
}
}
As you can see, I've tried using primitive/non-primitive type but it simply ignores the defaultValue.
Am I missing something here?
Thanks!
The problem is that the return type of your getter methods in the source object is always primitive which can't be null, you need to return Boolean.
MapStruct doesn't support direct private field access which requires reflection.
Here is my source object:
public class Record {
public final long captureTime;
public final String environnement;
public final String bundle;
public final String type;
public final String id;
public final Map<String,Object> meta;
}
Here is my destination object:
public class MappedRecord {
public final long captureTime;
public final String environnement;
public final String bundle;
public final String type;
public final String id;
public final String ip;
public final String server;
}
And my mapper looks like the following:
public interface RecordMapper {
RecordMapper MAPPER = Mappers.getMapper( RecordMapper.class );
#Mappings({
#Mapping(source = "captureTime", target = "captureTime"),
#Mapping(source = "environnement", target = "environnement"),
#Mapping(source = "bundle", target = "bundle"),
#Mapping(source = "type", target = "type"),
#Mapping(source = "id", target = "id"),
#Mapping(expression = "java((String) r.meta.get(\"ip\"))", target = "ip"),
#Mapping(expression = "java((String) r.meta.get(\"server\"))", target = "server"),
})
MappedRecord toMappedRecord(Record r);
}
For now it works well but I would like to know if there is a more "elegant" way to set Map entries as source. Because with this I did not manage to add transformation functions using the "qualifiedByName" property, it looks like it can only work when a "source" is specified. Did I misunderstand something ?
I tried the following approaches without satisfying results :
Overwrite getter for specific fields in my Record class
Add a transformation function with the "qualifiedByName" property. Something like:
#Named("metaGetter")
default String dataGetter (String property) {
return (String) r.meta.get(property);
}
But obviously this does not compile as the property name is not valid as a proper source.
Thanks for your time.
Write your own qualifier:
public class MappingUtil {
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface Ip {
}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public static #interface Server {
}
#Ip
public String ip(Map<String, Object> in) {
return (String)in.get("ip");
}
#Server
public String server(Map<String, Object> in) {
return (String)in.get("server");
}
}
and then just add to the mapping:
#Mapper( uses = MappingUtil.class )
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
#Mappings( {
#Mapping(source = "map", target = "ip", qualifiedBy = MappingUtil.Ip.class ),
#Mapping(source = "map", target = "server", qualifiedBy = MappingUtil.Server.class ),
} )
Target toTarget( Source s );
}