Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEXT_RELEASE_CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### Features

- [#3387](https://github.com/mapstruct/mapstruct/issues/3387) Add `nullValueIterablePropertyMappingStrategy` and `nullValueMapPropertyMappingStrategy` on `@Mapping`, `@BeanMapping`, `@Mapper` and `@MapperConfig`, allowing Iterable- and Map-typed properties to use a different null-handling strategy than the rest of the bean.

### Enhancements

### Bugs
Expand Down
30 changes: 30 additions & 0 deletions core/src/main/java/org/mapstruct/BeanMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,36 @@
NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source bean property is {@code null} or not present and the target
* property is of {@link java.util.Collection}, {@link Iterable} or array type. If no strategy is configured,
* the strategy given via {@link MapperConfig#nullValueIterablePropertyMappingStrategy()} or
* {@link Mapper#nullValueIterablePropertyMappingStrategy()} will be applied,
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Iterable (including array) type.
*/
NullValuePropertyMappingStrategy nullValueIterablePropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source bean property is {@code null} or not present and the target
* property is of {@link java.util.Map} type. If no strategy is configured, the strategy given via
* {@link MapperConfig#nullValueMapPropertyMappingStrategy()} or
* {@link Mapper#nullValueMapPropertyMappingStrategy()} will be applied,
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Map type.
*/
NullValuePropertyMappingStrategy nullValueMapPropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* Determines when to include a null check on the source property value of a bean mapping.
*
Expand Down
30 changes: 30 additions & 0 deletions core/src/main/java/org/mapstruct/Mapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,36 @@
NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source bean property is {@code null} or not present and the target
* property is of {@link java.util.Collection}, {@link Iterable} or array type. If no strategy is configured,
* the strategy set with {@link #nullValuePropertyMappingStrategy()} will be applied. If neither strategy is
* configured, the strategy given via {@link MapperConfig#nullValueIterablePropertyMappingStrategy()} will be
* applied, using {@link NullValuePropertyMappingStrategy#SET_TO_NULL} by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Iterable (including array) type.
*/
NullValuePropertyMappingStrategy nullValueIterablePropertyMappingStrategy() default
NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source bean property is {@code null} or not present and the target
* property is of {@link java.util.Map} type. If no strategy is configured, the strategy set with
* {@link #nullValuePropertyMappingStrategy()} will be applied. If neither strategy is configured, the strategy
* given via {@link MapperConfig#nullValueMapPropertyMappingStrategy()} will be applied, using
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Map type.
*/
NullValuePropertyMappingStrategy nullValueMapPropertyMappingStrategy() default
NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to use for applying method-level configuration annotations of prototype methods in the interface
* specified with {@link #config()}. Annotations that can be inherited are for example {@link Mapping},
Expand Down
28 changes: 28 additions & 0 deletions core/src/main/java/org/mapstruct/MapperConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,34 @@
NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source bean property is {@code null} or not present and the target
* property is of {@link java.util.Collection}, {@link Iterable} or array type. If no strategy is configured,
* the strategy set with {@link #nullValuePropertyMappingStrategy()} will be applied, using
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Iterable (including array) type.
*/
NullValuePropertyMappingStrategy nullValueIterablePropertyMappingStrategy() default
NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source bean property is {@code null} or not present and the target
* property is of {@link java.util.Map} type. If no strategy is configured, the strategy set with
* {@link #nullValuePropertyMappingStrategy()} will be applied, using
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Map type.
*/
NullValuePropertyMappingStrategy nullValueMapPropertyMappingStrategy() default
NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to use for applying method-level configuration annotations of prototype methods in the interface
* annotated with this annotation. Annotations that can be inherited are for example {@link Mapping},
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/java/org/mapstruct/Mapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,40 @@
NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source property is {@code null} or not present and the target property is
* of {@link java.util.Collection}, {@link Iterable} or array type. If no strategy is configured, the strategy
* given via {@link MapperConfig#nullValueIterablePropertyMappingStrategy()},
* {@link BeanMapping#nullValueIterablePropertyMappingStrategy()} or
* {@link Mapper#nullValueIterablePropertyMappingStrategy()} will be applied.
*
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Iterable (including array) type.
*/
NullValuePropertyMappingStrategy nullValueIterablePropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* The strategy to be applied when the source property is {@code null} or not present and the target property is
* of {@link java.util.Map} type. If no strategy is configured, the strategy given via
* {@link MapperConfig#nullValueMapPropertyMappingStrategy()},
* {@link BeanMapping#nullValueMapPropertyMappingStrategy()} or
* {@link Mapper#nullValueMapPropertyMappingStrategy()} will be applied.
*
* {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default.
*
* @since 1.7
*
* @return The strategy to be applied when {@code null} is passed as source property value or the source
* property is not present, for a target property of Map type.
*/
NullValuePropertyMappingStrategy nullValueMapPropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;

/**
* Allows detailed control over the mapping process.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,48 @@ Some types of mappings (collections, maps), in which MapStruct is instructed to
null check, regardless of the value of the `NullValuePropertyMappingStrategy`, to avoid addition of `null` to the target collection or map. Since the target is assumed to be initialised this strategy will not be applied.
====

[[mapping-result-for-null-iterable-or-map-properties]]
=== Controlling mapping result for 'null' Iterable or Map properties

With <<mapping-result-for-null-properties>> it is possible to control how a target property is set when the corresponding source property is `null` or not present. That strategy is applied uniformly, regardless of the type of the target property.

However, MapStruct also offers a more dedicated way to control this for target properties of Iterable (including array) or Map type, e.g. reset collections / maps to their *default* (empty) value, while setting plain bean properties to `null`.

For target properties of `Collection`, `Iterable` or array type this can be controlled through:

* `MapperConfig#nullValueIterablePropertyMappingStrategy`
* `Mapper#nullValueIterablePropertyMappingStrategy`
* `BeanMapping#nullValueIterablePropertyMappingStrategy`
* `Mapping#nullValueIterablePropertyMappingStrategy`

For target properties of `Map` type this can be controlled through:

* `MapperConfig#nullValueMapPropertyMappingStrategy`
* `Mapper#nullValueMapPropertyMappingStrategy`
* `BeanMapping#nullValueMapPropertyMappingStrategy`
* `Mapping#nullValueMapPropertyMappingStrategy`

If left unconfigured on a given annotation, the more specific (Iterable / Map) strategy falls back to `nullValuePropertyMappingStrategy` configured on that same annotation, before falling back to the next, less specific, annotation level. How the resulting `NullValuePropertyMappingStrategy` value is applied is the same as in <<mapping-result-for-null-properties>>.

.Mapper with dedicated null strategies for Iterable / Map properties
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL,
nullValueIterablePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT,
nullValueMapPropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT
)
public interface OrderMapper {

void update(OrderDto dto, @MappingTarget Order target);
}
----
====

Given the mapper above, a `null` `tags` (`List`) or `meta` (`Map`) source property resets the corresponding target property to an empty collection / map, while a `null` `address` (a plain bean property) sets the target property to `null`, without having to repeat `nullValuePropertyMappingStrategy = SET_TO_DEFAULT` on every Iterable / Map `@Mapping` individually.

[TIP]
====
`NullValuePropertyMappingStrategy` also applies when the presence checker returns `not present`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,15 @@ public PropertyMappingBuilder options(DelegatingOptions options) {
this.mappingControl = options.getMappingControl( ctx.getElementUtils() );
this.nvcs = options.getNullValueCheckStrategy();
if ( method.isUpdateMethod() ) {
this.nvpms = options.getNullValuePropertyMappingStrategy();
if ( targetType.isMapType() ) {
this.nvpms = options.getNullValueMapPropertyMappingStrategy();
}
else if ( targetType.isIterableType() ) {
this.nvpms = options.getNullValueIterablePropertyMappingStrategy();
}
else {
this.nvpms = options.getNullValuePropertyMappingStrategy();
}
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,40 @@ public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy()
.orElse( next().getNullValuePropertyMappingStrategy() );
}

@Override
public NullValuePropertyMappingStrategyGem getNullValueIterablePropertyMappingStrategy() {
Optional<NullValuePropertyMappingStrategyGem> specific =
Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValueIterablePropertyMappingStrategy )
.filter( GemValue::hasValue )
.map( GemValue::getValue )
.map( NullValuePropertyMappingStrategyGem::valueOf );
if ( specific.isPresent() ) {
return specific.get();
}
return Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValuePropertyMappingStrategy )
.filter( GemValue::hasValue )
.map( GemValue::getValue )
.map( NullValuePropertyMappingStrategyGem::valueOf )
.orElse( next().getNullValueIterablePropertyMappingStrategy() );
}

@Override
public NullValuePropertyMappingStrategyGem getNullValueMapPropertyMappingStrategy() {
Optional<NullValuePropertyMappingStrategyGem> specific =
Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValueMapPropertyMappingStrategy )
.filter( GemValue::hasValue )
.map( GemValue::getValue )
.map( NullValuePropertyMappingStrategyGem::valueOf );
if ( specific.isPresent() ) {
return specific.get();
}
return Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValuePropertyMappingStrategy )
.filter( GemValue::hasValue )
.map( GemValue::getValue )
.map( NullValuePropertyMappingStrategyGem::valueOf )
.orElse( next().getNullValueMapPropertyMappingStrategy() );
}

@Override
public NullValueMappingStrategyGem getNullValueMappingStrategy() {
return Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValueMappingStrategy )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy()
mapper.nullValuePropertyMappingStrategy().getDefaultValue() );
}

public NullValuePropertyMappingStrategyGem getNullValueIterablePropertyMappingStrategy() {
return NullValuePropertyMappingStrategyGem.valueOf(
mapper.nullValueIterablePropertyMappingStrategy().getDefaultValue() );
}

public NullValuePropertyMappingStrategyGem getNullValueMapPropertyMappingStrategy() {
return NullValuePropertyMappingStrategyGem.valueOf(
mapper.nullValueMapPropertyMappingStrategy().getDefaultValue() );
}

public NullValueMappingStrategyGem getNullValueMappingStrategy() {
return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().getDefaultValue() );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy()
return next.getNullValuePropertyMappingStrategy();
}

public NullValuePropertyMappingStrategyGem getNullValueIterablePropertyMappingStrategy() {
return next.getNullValueIterablePropertyMappingStrategy();
}

public NullValuePropertyMappingStrategyGem getNullValueMapPropertyMappingStrategy() {
return next.getNullValueMapPropertyMappingStrategy();
}

public NullValueMappingStrategyGem getNullValueMappingStrategy() {
return next.getNullValueMappingStrategy();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,32 @@ public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy()
next().getNullValuePropertyMappingStrategy();
}

@Override
public NullValuePropertyMappingStrategyGem getNullValueIterablePropertyMappingStrategy() {
if ( mapperConfig.nullValueIterablePropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf(
mapperConfig.nullValueIterablePropertyMappingStrategy().get() );
}
if ( mapperConfig.nullValuePropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf(
mapperConfig.nullValuePropertyMappingStrategy().get() );
}
return next().getNullValueIterablePropertyMappingStrategy();
}

@Override
public NullValuePropertyMappingStrategyGem getNullValueMapPropertyMappingStrategy() {
if ( mapperConfig.nullValueMapPropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf(
mapperConfig.nullValueMapPropertyMappingStrategy().get() );
}
if ( mapperConfig.nullValuePropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf(
mapperConfig.nullValuePropertyMappingStrategy().get() );
}
return next().getNullValueMapPropertyMappingStrategy();
}

@Override
public NullValueMappingStrategyGem getNullValueMappingStrategy() {
return mapperConfig.nullValueMappingStrategy().hasValue() ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,29 @@ public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy()
next().getNullValuePropertyMappingStrategy();
}

@Override
public NullValuePropertyMappingStrategyGem getNullValueIterablePropertyMappingStrategy() {
if ( mapper.nullValueIterablePropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf(
mapper.nullValueIterablePropertyMappingStrategy().get() );
}
if ( mapper.nullValuePropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf( mapper.nullValuePropertyMappingStrategy().get() );
}
return next().getNullValueIterablePropertyMappingStrategy();
}

@Override
public NullValuePropertyMappingStrategyGem getNullValueMapPropertyMappingStrategy() {
if ( mapper.nullValueMapPropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf( mapper.nullValueMapPropertyMappingStrategy().get() );
}
if ( mapper.nullValuePropertyMappingStrategy().hasValue() ) {
return NullValuePropertyMappingStrategyGem.valueOf( mapper.nullValuePropertyMappingStrategy().get() );
}
return next().getNullValueMapPropertyMappingStrategy();
}

@Override
public NullValueMappingStrategyGem getNullValueMappingStrategy() {
return mapper.nullValueMappingStrategy().hasValue() ?
Expand Down
Loading
Loading