Skip to content

NullValuePropertyMappingStrategy#IGNORE is not properly applied on maps / collections without setters #3806

@Majlanky

Description

@Majlanky

Expected behavior

I would expect that when mapping for Map attribute that has getter in destination but no setter will get similar implementation as for Map that has both setter and getter, especially when null attributes should be ignored as declare in examples below. Lets assume these types and mapper:

public interface DestinationType {

    void addAttribute(String key, String value);

    Map<String, String> getAttributes();

    Map<String, String> getOthers();

    void setOthers(Map<String, String> others);

}

public class SourceType {

    Map<String, String> attributes = new HashMap<>();
    Map<String, String> others = new HashMap<>();

    public Map<String, String> getAttributes() {
        return attributes;
    }

    public void setAttributes(Map<String, String> attributes) {
        this.attributes = attributes;
    }

    public Map<String, String> getOthers() {
        return others;
    }

    public void setOthers(Map<String, String> others) {
        this.others = others;
    }
}

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE,
    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
    nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface MyMapper{

    void update(@MappingTarget DestinationType destination, SourceType source);

}

Actual behavior

When I change MyMapperImpl I can see:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2025-01-21T13:46:15+0100",
    comments = "version: 1.6.3, compiler: javac, environment: Java 21.0.1 (Azul Systems, Inc.)"
)
public class MyMapperImpl implements MyMapper {

    @Override
    public void update(DestinationType destination, SourceType source) {
        if ( source == null ) {
            return;
        }

        if ( destination.getOthers() != null ) {
            Map<String, String> map = source.getOthers();
            if ( map != null ) {
                destination.getOthers().clear();
                destination.getOthers().putAll( map );
            }
        }
        else {
            Map<String, String> map = source.getOthers();
            if ( map != null ) {
                destination.setOthers( new LinkedHashMap<String, String>( map ) );
            }
        }
        if ( destination.getAttributes() != null ) {
            destination.getAttributes().clear();
            Map<String, String> map1 = source.getAttributes();
            if ( map1 != null ) {
                destination.getAttributes().putAll( map1 );
            }
        }
    }
}

the biggest difference is clearing the destination collection. For others map we can see the "ignoring" as destination is untouched when source is null but for attributes map, there is clear always, which breaks the "ignoring" rule.

Maybe I get something wrong, but I am at the end with ideas.

Steps to reproduce the problem

Described above

MapStruct Version

1.6.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions