Map multiple sources from multiple objects to one target

22.8k Views Asked by At

I want to map the following classes

class Schedule {
    ZoneId timezoneId;
    List<AvailabilityRule> rules;
}
class AvailabilityRule {
    long startEpoch;
    long endEpoch;
}

to these classes.

class ScheduleDTO {
    String timezone;
    List<AvailabilityRuleDTO> rules;
}
class AvailabilityRuleDTO {
    ZonedDateTime startTime;
    ZonedDateTime endTime;
}

Both timezoneId and startEpoch are needed for calculating startTime.

Instant instant = Instant.ofEpochMilli(startEpoch);
ZonedDateTime zonedDateTime = instant.atZone(timezoneId);
        

How can I achieve this using mapstruct?

Pseudo code of what I want

@Mapping(source = {"startEpoch", "timezoneId"}, target = "startTime", qualifiedByName = "epochToString")
AvailabilityRuleDTO toAvailabilityRuleDTO(AvailabilityRule
                                            availabilityRule, Schedule schedule);
2

There are 2 best solutions below

5
Ben Zegveld On BEST ANSWER

This can be done in several ways. Below you see 2 options. They do the same thing only one uses qualifiedByName while the other uses expression. Depending on your need one might fit better then the other.

Using a custom method found by mapstruct

qualifiedByName required because otherwise mapstruct does not know which method to use.

    @Mapping(source = ".", target = "startTime", qualifiedByName = "startTime")
    @Mapping(source = ".", target = "endTime", qualifiedByName = "endTime")
    AvailabilityRuleDTO toAvailabilityRuleDTO(AvailabilityRule availabilityRule, @Context Schedule schedule);

    @Named("startTime")
    protected ZonedDateTime startTime(AvailabilityRule availabilityRule, @Context Schedule schedule) {
        return convertTime(availabilityRule.startEpoch, schedule.timezoneId);
    }

    @Named("endTime")
    protected ZonedDateTime endTime(AvailabilityRule availabilityRule, @Context Schedule schedule) {
        return convertTime(availabilityRule.endEpoch, schedule.timezoneId);
    }

    private ZonedDateTime convertTime(long epoch, String timezoneId) {
        Instant instant = Instant.ofEpochMilli(epoch);
        ZonedDateTime time = LocalDateTime.from(instant).atZone(timezoneId);
        return time;
    }

Using a custom method configured by an expression

@Named used here to prevent mapstruct from accidentally using this method for other mapping actions. Without it it will most likely still work.

    @Mapping(target = "startTime", expression = "java(convertTime(availabilityRule.startEpoch, schedule.timezoneId))" )
    @Mapping(target = "endTime", expression = "java(convertTime(availabilityRule.endEpoch, schedule.timezoneId))" )
    AvailabilityRuleDTO toAvailabilityRuleDTO(AvailabilityRule
                                                availabilityRule, Schedule schedule);


    @Named("time")
    protected ZonedDateTime convertTime(long epoch, String timezoneId) {
        Instant instant = Instant.ofEpochMilli(epoch);
        ZonedDateTime time = LocalDateTime.from(instant).atZone(timezoneId);
        return time;
    }

Complete mapper including schedule


@Mapper
public abstract class ScheduleMapper {
    @Mapping(target = "timezone", source = "timezoneId")
    @Mapping(target = "rules", expression = "java(toAvailabilityRuleDTOs(schedule.getRules(), schedule))")
    abstract ScheduleDTO toScheduleDTO(Schedule schedule);

    @Named("rules")
    abstract List<AvailabilityRuleDTO> toAvailabilityRuleDTOs(List<AvailabilityRule> rules, @Context Schedule schedule);

    @Mapping(target = "startTime", expression = "java(convertTime(availabilityRule.startEpoch, schedule.timezoneId))")
    @Mapping(target = "endTime", expression = "java(convertTime(availabilityRule.endEpoch, schedule.timezoneId))")
    abstract AvailabilityRuleDTO toAvailabilityRuleDTO(AvailabilityRule availabilityRule, @Context Schedule schedule);

    @Named("time")
    protected ZonedDateTime convertTime(long epoch, ZoneId timezoneId) {
        Instant instant = Instant.ofEpochMilli(epoch);
        ZonedDateTime time = LocalDateTime.from(instant).atZone(timezoneId);
        return time;
    }

    String map(ZoneId zoneId) {
        return zoneId == null ? null : zoneId.getId();
    }
}
1
Optimizer On

Mapper Interface to map two objects into one will be like:

@Mapper
public interface CalculateTimeMapper{
     @Mapping(source = "schedule.timezoneId", target = "timezone")
     @Mapping(source = "availabilityRule.startEpoch", target = "startTime")
     AvailabilityRuleDTO toAvailabilityRuleDTO(AvailabilityRule
                                            availabilityRule, Schedule schedule);
}

You can use below for reference:

https://www.tutorialspoint.com/tutorial_view.htm?cid=mapstruct&pid=mapstruct_mapping_multiple.htm