Reputation: 119
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
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
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 mappingNB The nullValuePropertyMappingStrategy
is from MapStruct 1.3.0.Beta2
Upvotes: 4