human547234923750547
human547234923750547

Reputation: 119

How recursively update nested objects from one object by another use mapstruct or modelmapper?

Better i explain my task by an example of what I want to get. Is it possible to solve this use mapstruct / modelmapper / etc?

class Person{
    String name;
    Address address;
}

class Address{
    String street;
    Integer home;
}

Updates:

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

Target:

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

And as result i want get:

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

It must't rewrite Address object. It recursively set new values in it.

Upvotes: 2

Views: 4222

Answers (2)

Radzivon Hrechukha
Radzivon Hrechukha

Reputation: 86

Although the existing answer is correct, there's another solution which does not require defining methods for all inner objects. I personally faced similar issue to define mappers for all inner classes when trying to map lots of Embeddable objects with the requirement to not replace but leave original instances of them.

According to official documentation mapper behavior can be controlled using the mappingControl property. Combining mappingControl = DeepClone.class with @BeanMapping we can write very simple mapper:

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

and generated mapper implementaion will go through inner objects and update them with the new values:

    @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() );
    }

Note that generated departmentToDepartment method sets all fields of the Department class one-by-one instead of just setting the reference to department from new object to mapping target.

P.S.

Source of User.class:

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

Source of Department.class:

public class Department {
    private String name;
    private String address;
}

Upvotes: 0

Filip
Filip

Reputation: 21393

Yes you can use Updating existing bean instances from MapStruct to do the updates you are looking for.

The mapper would look like:

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

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

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

The generated code for this would look like:

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 - The strategy to be applied when a source bean property is null or not present. The default is to set the value to target value to null
  • nullValueCheckStrategy - Determines when to include a null check on the source property value of a bean mapping

NB The nullValuePropertyMappingStrategy is from MapStruct 1.3.0.Beta2

Upvotes: 4

Related Questions