diff --git a/documentation/src/main/asciidoc/controlling-nested-bean-mappings.asciidoc b/documentation/src/main/asciidoc/controlling-nested-bean-mappings.asciidoc new file mode 100644 index 000000000..4fdcdf557 --- /dev/null +++ b/documentation/src/main/asciidoc/controlling-nested-bean-mappings.asciidoc @@ -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. +==== diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 9e0f26449..6fe7ddcb0 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -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 @@ -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