mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
parent
61f941aa80
commit
0d23f09e37
@ -233,34 +233,36 @@ public interface AddressMapper {
|
||||
In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameter `hn`, a non bean type (in this case `java.lang.Integer`) is mapped to `houseNumber`.
|
||||
|
||||
[[mapping-nested-bean-properties-to-current-target]]
|
||||
=== Mapping nested bean properties to current target
|
||||
=== Mapping nested bean properties to current target
|
||||
|
||||
If you don't want explicitly name all properties from nested source bean, you can use `.` as target.
|
||||
If you don't want explicitly name all properties from nested source bean, you can use `.` as target.
|
||||
This will tell MapStruct to map every property from source bean to target object. The following shows an example:
|
||||
|
||||
.Nested multi mapping method
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
.use of "target this" annotation "."
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
@Mapper
|
||||
public interface CustomerMapper {
|
||||
|
||||
@Mapping(source = "record", target = ".")
|
||||
@Mapping( target = "name", source = "record.name" )
|
||||
@Mapping( target = ".", source = "record" )
|
||||
@Mapping( target = ".", source = "account" )
|
||||
Customer customerDtoToCustomer(CustomerDto customerDto);
|
||||
}
|
||||
----
|
||||
====
|
||||
----
|
||||
====
|
||||
|
||||
The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
|
||||
The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
|
||||
The same goes for `Customer.account`.
|
||||
|
||||
When there are conflicts, these can be resolved by explicitely defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict.
|
||||
|
||||
|
||||
This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`).
|
||||
|
||||
Multi mapping notation can be very useful when mapping hierarchical objects to flat objects and opposite way.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you need to send your domain object using JSON, one way to do this is to convert it to single object, where every parent class is represented as separate field in JSON.
|
||||
And MapStruct allows you easily convert between these 2 types.
|
||||
====
|
||||
|
||||
[[updating-bean-instances]]
|
||||
=== Updating existing bean instances
|
||||
|
@ -657,15 +657,30 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
* <p>
|
||||
* When a target property matches its name with the (nested) source property, it is added to the list if and
|
||||
* only if it is an unprocessed target property.
|
||||
*
|
||||
* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
|
||||
*/
|
||||
private void applyTargetThisMapping() {
|
||||
if ( mappingReferences.getTargetThis() != null ) {
|
||||
List<SourceReference> sourceRefs = mappingReferences.getTargetThis()
|
||||
Set<String> handledTargetProperties = new HashSet<>();
|
||||
for ( MappingReference targetThis : mappingReferences.getTargetThisReferences() ) {
|
||||
|
||||
// handle all prior unprocessed target properties, but let duplicates fall through
|
||||
List<SourceReference> sourceRefs = targetThis
|
||||
.getSourceReference()
|
||||
.push( ctx.getTypeFactory(), ctx.getMessager(), method ).stream()
|
||||
.filter( sr -> unprocessedTargetProperties.containsKey( sr.getDeepestPropertyName() ) )
|
||||
.push( ctx.getTypeFactory(), ctx.getMessager(), method )
|
||||
.stream()
|
||||
.filter( sr -> unprocessedTargetProperties.containsKey( sr.getDeepestPropertyName() )
|
||||
|| handledTargetProperties.contains( sr.getDeepestPropertyName() ) )
|
||||
.collect( Collectors.toList() );
|
||||
|
||||
// apply name based mapping
|
||||
applyPropertyNameBasedMapping( sourceRefs );
|
||||
|
||||
// add handled target properties
|
||||
handledTargetProperties.addAll( sourceRefs.stream()
|
||||
.map( SourceReference::getDeepestPropertyName )
|
||||
.collect(
|
||||
Collectors.toList() ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,10 @@
|
||||
*/
|
||||
package org.mapstruct.ap.internal.model.beanmapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
||||
@ -19,7 +21,7 @@ public class MappingReferences {
|
||||
private static final MappingReferences EMPTY = new MappingReferences( Collections.emptySet(), false );
|
||||
|
||||
private final Set<MappingReference> mappingReferences;
|
||||
private final MappingReference targetThis;
|
||||
private final List<MappingReference> targetThisReferences;
|
||||
private final boolean restrictToDefinedMappings;
|
||||
private final boolean forForgedMethods;
|
||||
|
||||
@ -31,7 +33,7 @@ public class MappingReferences {
|
||||
TypeFactory typeFactory) {
|
||||
|
||||
Set<MappingReference> references = new LinkedHashSet<>();
|
||||
MappingReference targetThisReference = null;
|
||||
List<MappingReference> targetThisReferences = new ArrayList<>( );
|
||||
|
||||
for ( Mapping mapping : sourceMethod.getMappingOptions().getMappings() ) {
|
||||
|
||||
@ -53,29 +55,29 @@ public class MappingReferences {
|
||||
MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference );
|
||||
if ( isValidWhenInversed( mappingReference ) ) {
|
||||
if ( ".".equals( mapping.getTargetName() ) ) {
|
||||
targetThisReference = mappingReference;
|
||||
targetThisReferences.add( mappingReference );
|
||||
}
|
||||
else {
|
||||
references.add( mappingReference );
|
||||
}
|
||||
}
|
||||
}
|
||||
return new MappingReferences( references, targetThisReference, false );
|
||||
return new MappingReferences( references, targetThisReferences, false );
|
||||
}
|
||||
|
||||
public MappingReferences(Set<MappingReference> mappingReferences, MappingReference targetThis,
|
||||
public MappingReferences(Set<MappingReference> mappingReferences, List<MappingReference> targetThisReferences,
|
||||
boolean restrictToDefinedMappings) {
|
||||
this.mappingReferences = mappingReferences;
|
||||
this.restrictToDefinedMappings = restrictToDefinedMappings;
|
||||
this.forForgedMethods = restrictToDefinedMappings;
|
||||
this.targetThis = targetThis;
|
||||
this.targetThisReferences = targetThisReferences;
|
||||
}
|
||||
|
||||
public MappingReferences(Set<MappingReference> mappingReferences, boolean restrictToDefinedMappings) {
|
||||
this.mappingReferences = mappingReferences;
|
||||
this.restrictToDefinedMappings = restrictToDefinedMappings;
|
||||
this.forForgedMethods = restrictToDefinedMappings;
|
||||
this.targetThis = null;
|
||||
this.targetThisReferences = Collections.emptyList();
|
||||
}
|
||||
|
||||
public MappingReferences(Set<MappingReference> mappingReferences, boolean restrictToDefinedMappings,
|
||||
@ -83,7 +85,7 @@ public class MappingReferences {
|
||||
this.mappingReferences = mappingReferences;
|
||||
this.restrictToDefinedMappings = restrictToDefinedMappings;
|
||||
this.forForgedMethods = forForgedMethods;
|
||||
this.targetThis = null;
|
||||
this.targetThisReferences = Collections.emptyList();
|
||||
}
|
||||
|
||||
public Set<MappingReference> getMappingReferences() {
|
||||
@ -127,8 +129,8 @@ public class MappingReferences {
|
||||
return false;
|
||||
}
|
||||
|
||||
public MappingReference getTargetThis() {
|
||||
return targetThis;
|
||||
public List<MappingReference> getTargetThisReferences() {
|
||||
return targetThisReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -482,6 +482,10 @@ public class Mapping {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( ".".equals( this.targetName ) ) {
|
||||
// target this will never be equal to any other target this or any other.
|
||||
return false;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConfictsResolvedNestedMapper {
|
||||
|
||||
ConfictsResolvedNestedMapper INSTANCE = Mappers.getMapper( ConfictsResolvedNestedMapper.class );
|
||||
|
||||
@Mapping( target = "id", source = "customer.item.id" )
|
||||
@Mapping( target = ".", source = "customer.item" )
|
||||
@Mapping( target = "status", source = "item.status" )
|
||||
@Mapping( target = ".", source = "item" )
|
||||
OrderItem map(OrderDTO order);
|
||||
|
||||
}
|
@ -5,6 +5,9 @@
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
/**
|
||||
* @author Dainius Figoras
|
||||
*/
|
||||
public class CustomerDTO {
|
||||
private String name;
|
||||
private int level;
|
||||
|
@ -5,6 +5,9 @@
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
/**
|
||||
* @author Dainius Figoras
|
||||
*/
|
||||
public class CustomerItem {
|
||||
private String id;
|
||||
private int status;
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@Mapper
|
||||
public interface ErroneousNestedMapper {
|
||||
|
||||
ErroneousNestedMapper INSTANCE = Mappers.getMapper( ErroneousNestedMapper.class );
|
||||
|
||||
@Mapping( target = ".", source = "customer.item" )
|
||||
@Mapping( target = ".", source = "item" )
|
||||
OrderItem map(OrderDTO order);
|
||||
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
import org.mapstruct.InheritInverseConfiguration;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@Mapper
|
||||
public interface FlatteningMapper {
|
||||
|
||||
FlatteningMapper INSTANCE = Mappers.getMapper( FlatteningMapper.class );
|
||||
|
||||
@Mapping(target = ".", source = "name")
|
||||
@Mapping(target = ".", source = "account")
|
||||
Customer flatten(CustomerDTO customer);
|
||||
|
||||
@InheritInverseConfiguration
|
||||
CustomerDTO expand(Customer customer);
|
||||
|
||||
class Customer {
|
||||
|
||||
private String name;
|
||||
private String id;
|
||||
private String details;
|
||||
private String number;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Customer setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Customer setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public Customer setDetails(String details) {
|
||||
this.details = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public Customer setNumber(String number) {
|
||||
this.number = number;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomerDTO {
|
||||
|
||||
private NameDTO name;
|
||||
private AccountDTO account;
|
||||
|
||||
public NameDTO getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public CustomerDTO setName(NameDTO name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountDTO getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public CustomerDTO setAccount(AccountDTO account) {
|
||||
this.account = account;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class NameDTO {
|
||||
private String name;
|
||||
private String id;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public NameDTO setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public NameDTO setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class AccountDTO {
|
||||
private String number;
|
||||
private String details;
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public AccountDTO setNumber(String number) {
|
||||
this.number = number;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public AccountDTO setDetails(String details) {
|
||||
this.details = details;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,9 @@
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
/**
|
||||
* @author Dainius Figoras
|
||||
*/
|
||||
public class ItemDTO {
|
||||
private String id;
|
||||
private int status;
|
||||
|
@ -9,6 +9,9 @@ import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@Mapper
|
||||
public interface NestedMapper {
|
||||
|
||||
|
@ -5,6 +5,9 @@
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
/**
|
||||
* @author Dainius Figoras
|
||||
*/
|
||||
public class OrderDTO {
|
||||
|
||||
private ItemDTO item;
|
||||
|
@ -5,6 +5,9 @@
|
||||
*/
|
||||
package org.mapstruct.ap.test.targetthis;
|
||||
|
||||
/**
|
||||
* @author Dainius Figoras
|
||||
*/
|
||||
public class OrderItem {
|
||||
private String id;
|
||||
private int status;
|
||||
|
@ -9,6 +9,9 @@ import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@Mapper
|
||||
public interface SimpleMapper {
|
||||
SimpleMapper INSTANCE = Mappers.getMapper( SimpleMapper.class );
|
||||
|
@ -9,6 +9,9 @@ import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@Mapper
|
||||
public interface SimpleMapperWithIgnore {
|
||||
SimpleMapperWithIgnore INSTANCE = Mappers.getMapper( SimpleMapperWithIgnore.class );
|
||||
|
@ -8,10 +8,16 @@ package org.mapstruct.ap.test.targetthis;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
|
||||
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dainius Figoras
|
||||
*/
|
||||
@RunWith(AnnotationProcessorTestRunner.class)
|
||||
@WithClasses( {
|
||||
OrderDTO.class,
|
||||
@ -82,4 +88,72 @@ public class TargetThisMappingTest {
|
||||
assertThat( c.getId() ).isNull();
|
||||
assertThat( c.getStatus() ).isEqualTo( ce.getItem().getStatus() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses( ErroneousNestedMapper.class )
|
||||
@ExpectedCompilationOutcome(
|
||||
value = CompilationResult.FAILED,
|
||||
diagnostics = {
|
||||
@Diagnostic(type = ErroneousNestedMapper.class,
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
line = 22,
|
||||
messageRegExp = "^Several possible source properties for target property \"id\"\\.$"),
|
||||
@Diagnostic(type = ErroneousNestedMapper.class,
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
line = 22,
|
||||
messageRegExp = "^Several possible source properties for target property \"status\"\\.$")
|
||||
}
|
||||
)
|
||||
public void testNestedDuplicates() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses( ConfictsResolvedNestedMapper.class )
|
||||
public void testWithConflictsResolved() {
|
||||
|
||||
OrderDTO orderDTO = new OrderDTO();
|
||||
orderDTO.setItem( new ItemDTO() );
|
||||
orderDTO.getItem().setId( "item1" );
|
||||
orderDTO.getItem().setStatus( 1 );
|
||||
orderDTO.setCustomer( new CustomerDTO() );
|
||||
orderDTO.getCustomer().setName( "customer name" );
|
||||
orderDTO.getCustomer().setItem( new ItemDTO() );
|
||||
orderDTO.getCustomer().getItem().setId( "item2" );
|
||||
orderDTO.getCustomer().getItem().setStatus( 2 );
|
||||
|
||||
OrderItem c = ConfictsResolvedNestedMapper.INSTANCE.map( orderDTO );
|
||||
|
||||
assertThat( c ).isNotNull();
|
||||
assertThat( c.getStatus() ).isEqualTo( orderDTO.getItem().getStatus() );
|
||||
assertThat( c.getId() ).isEqualTo( orderDTO.getCustomer().getItem().getId() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses( FlatteningMapper.class )
|
||||
public void testFlattening() {
|
||||
|
||||
FlatteningMapper.CustomerDTO customerDTO = new FlatteningMapper.CustomerDTO();
|
||||
customerDTO.setName( new FlatteningMapper.NameDTO() );
|
||||
customerDTO.getName().setName( "john doe" );
|
||||
customerDTO.getName().setId( "1" );
|
||||
customerDTO.setAccount( new FlatteningMapper.AccountDTO() );
|
||||
customerDTO.getAccount().setDetails( "nice guys" );
|
||||
customerDTO.getAccount().setNumber( "11223344" );
|
||||
|
||||
FlatteningMapper.Customer customer = FlatteningMapper.INSTANCE.flatten( customerDTO );
|
||||
|
||||
assertThat( customer ).isNotNull();
|
||||
assertThat( customer.getName() ).isEqualTo( "john doe" );
|
||||
assertThat( customer.getId() ).isEqualTo( "1" );
|
||||
assertThat( customer.getDetails() ).isEqualTo( "nice guys" );
|
||||
assertThat( customer.getNumber() ).isEqualTo( "11223344" );
|
||||
|
||||
FlatteningMapper.CustomerDTO customerDTO2 = FlatteningMapper.INSTANCE.expand( customer );
|
||||
|
||||
assertThat( customerDTO2 ).isNotNull();
|
||||
assertThat( customerDTO2.getName().getName() ).isEqualTo( "john doe" );
|
||||
assertThat( customerDTO2.getName().getId() ).isEqualTo( "1" );
|
||||
assertThat( customerDTO2.getAccount().getDetails() ).isEqualTo( "nice guys" );
|
||||
assertThat( customerDTO2.getAccount().getNumber() ).isEqualTo( "11223344" );
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user