Skip to content

Commit

Permalink
#1057 documentation for Controlling forged name based bean mapping fr…
Browse files Browse the repository at this point in the history
…om root level with @mapping
  • Loading branch information
sjaakd authored and filiphr committed Feb 20, 2017
1 parent 51e5976 commit 582bbe4
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
[[controlling-nested-bean-mappings]]
=== Controlling nested bean mappings

As explained above, MapStruct will generate a method based on the name of the source and target property. Unfortunately, in many occasions these names do not match.

The ‘.’ notation in an `@Mapping` source or target type can be used to control how properties should be mapped when names do not match.
There is an elaborate https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-nested-bean-mappings[example] in our examples repository to explain how this problem can be overcome.

In the simplest scenario there’s a property on a nested level that needs to be corrected.
Take for instance a property `fish` which has an identical name in `FishTankDto` and `FishTank`.
For this property MapStruct automatically generates a mapping: `FishDto fishToFishDto(Fish fish)`.
MapStruct cannot possibly be aware of the deviating properties `kind` and `type`.
Therefore this can be addressed in a mapping rule: `@Mapping(target="fish.kind", source="fish.type")`.
This tells MapStruct to deviate from looking for a name `kind` at this level and map it to `type`.

.Mapper controlling nested beans mappings I
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FishTankMapper {
@Mappings({
@Mapping(target = "fish.kind", source = "fish.type"),
@Mapping(target = "fish.name", ignore = true),
@Mapping(target = "ornament", source = "interior.ornament"),
@Mapping(target = "material.materialType", source = "material"),
@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
})
FishTankDto map( FishTank source );
}
----
====

The same constructs can be used to ignore certain properties at a nesting level, as is demonstrated in the second `@Mapping` rule.

MapStruct can even be used to “cherry pick” properties when source and target do not share the same nesting level (the same number of properties).
This can be done in the source – and in the target type. This is demonstrated in the next 2 rules: `@Mapping(target="ornament", source="interior.ornament")` and `@Mapping(target="material.materialType", source="material")`.

The latter can even be done when mappings first share a common base.
For example: all properties that share the same name of `Quality` are mapped to `QualityDto`.
Likewise, all properties of `Report` are mapped to `ReportDto`, with one exception: `organisation` in `OrganisationDto` is left empty (since there is no organization at the source level).
Only the `name` is populated with the `organisationName` from `Report`.
This is demonstrated in `@Mapping(target="quality.report.organisation.name", source="quality.report.organisationName")`

Coming back to the original example: what if `kind` and `type` would be beans themselves?
In that case MapStruct would again generate a method continuing to map.
Such is demonstrated in the next example:


.Mapper controlling nested beans mappings II
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FishTankMapperWithDocument {
@Mappings({
@Mapping(target = "fish.kind", source = "fish.type"),
@Mapping(target = "fish.name", expression = "java(\"Jaws\")"),
@Mapping(target = "plant", ignore = true ),
@Mapping(target = "ornament", ignore = true ),
@Mapping(target = "material", ignore = true),
@Mapping(target = "quality.document", source = "quality.report"),
@Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
})
FishTankWithNestedDocumentDto map( FishTank source );
}
----
====

Note what happens in `@Mapping(target="quality.document", source="quality.report")`.
`DocumentDto` does not exist as such on the target side. It is mapped from `Report`.
MapStruct continues to generate mapping code here. That mapping it self can be guided towards another name.
This even works for constants and expression. Which is shown in the final example: `@Mapping(target="quality.document.organisation.name", constant="NoIdeaInc")`.

MapStruct will perform a null check on each nested property in the source.

[TIP]
====
Instead of configuring everything via the parent method we encourage users to explicitly write their own nested methods.
This puts the configuration of the nested mapping into one place (method) where it can be reused from several methods in the upper level,
instead of re-configuring the same things on all of those upper methods.
====
36 changes: 14 additions & 22 deletions documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -466,36 +466,26 @@ Specifying the parameter in which the property resides is mandatory when using t
Mapping methods with several source parameters will return `null` in case all the source parameters are `null`. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated.
====

[[nested-mappings]]
=== Nested mappings
MapStruct also offers the possibility to directly refer to a source parameter.

MapStruct will handle nested mappings (in source), by means of the `.` notation:

.Mapping method with several source parameters
.Mapping method directly referring to a source parameter
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mappings({
@Mapping(target = "chartName", source = "chart.name"),
@Mapping(target = "title", source = "song.title"),
@Mapping(target = "artistName", source = "song.artist.name"),
@Mapping(target = "recordedAt", source = "song.artist.label.studio.name"),
@Mapping(target = "city", source = "song.artist.label.studio.city"),
@Mapping(target = "position", source = "position")
})
ChartEntry map(Chart chart, Song song, Integer position);
@Mapper
public interface AddressMapper {
@Mappings({
@Mapping(source = "person.description", target = "description"),
@Mapping(source = "hn", target = "houseNumber")
})
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}
----
====

Note: the parameter name (`chart`, `song`, `position`) is required, since there are several source parameters in the mapping. If there's only one source parameter, the parameter name can be ommited.

MapStruct will perform a null check on each nested property in the source.

[TIP]
====
Also non java bean source parameters (like the `java.lang.Integer`) can be mapped in this fashion.
====
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`.

[[updating-bean-instances]]
=== Updating existing bean instances
Expand Down Expand Up @@ -827,6 +817,8 @@ When generating the implementation of a mapping method, MapStruct will apply the
* If no such method was found MapStruct will try to generate an internal method that will do the mapping between the source and target attributes
* If MapStruct could not create a name based mapping method an error will be raised at build time, indicating the non-mappable attribute and its path.

include::controlling-nested-bean-mappings.asciidoc[]

[[invoking-other-mappers]]
=== Invoking other mappers

Expand Down

0 comments on commit 582bbe4

Please sign in to comment.