Как рекурсивно обновлять вложенные объекты из одного объекта другим, используя mapstruct или modelmapper?

Лучше я объясню свою задачу на примере того, что я хочу получить. Возможно ли решить эту проблему с помощью mapstruct / modelmapper / etc?

class Person{
    String name;
    Address address;
}

class Address{
    String street;
    Integer home;
}

Обновления:

{
    name: "Bob"
    address: {
                 street: "Abbey Road"
             }
}

Цель:

{
    name: "Michael"
    address: {
                 street: "Kitano"
                 home: 5
             }
}

И в результате я хочу получить:

{
    name: "Bob"
    address: {
                 street: "Abbey Road"
                 home: 5
             }
}

Он не должен переписывать объект Address. Он рекурсивно устанавливает новые значения в нем.

2 ответа

Да, вы можете использовать Обновление существующих экземпляров bean-компонентов из MapStruct для выполнения необходимых обновлений.

Картограф будет выглядеть так:

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

    void update(@MappingTarget Person toUpdate, Person person);

    void update(@MappingTarget Address toUpdate, Address address);
}

Сгенерированный код для этого будет выглядеть так:

public class PersonMapperImpl implements PersonMapper {

    @Override
    public void update(Person toUpdate, Person person) {
        if ( person == null ) {
            return;
        }

        if ( person.getName() != null ) {
            toUpdate.setName( person.getName() );
        }
        if ( person.getAddress() != null ) {
            if ( toUpdate.getAddress() == null ) {
                toUpdate.setAddress( new Address() );
            }
            update( toUpdate.getAddress(), person.getAddress() );
        }
    }

    @Override
    public void update(Address toUpdate, Address address) {
        if ( address == null ) {
            return;
        }

        if ( address.getStreet() != null ) {
            toUpdate.setStreet( address.getStreet() );
        }
        if ( address.getHome() != null ) {
            toUpdate.setHome( address.getHome() );
        }
    }
}
  • nullValuePropertyMappingStrategy - Стратегия, которая должна применяться, когда свойство исходного компонента null или нет По умолчанию устанавливается значение целевого значения null
  • nullValueCheckStrategy - Определяет, когда включать null проверка значения свойства источника отображения bean-компонента

NB nullValuePropertyMappingStrategy это из MapStruct 1.3.0.Beta2

Хотя существующий ответ верен, есть другое решение, которое не требует определения методов для всех внутренних объектов. Я лично столкнулся с аналогичной проблемой при определении преобразователей для всех внутренних классов при попытке сопоставить множество встраиваемых объектов с требованием не заменять, а оставлять их исходные экземпляры.

Согласно официальной документации, поведением картографа можно управлять с помощьюmappingControlсвойство. ОбъединениеmappingControl = DeepClone.classс@BeanMappingмы можем написать очень простой картограф:

          @BeanMapping(mappingControl = DeepClone.class)
    public abstract User updateFields(@MappingTarget User oldUser, User newUser);

и сгенерированная реализация картографа будет проходить через внутренние объекты и обновлять их новыми значениями:

          @Override
    public User updateFields(User oldUser, User newUser) {
        if ( newUser == null ) {
            return oldUser;
        }

        oldUser.setName( newUser.getName() );
        oldUser.setEmail( newUser.getEmail() );
        if ( newUser.getDepartment() != null ) {
            if ( oldUser.getDepartment() == null ) {
                oldUser.setDepartment( new Department() );
            }
            departmentToDepartment( newUser.getDepartment(), oldUser.getDepartment() );
        }
        else {
            oldUser.setDepartment( null );
        }

        return oldUser;
    }

    protected void departmentToDepartment(Department department, Department mappingTarget) {
        if ( department == null ) {
            return;
        }

        mappingTarget.setName( department.getName() );
        mappingTarget.setAddress( department.getAddress() );
    }

Обратите внимание, что созданныйdepartmentToDepartmentметод устанавливает все поляDepartmentкласс один за другим вместо того, чтобы просто устанавливать ссылку наdepartmentот нового объекта к цели сопоставления.

ПС

ИсточникUser.class:

      public class User {
    private String name;
    private String email;
    private Department department;
}

ИсточникDepartment.class:

      public class Department {
    private String name;
    private String address;
}
Другие вопросы по тегам