Specify multiple source fields for a tertiary mapper

Viewed 50

Sample Objects

public class Source {
    String fromPersonFirstName;
    String fromPersonLastName;
    String toPersonFirstName;
    String toPersonLastName;
    String message;
}

public class Target {
    Person from;
    Person to;
    String message;

}

public class Person {
    String firstName;
    String lastName;
}

@Mapper(componentModel = "spring")
public interface PersonMapper {
    Person toPerson(String firstName, String lastName);
}

The Question

Now, what would be a clean way of employing as much as mapstruct as possible? Without using expressions?

Ideally I'd tell mapstruct to do the following

@Mapper(componentModel = "spring", uses = PersonMapper.class)
public interface ExampleMapper {

    @Mapping(target = "from", source = "fromPersonFirstName, fromPersonLastName")
    @Mapping(target = "to", source = "toPersonFirstName, toPersonLastName")
    Target toTarget(Source s);
}

Alternatively I could annotate PersonMapper and toPerson, then for the toTarget method qualifiedByName = "TheClassNameValue, TheMethodNameValue". But I still don't know how to tell the method which source fields to use.

What would be another option? I could use a local method toPerson, qualifiedByName. But then I'd have to import the other mapper in some way. Maybe change the ExampleMapper to abstract and autowire the other mapper. But I still can't specify multiple fields.

I know that expression is probably meant to fill in this gap. But ... I would rather never use it if possible. Unless the alternative is considerably more complicated.

1 Answers

Solution 1, Mapping method selection based on qualifiers

@Mapper
public interface ExampleMapper {
    @Mapping(target = "from", source = "source", qualifiedByName = "fromPersonMapping")
    @Mapping(target = "to", source = "source", qualifiedByName = "toPersonMapping")
    Target toTarget(Source source);

    @Named("fromPersonMapping")
    @Mapping(target = "firstName", source = "fromPersonFirstName")
    @Mapping(target = "lastName", source = "fromPersonLastName")
    Person fromPersonMapping(Source source);

    @Named("toPersonMapping")
    @Mapping(target = "firstName", source = "toPersonFirstName")
    @Mapping(target = "lastName", source = "toPersonLastName")
    Person toPersonMapping(Source source);
}

Generated code:

public class ExampleMapperImpl implements ExampleMapper {

    @Override
    public Target toTarget(Source source) {
        if ( source == null ) {
            return null;
        }
        Target target = new Target();
        target.from = fromPersonMapping( source );
        target.to = toPersonMapping( source );
        target.message = source.message;
        return target;
    }

    @Override
    public Person fromPersonMapping(Source source) {
        if ( source == null ) {
            return null;
        }
        Person person = new Person();
        person.firstName = source.fromPersonFirstName;
        person.lastName = source.fromPersonLastName;
        return person;
    }

    @Override
    public Person toPersonMapping(Source source) {
        if ( source == null ) {
            return null;
        }
        Person person = new Person();
        person.firstName = source.toPersonFirstName;
        person.lastName = source.toPersonLastName;
        return person;
    }
}

Alternative with custom methods:

@Mapper
public interface ExampleMapper {
    @Mapping(target = "from", source = "source", qualifiedByName = "fromFieldsMapping")
    @Mapping(target = "to",  source = "source", qualifiedByName = "toFieldsMapping")
    Target toTarget(Source source);

    Person toPerson(String firstName, String  lastName);

    @Named("fromFieldsMapping")
    default Person fromFieldsMapping(Source source) {
        return toPerson(source.fromPersonFirstName, source.fromPersonLastName);
    }

    @Named("toFieldsMapping")
    default Person toFieldsMapping(Source source) {
        return toPerson(source.toPersonFirstName, source.toPersonLastName);
    }
}

Generated code:

public class ExampleMapperImpl implements ExampleMapper {

    @Override
    public Target toTarget(Source source) {
        if ( source == null ) {
            return null;
        }

        Target target = new Target();

        target.from = fromFieldsMapping( source );
        target.to = toFieldsMapping( source );
        target.message = source.message;

        return target;
    }

    @Override
    public Person toPerson(String firstName, String lastName) {
        if ( firstName == null && lastName == null ) {
            return null;
        }

        Person person = new Person();

        if ( firstName != null ) {
            person.firstName = firstName;
        }
        if ( lastName != null ) {
            person.lastName = lastName;
        }

        return person;
    }
}

With separate mapper:

@Mapper(uses = PersonMapper.class)
public interface ExampleMapper1 {
    @Mapping(target = "from", source = "source", qualifiedBy = FromAnnotation.class)
    @Mapping(target = "to", source = "source", qualifiedBy = ToAnnotation.class)
    Target toTarget(Source source);
}

@Mapper
public interface PersonMapper {
    Person toPerson(String firstName, String lastName);

    @FromAnnotation
    default Person fromFieldsMapping(Source source) {
        return toPerson(source.fromPersonFirstName, source.fromPersonLastName);
    }

    @ToAnnotation
    default Person toFieldsMapping(Source source) {
        return toPerson(source.toPersonFirstName, source.toPersonLastName);
    }
}

@Qualifier
@java.lang.annotation.Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ToAnnotation {
}

@Qualifier
@java.lang.annotation.Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface FromAnnotation {
}

Solution 2, Expression

@Mapper
public interface ExampleMapper {
    @Mapping(target = "from", expression = "java(toPerson(source.fromPersonFirstName, source.fromPersonLastName))")
    @Mapping(target = "to", expression = "java(toPerson(source.toPersonFirstName, source.toPersonLastName))")
    Target toTarget(Source source);

    Person toPerson(String firstName, String  lastName);
}

Generated code:

    @Override
    public Target toTarget(Source source) {
        if ( source == null ) {
            return null;
        }
        Target target = new Target();
        target.message = source.message;
        target.from = toPerson(source.fromPersonFirstName, source.fromPersonLastName);
        target.to = toPerson(source.toPersonFirstName, source.toPersonLastName);
        return target;
    }

    @Override
    public Person toPerson(String firstName, String lastName) {
        if ( firstName == null && lastName == null ) {
            return null;
        }
        Person person = new Person();
        if ( firstName != null ) {
            person.firstName = firstName;
        }
        if ( lastName != null ) {
            person.lastName = lastName;
        }
        return person;
    }

Solution 3, regular mapping

@Mapper
public interface ExampleMapper {
    @Mapping(target = "from.firstName", source = "source.fromPersonFirstName")
    @Mapping(target = "from.lastName", source = "source.fromPersonLastName")
    @Mapping(target = "to.firstName",  source = "source.toPersonFirstName")
    @Mapping(target = "to.lastName",  source = "source.toPersonLastName")
    Target toTarget(Source source);
}

Generated code:

public class ExampleMapperImpl implements ExampleMapper {

    @Override
    public Target toTarget(Source source) {
        if ( source == null ) {
            return null;
        }

        Target target = new Target();

        target.from = sourceToPerson( source );
        target.to = sourceToPerson1( source );
        target.message = source.message;

        return target;
    }

    protected Person sourceToPerson(Source source) {
        if ( source == null ) {
            return null;
        }

        Person person = new Person();

        person.firstName = source.fromPersonFirstName;
        person.lastName = source.fromPersonLastName;

        return person;
    }

    protected Person sourceToPerson1(Source source) {
        if ( source == null ) {
            return null;
        }

        Person person = new Person();

        person.firstName = source.toPersonFirstName;
        person.lastName = source.toPersonLastName;

        return person;
    }
}
Related