Skip to content

defaultExpression not being applied when source property not specified explicitly #1966

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
marceloverdijk opened this issue Nov 8, 2019 · 10 comments
Assignees
Labels
Milestone

Comments

@marceloverdijk
Copy link
Contributor

When having the following classes:

public class AnimalRecord {

    private String[] previousNames;
}

public class Animal {

    private List<String> previousNames;
}

and a mapper like:

@Mapper
public interface AnimalRecordMapper {

    @Mapping(target = "previousNames", 
            nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, 
            defaultExpression = "java(Collections.emptyList())")
    Animal toAnimal(AnimalRecord record);
}

I would expect that the implementation would convert a null array in AnimalRecord to an empty List (not null) in the Animal instance.

However the generated code looks like:

@Override
public Animal toAnimal(AnimalRecord record) {
    if ( record == null ) {
        return null;
    }

    Animal animal = new Animal();

    List<String> list = stringArrayToStringList( record.getPreviousNames() );
    if ( list != null ) {
        animal.setPreviousNames( list );
    }

    return animal;
}

When I explicitly add the source = "previousNames" to the @Mapping like:

@Mapper
public interface AnimalRecordMapper {

    @Mapping(source = "previousNames", 
            target = "previousNames", 
            nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, 
            defaultExpression = "java(Collections.emptyList())")
    Animal toAnimal(AnimalRecord record);
}

then the code is generated as expected:

@Override
public Animal toAnimal(AnimalRecord record) {
    if ( record == null ) {
        return null;
    }

    Animal animal = new Animal();

    List<String> list = stringArrayToStringList( record.getPreviousNames() );
    if ( list != null ) {
        animal.setPreviousNames( list );
    }
    else {
        animal.setPreviousNames( Collections.emptyList() );
    }

    return animal;
}

Note the else part generated to set the empty list.

It seems the defaultExpression is not being applied when the source property is not specified.
Maybe in general or related to the fact that there is also an array to List conversion needed.

@marceloverdijk
Copy link
Contributor Author

Note: using MapStruct 1.3.1.Final.

@filiphr filiphr added this to the 1.4.0 milestone Nov 8, 2019
@filiphr filiphr added the bug label Nov 8, 2019
@filiphr
Copy link
Member

filiphr commented Nov 8, 2019

Thanks for the issue @marceloverdijk. In my opinion this is a bug, we should always apply the defaultExpression irregardless whether source is defined or not. Since it applies to the target property

@sjaakd
Copy link
Contributor

sjaakd commented Nov 10, 2019

@marceloverdijk .. I tried to replicate the problem, but.. it works on my machine 😄

@Mapper( imports = Collections.class )
public interface Issue1966Mapper {

    Issue1966Mapper INSTANCE = Mappers.getMapper(Issue1966Mapper.class);

    @Mapping(target = "previousNames",
        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
        defaultExpression = "java(Collections.emptyList())")
    Animal toAnimal(AnimalRecord record);

    class AnimalRecord {

         private String[] previousNames;

        public String[] getPreviousNames() {
            return previousNames;
        }

        public void setPreviousNames(String[] previousNames) {
            this.previousNames = previousNames;
        }
    }

    class Animal {

        private List<String> previousNames;

        public List<String> getPreviousNames() {
            return previousNames;
        }

        public void setPreviousNames(List<String> previousNames) {
            this.previousNames = previousNames;
        }
    }

}

result (as expected):

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2019-11-10T12:25:37+0100",
    comments = "version: , compiler: Eclipse JDT (Batch) 1.2.100.v20160418-1457, environment: Java 1.8.0_181 (Oracle Corporation)"
)
public class Issue1966MapperImpl implements Issue1966Mapper {

    @Override
    public Animal toAnimal(AnimalRecord record) {
        if ( record == null ) {
            return null;
        }

        Animal animal = new Animal();

        List<String> list = stringArrayToStringList( record.getPreviousNames() );
        if ( list != null ) {
            animal.setPreviousNames( list );
        }
        else {
            animal.setPreviousNames( Collections.emptyList() );
        }

        return animal;
    }

    protected List<String> stringArrayToStringList(String[] stringArray) {
        if ( stringArray == null ) {
            return null;
        }

        List<String> list = new ArrayList<String>( stringArray.length );
        for ( String string : stringArray ) {
            list.add( string );
        }

        return list;
    }
}

Note: I used the latest master branch in order to create a test case. But I don't think we fixed an issue in this area.

Note ii: I added an import to my mapper.

@marceloverdijk
Copy link
Contributor Author

Interesting @sjaakd :-)

I've setup this sample project https://github.com/marceloverdijk/mapstruct-1966
It contains a failing tests, when adding the source property explicitly the test succeeds.

Could you check it out and see if you see different behavior?

@sjaakd
Copy link
Contributor

sjaakd commented Nov 10, 2019

I just checked.. Compile your example against 1.4.0-SNAPSHOT (our master) and it works as expected.. I guess we must have fixed something for the better 😄

sjaakd pushed a commit to sjaakd/mapstruct that referenced this issue Nov 10, 2019
@sjaakd
Copy link
Contributor

sjaakd commented Nov 10, 2019

I added #1967 jus to avoid regression.

sjaakd pushed a commit to sjaakd/mapstruct that referenced this issue Nov 10, 2019
sjaakd added a commit that referenced this issue Nov 10, 2019
@sjaakd
Copy link
Contributor

sjaakd commented Nov 10, 2019

@marceloverdijk thanks for reporting. I'll proceed to close the issue, since the problem does not occur on our master.

@sjaakd sjaakd closed this as completed Nov 10, 2019
@marceloverdijk
Copy link
Contributor Author

Yes, I can confirm with 1.4.0-SNAPSHOT it works as expected 👍

ttzn added a commit to ttzn/mapstruct that referenced this issue Nov 11, 2019
@shamoh
Copy link

shamoh commented Apr 2, 2020

When do you expect the 1.4.0 is going to be released?

@rdolejsi
Copy link

rdolejsi commented Apr 13, 2022

Hello, I'm trying the same use case and hitting the bug as well. Tried 1.4.2.Final, 1.5.0.Beta1 and 1.5.0.RC1.

In my case an object has List where Step -> StepRecord is also processed by the mapper. The mapping looks like this:
@Mapping(target = "steps", source = "steps", defaultExpression = "java( new ArrayList<>() )"),

The generated code calls a separate method:
parentRecord.steps( StepListToStepRecordList( dto.getSteps() ) )

And the method starts with the following statement:

if ( list == null ) {
    return null;
}

After checking the rest of the generated mapper, the defaultExpression code is actually present, but just in the update() logic, not in to(). Is there any way to process default values during standard conversion or do we need to always call to() and then update() to make sure the defaults are applied?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants