vivek
vivek

Reputation: 396

MapStruct: How to achieve NullValuePropertyMappingStrategy.SET_TO_DEFAULT while mapping to a new Object

I want to map attributes in target object to default (e.g. String as "") if the corresponding attribute in source is null. How can i achieve that ? I see that the

nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT

only works in case of update.

Is it possible to achieve something while creating the target object ?

Upvotes: 5

Views: 17497

Answers (3)

hc_dev
hc_dev

Reputation: 9418

Default values at object instantiation

To assure default values for objects at initialization we can define them either in the constructor or at field declaration.

Example:

class CarEntity {

    String type = "SUV"; // default set at declaration 
    String color; // default set in constructor
    String name; // no default, may be null!

    public CarEntity() {
        this.color = "black";  // default color set in constructor
    }
}

Here we have preset defaults for two fields (type and color).

If fields are required (as non-null) they should be declared final and either initialized directly or passed as constructor argument: e.g. the required field final UUID id can not have a default, it must be unique like new CarEntity( UUID.randomUUID() ).

Only optional and nullable field is name. It has no default and can be freely assigned a value from a mapping.

Let's see how we can use MapStruct to set a default value when mapped.

Mapping to default values

When default values are set in the class already, the mapping can be simplified.

  • we don't need to set fields at target to assure non-null values, they have preset defaults already
  • all non-null values are mapped from source to target
  • we only need to prevent a null value in respective source properties to overwrite the preset defaults in target

MapStruct may support this:

@Mapper
public interface CarMapper {

    @Mapping(target = "type", ignore = true)
    @Mapping(target = "name", source = "name", defaultValue = "Sample")
    @Mapping(target = "color", condition = "isNotEmpty")
    CarEntity dtoToCarEntity(CarDTO car);

    @Condition
    default boolean isNotEmpty(String value) {
        return value != null && !value.isEmpty();
    }
}

How it works:

  • the predefined default field type is completely ignored in mapping. It will not be mapped at all. So the default at target is preserved, not modified by mapping.
  • the name field is set to default "Sample" if null in source. The default value can also be an empty String like defaultValue = "" or a defaultExpression like for an ID field: defaultExpression = "java( UUID.randomUUID().toString() ).
  • If the field color exists in source as null or empty String it will be ignored by the specified condition = "isNotEmpty" referencing the @Conditional predicate-method. See the MapStruct feature issue-205.

Conditional mapping of properties

However in 10.10. Conditional Mapping is explained that we can decide on property-mapping with a condition:

A custom condition method is a method that is annotated with org.mapstruct.Condition and returns boolean.

e.g. if you only want to map a String property when it is not `null, and it is not empty then you can do something like:

    @Condition
    default boolean isNotEmpty(String value) {
        return value != null && !value.isEmpty();
    }

This will have the conditional effect we want, here for Strings:

  • map only non-null) source-properties and ignore null/empty source-properties
  • all target properties will be set to defaults during initialization

Ignore strategies

Closely related to default values in mapping are the ignore-strategies. However they seem not applicable to creational mappings, but only on update (as of current version 1.5.0.Beta2).

Ignore null properties (currently on update only)

See MapStruct Reference Guide, 10.6. Controlling mapping result for 'null' arguments.

Any annotation parameter nullValuePropertyMappingStrategy like NullValuePropertyMappingStrategy.IGNORE or NullValuePropertyMappingStrategy.SET_TO_DEFAULT has no effect for creational mappings.

Ignore unmapped properties (and keep the preset defaults at target)

There are also strategies to completely ignore unmapped properties. See the tutorial on Baeldung: Ignoring Unmapped Properties with MapStruct.

However this would turn our defaults into something like final fields, similar to constants. Defaults should are designed to be overwritten when more specific values are given.

Upvotes: 2

Filip
Filip

Reputation: 21471

If you want to set default values you need to use Mapping#defaultValue or Mapping#defaultValueExpression.

We currently do not support doing something similar to the nullValuePropertyMappingStrategy.

Another approach could be to use a custom @ObjectFactory that will set the default values in your object. You can read more about Object Factories in the MapStruct documentation

One could also argue that default values should be set in the object construction.

Upvotes: 1

Deb Das
Deb Das

Reputation: 294

In following example the name attribute is mapped to default string "Sample":

@Mapper
public interface CarMapper {
   @Mapping(source = "name", target = "name", defaultValue = "Sample")
   Car getModelFromEntity(CarEntity carEntity);
}

You can use the parameter defaultValue on the @Mapping annotation to map any null value attribute from the source to the specified default value on the target.

Upvotes: 3

Related Questions