Map null values to default using builder with MapStruct

Viewed 12913

I want to map field from Source to Target class, and if the source value is null, I would like to convert it to default value based on the data type ("" for strings, 0 for numeric types etc.). For setting the values, I am not using regular setters, but builder (with protobuf, so the names of the methods is newBuilder() and build()).

class Source {
    private final String value; // getter
}

class Target {
    private final String value;

    public static Builder newBuilder() {return new Builder()}

    public static class Builder {
        public static setValue() {/*Set the field*/}
        public static Target build() {/*Return the constructed instance*/}
}

My mapper looks like this:

@Mapper(
    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT,
    nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT
)
public interface TargetMapper {
    Target map(Source source);
}

The generated mapper implementation with this code calls target.setValue(source.getValue()), instead of performing the null check and setting default value if source returns null. The interesting part is when I add the following annotation to the map method, the null check is present in the implementation.

@Mapping(source="value", target="value", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)

Is this a bug in MapStruct with builders, or am I missing some configuration to be ably to set the null mapping as a default policy, instead of duplicating it on all field mappings?

EDIT: For some reason, adding nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS to the class level @Mapper annotation adds the null check, but does not explicitly set the value, just skips the call to setValue. For protobuf, this is okay, since this functionality is in the library, but for other implementations the field would remain null.

1 Answers

@Mapping(source="value", target="value", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)

applies to update method (so methods that have the @MappingTarget annotated parameter

There's no real counterpart for regular methods: 1. NullValueMappingStragegy applies to the bean argument itself. 2. NullValueCheckStragegy does perform a check on bean properties, but does not return a default.

Naming is not really brilliant and it has a long history. We still have the intention to align this one day.

A solution would be to use an Object factory creating the builder target object and pre-populate it with default values and then let MapStuct override these one day.

Perhaps you could do something like this:

@Mapper(
    // to perform a null check
    nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface TargetMapper {
    Target map(Source source);
}

// to create a pre-defined object (defaults set a-priori). Not sure
// whether this works with builders.. just try
@ObjectFactory
default Target.Builder create() {

   Target.Builder builder = Target.newBuilder();
   builder.setValueX( "someDefaultValue" );
   return builder;

}
Related