7

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);
duplex143
  • 619
  • 2
  • 9
  • 25

2 Answers2

14

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();
    }
}
Ben Zegveld
  • 1,186
  • 2
  • 6
  • The code you've mentioned is converting `AvailabilityRule` to `AvailabilityRuleDTO` properly. How do I convert `Schedule` to `Schedule DTO`? ``` @Mapping(source = "timeZone", target = "timeZone", qualifiedByName = "zoneIdToString") ScheduleDTO toScheduleDTO(Schedule schedule); ``` isn't working fine. – duplex143 Feb 02 '22 at 07:05
  • I'm not able to use `toAvailabilityRuleDTO ` in conjunction with `toScheduleDTO`. My goal is to convert Schedule to ScheduleDTO. – duplex143 Feb 02 '22 at 07:19
  • 1
    I've added an example that contains the complete mapper that also maps schedule. – Ben Zegveld Feb 02 '22 at 07:57
  • Thanks. This is what I'm looking for. Is there any way `expression = "java(toAvailabilityRuleDTOs(schedule.getRules(), schedule))"` can be avoided and replaced with `qualifiedByName` ? – duplex143 Feb 02 '22 at 15:36
  • 1
    I do not think that is possible at the moment, but create an issue at https://github.com/mapstruct/mapstruct/issues about it. Perhaps it is possible and I do not know about it, or otherwise it might be possible to add it in the future. – Ben Zegveld Feb 02 '22 at 16:42
-1

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

Optimizer
  • 116
  • 1
  • 1
  • 5
  • 1
    This is NOT what is being asked: what we are looking for is a way to COMBINE properties from 2 sources to calculate 1 property in the target bean. And what you are saying here is how to get the value of one target from one source property and another target property from a second source property; that's straightforward. – WinterBoot Jan 17 '23 at 11:24