简体   繁体   中英

How to configurate mapstruct to ignore map object when all field are null

env:

  • jdk: 17.0.1
  • mapstruct: 1.5.1.Final

Using the default configuration I generated the following code

        protected AgentInfo wealthProdAccountInfoDTOToAgentInfo(WealthProdAccountInfoDTO wealthProdAccountInfoDTO) {
        if ( wealthProdAccountInfoDTO == null ) {
            return null;
        }

        String agentName = null;
        String agentIdentityType = null;
        String agentIdentityNo = null;
        String agentIdentityExpireAt = null;

        agentName = wealthProdAccountInfoDTO.getAgentName();
        agentIdentityType = wealthProdAccountInfoDTO.getAgentIdentityType();
        agentIdentityNo = wealthProdAccountInfoDTO.getAgentIdentityNo();
        agentIdentityExpireAt = wealthProdAccountInfoDTO.getAgentIdentityExpireAt();

        AgentInfo agentInfo = new AgentInfo( agentName, agentIdentityType, agentIdentityNo, agentIdentityExpireAt );

        return agentInfo;
    }

But I want to return null when all field of source are null, like this

    protected AgentInfo wealthProdAccountInfoDTOToAgentInfo(WealthProdAccountInfoDTO wealthProdAccountInfoDTO) {
        if ( wealthProdAccountInfoDTO == null ) {
            return null;
        }
        // add check logic
        if (agentName == null && agentIdentityType == null && agentIdentityNo == null && agentIdentityExpireAt == null) {
            return null;
        }

        String agentName = null;
        String agentIdentityType = null;
        String agentIdentityNo = null;
        String agentIdentityExpireAt = null;

        agentName = wealthProdAccountInfoDTO.getAgentName();
        agentIdentityType = wealthProdAccountInfoDTO.getAgentIdentityType();
        agentIdentityNo = wealthProdAccountInfoDTO.getAgentIdentityNo();
        agentIdentityExpireAt = wealthProdAccountInfoDTO.getAgentIdentityExpireAt();

        AgentInfo agentInfo = new AgentInfo( agentName, agentIdentityType, agentIdentityNo, agentIdentityExpireAt );

        return agentInfo;
    }

how should I configure it?

Unfortunately there's no clean solution for your problem, except implementing code for null check by yourself, Marc specified the right approach to your problem (I'd go with it personally or would use default method for the same purpose).

I can add some workarounds, which will only work if mapping target is inner object:

  1. Use @BeforeMapping to set input inner object to null, so when there will be null-check it will be skipped

     @BeforeMapping default void clearData(TestB source, @MappingTarget TestA target) { TestD innerD = source.getInnerD(); if (innerD.getSecond() == null && innerD.getFirst() == null) { source.setInnerD(null); } }

    And it will generate the following code:

     @Override public TestA from(TestB input) { .... clearData( input, testA ); //set input field to null testA.setInnerC( fromInner( input.getInnerD() ) ); .... } @Override public TestC fromInner(TestD input) { if ( input == null ) { //skip because of null return null; } .... }
  2. Use @AfterMapper to set output parameter to null(it will be mapped in the first place, so there will be some overhead)

     @AfterMapping default void clearData(TestB source, @MappingTarget TestA target) { TestD innerD = source.getInnerD(); if (innerD.getSecond() == null && innerD.getFirst() == null) { target.setInnerC(null); } }

    And generated code will be:

     @Override public TestA from(TestB input) { .... testA.setInnerC( fromInner( input.getInnerD() ) ); //field is actually mapped but cleared later clearData( input, testA ); return testA; }

As I said, these solutions aren't really clean and should be seen as workarounds only. Pros of these workaround is that you will keep working with autogenerated code and these hacks will be hidden inside that code.

UPD Stumbled upon @DecoratedWith lately and it also can do the trick. https://mapstruct.org/documentation/stable/reference/html/#_customizing_mappings

Just implement decorator for iterable2iterable mapping method: List<A> from(List<b> b) and just manually iterate over b checking if all b's fields are null and if so skip it

brute force... it's a simple class, so create a custom mapper

@Mapper
public interface AgentInfoMapper {

    @Named("AgentInfoNullIfContentsNull")
    public static AgentInfo custom(WealthProdAccountInfoDTO dto) {
        if ( wealthProdAccountInfoDTO == null ) {
            return null;
        }
        if (agentName == null && agentIdentityType == null && agentIdentityNo == null && agentIdentityExpireAt == null) {
            return null;
        }
        // mapping code
    }
}

https://www.baeldung.com/mapstruct-custom-mapper

Thanks to ArtemAgaev's idea, I ended up considering using @AfterMapping and java reflection for this type of scenario

    @AfterMapping
    default void cleanData(@MappingTarget AccountInfoDomain domain) {
        Optional.ofNullable(domain).ifPresent(c -> {
            if (isAllFieldNull(domain.getAgentInfo())) {
                domain.setAgentInfo(null);
            }
        });
    }

    public static boolean isAllFieldNull(Object o) {
        Object[] fieldsValue = getFieldsValue(o);
        return Optional.ofNullable(fieldsValue).map(f -> Arrays.stream(f).allMatch(Objects::isNull)).orElse(true);
    }

    public static Object[] getFieldsValue(Object obj) {
        if (null != obj) {
            final Field[] fields = getFields(obj instanceof Class ? (Class<?>) obj : obj.getClass());
            if (null != fields) {
                final Object[] values = new Object[fields.length];
                for (int i = 0; i < fields.length; i++) {
                    values[i] = getFieldValue(obj, fields[i]);
                }
                return values;
            }
        }
        return null;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM