Default value for secondary (and subsequent) source parameters - bug or feature request?

800 views
Skip to first unread message

Nick Tinnemeier

unread,
Oct 20, 2020, 2:33:11 AM10/20/20
to mapstruct-users
Good morning,

While using MapStruct - which is doing a great job for us, so thanks for that  - I ran into the following issue. Now, I am wondering if this is a bug or a possible new feature.

I am using the latest version 1.4.1 running on Java 8.

Consider the following domain models in which we have two simple source classes A and B:

 public class A {
    private final String a;

    // Getters and setters
}


public class B {
    private final Integer b;

    // Getters and setters

}

I like to map these two classes to a target class with two fields:

public class ABTarget {
    private String a;
    private B b;

    // Getters and setters
 }

 I create the following mapper for doing so: 

@Mapper
public interface MyMapper {
    @Mapping(target = "b", source = "b")
    ABTarget map(A a, B b);
}

 Which generates the following mapper method: 

@Override
public ABTarget map(A a, B b) {

    ABTarget aBTarget = new ABTarget();

    if ( a != null ) {
        aBTarget.setA( a.getA() );
    }
    if ( b != null ) {

        aBTarget.setB( b );
    }

    return aBTarget;
}

 

This works fine for most cases. But now let’s assume that `b` can be null in which case I want to set a default value for this field, for example `new B(1)`.

 I go with the following mapper using a default expression:

@Mapper(nullValuePropertyMappingStrategy = SET_TO_DEFAULT,

        nullValueMappingStrategy = NRETURN_DEFAULT)
public interface MyMapperWrongSolutionWithDefaultValue {
    @Mapping(target = "b",

             source = "b",

             defaultExpression = "java(new models.B(1))")
    ABTarget map(A a, B b);
}

 

resulting in this mapping method:

@Override
public ABTarget map(A a, B b) {
    ABTarget aBTarget = new ABTarget();
    if ( a != null ) {
        aBTarget.setA( a.getA() );
    }
    if ( b != null ) {
        if ( b != null ) {
            aBTarget.setB( b );
        }
        else {
            aBTarget.setB( new models.B(1) );
        }
    }
    return aBTarget;
}
 

Oops, that is not going to work. I understand that this is an edge case, because in this case the property corresponds to the whole source object. Indeed, if we would apply the same concept on the ‘a’ field, it becomes clear why MapStruct generates that seemingly redundant null check:

 

@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
public interface MyMapperDefaultValueForProperty {
    @Mapping(target = "a", source = "a.a", defaultExpression = "java(\"\")")
    @Mapping(target = "b", source = "b")
    ABTarget map(A a, B b);
}

 

@Override
public ABTarget map(A a, B b) {
    ABTarget aBTarget = new ABTarget();
    if ( a != null ) {
        if ( a.getA() != null ) {
            aBTarget.setA( a.getA() );
        }
        else {
            aBTarget.setA( "" );
        }
    }
    if ( b != null ) {
        aBTarget.setB( b );
    }
    return aBTarget;
}

 

I managed to come up with a few workarounds, though. For the first workaround I resort to using an expression, instead of a default expression:

@Mapper
public interface MyMapperWorkingSolutionWithExpression {
    @Mapping(target = "b", expression = "java(map(b))")
    ABTarget map(A a, B b);

    default B map(B b) {
        return b != null? b : new B(1);
    }
}

The second workaround boils down to using the after mapping construct:

@Mapper
public interface MyMapperSolutionWithAfterMapping {
    @AfterMapping
    default ABTarget afterMap(@MappingTarget ABTarget t) {
        if (t.getB() == null) {
            t.setB(new B(1));
        }

        return t;
    }

    @Mapping(target = "b", source = "b")
    ABTarget map(A a, B b);
}

Yet, wouldn’t it be cool if the default expression would work out-of-the-box for nullable source objects? Is it worthwhile creating a bug report / feature request for this?

Filip Hrisafov

unread,
Oct 20, 2020, 1:28:40 PM10/20/20
to mapstruct-users
Hey,

Thanks for the detailed write up. I think that this is a bug and that mapstruct/mapstruct#2023 is the actual issue that is identical to what you wrote up. Please let me know if you think that it is different.

Cheers,
Filip

Nick Tinnemeier

unread,
Oct 22, 2020, 1:59:15 AM10/22/20
to Filip Hrisafov, mapstruct-users
Hi Filip,

Yes, this indeed seems like the exact same issue. I looked for similar issues, but didn't find this one. So thanks for pointing it out to me. Do you want me to share my workarounds in the ticket also?

- Nick

Op di 20 okt. 2020 19:28 schreef Filip Hrisafov <filip.h...@gmail.com>:
--
You received this message because you are subscribed to a topic in the Google Groups "mapstruct-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/mapstruct-users/zBAxuWrxHK0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to mapstruct-use...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/mapstruct-users/2f1c3a57-324c-4ae3-8396-941c649d1684n%40googlegroups.com.

Filip Hrisafov

unread,
Oct 22, 2020, 2:08:39 PM10/22/20
to mapstruct-users
Hey Nich,

I think that it is a good idea to share your workarounds in the tickets. Maybe it will help someone else as well.

Cheers,
Filip
Reply all
Reply to author
Forward
0 new messages