Search code examples
javamapstructobject-object-mapping

Map multiple sources from multiple objects to one target


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);

Solution

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