Prevent null check in Mapstruct on update

Viewed 5709

I'm struggling with form update. Please consider this example:

// Entity with which I need to perform CRUD operations
public class User {
    private String name;
    private String email;
    private String phone;
    private String address;
}

I send to UI is UserDTO:

public class UserDTO {
    private String name;
    private ContactDataDTO contactDataDTO;
}

public class ContactDataDTO {
    private String email;
    private String phone;
    private String address;
}

My mapper:

@Mapper
public interface UserMapper {

    @Mappings({
        @Mapping(source="email", target="contactDataDTO.email"),
        @Mapping(source="phone", target="contactDataDTO.phone"),
        @Mapping(source="address", target="contactDataDTO.address")
    })
    UserDTO userToUserDTO(User user);

    @InheritInverseConfiguration
    User updateUserFromUserDTO(UserDTO userDTO, @MappingTarget User user);

}

userToUserDTO() works as expected, but generated userDTOToUser() for me seems wierd:

@Override
public User updateUserFromUserDTO(UserDTO userDTO, User user) {
    if ( userDTO == null ) {
        return null;
    }

    String address = userDTOContactDataDTOAddress( userDTO );
    if ( address != null ) {
        user.setAddress( address );
    }
    String phone = userDTOContactDataDTOPhone( userDTO );
    if ( phone != null ) {
        user.setPhone( phone );
    }
    String email = userDTOContactDataDTOEmail( userDTO );
    if ( email != null ) {
        user.setEmail( email );
    }
    user.setName( userDTO.getName() );

    return user;
}

Problematic use case:

  1. Fill in all fields for User.
  2. Open form again and clear phone field.
  3. That means to backend I will send smth like this:

userDTO: {
        name: 'John Doe';
        contactDataDTO: {
            email: 'johndoe@gmail.com',
            phone: null,
            address: 'Home'
        }
    }

So, user.phone won't be updated, as far as I have null check for it in generated code.


I thought NullValueCheckStrategy is what I need, but there no option which fits me. For now the only option I see - write my own implementation of userDTOToUser() without null checks. Maybe you can advise better solution, cause for me it looks like a problem that can happen in any mapper for a target update from DTO with non-primitive source.

Runnable demo: https://repl.it/@aksankin/SlateblueUnimportantStack

Thanks a lot.

3 Answers

Probably Optional<> is what you're searching for. In this case you would have null for empty field, Optional if null was send from UI and Optional for actual value. But probably you need to create different DTO for request and response.

If you want null to have a certain value for you then you are looking for source presence checking. You can then control in the setPhone of the DTO whether it was set or not and add hasPhone that would use the flag. Then MapStruct will use the presence check method when setting the value.

try:

@Mapper(  )
public interface UserMapper {

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

    @Mappings({
        @Mapping(source="email", target="contactDataDTO.email"),
        @Mapping(source="phone", target="contactDataDTO.phone"),
        @Mapping(source="address", target="contactDataDTO.address")
    })
    UserDTO userToUserDTO(User user);

    default void updateUserFromUserDTO(UserDTO userDTO, User user) {
        intUpdateUserFromUserDTO( userDTO, userDTO.getContactDataDTO(), user );
    }

    void intUpdateUserFromUserDTO(UserDTO userDTO, ContactDataDTO contactDataDTO, @MappingTarget User user);

}

(note: I returned void iso a type, which is strictly not needed).

Related