You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用MapStruct避免为嵌套类编写映射器并处理日期转换?

问题描述

我有如下源类与目标类:

源类

class UserHistorySource {
   private DetailsSource details;
}
class DetailsSource {
   private List<BalanceSource> balance;
   private List<RewardsSource> rewards;
   private List<LogsSource> logs;
   // 其他字段
}

class BalanceSource {
private String userId;
private OffsetDateTime datetime;
private String amount;
}

class RewardsSource {
private String userId;
private OffsetDateTime datetime;
private String amount;
}

class LogsSource {
private String userId;
private OffsetDateTime datetime;
private String amount;
}

目标类

class UserHistoryTarget {
  private DetailsTarget details;
}

class DetailsTarget {
  private List<BalanceTarget> balance;
  private List<RewardsTarget> rewards;
  private List<LogsTarget> logs;
  // 其他字段
}

class BalanceTarget {
  private String userId;
  private String datetime;
  private String amount;
}

class RewardsTarget {
  private String userId;
  private String datetime;
  private String amount;
}

class LogsTarget {
  private String userId;
  private String datetime;
  private String amount;
}

目标类中的datetime字段为String类型,而源类中的datetime为OffsetDateTime类型。原本可以为Details、Balance、Rewards和Logs分别编写独立的映射器文件,并实现默认方法来完成OffsetDateTime到String的转换,但我希望避免为每个类编写映射器,请问是否有更优的解决方案?


最优解决方案

以下基于主流对象映射框架或通用工具,提供无需重复编写映射器的方案:

方案1:MapStruct全局类型转换器

MapStruct支持定义全局转换规则,一次编写即可覆盖所有涉及OffsetDateTime转String的映射场景:

  1. 定义全局转换器类:
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.Named;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface GlobalDateMapper {
    DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

    @Named("offsetDateTimeToString")
    default String offsetDateTimeToString(OffsetDateTime dateTime) {
        return dateTime != null ? dateTime.format(FORMATTER) : null;
    }
}
  1. 编写顶层映射器并引入全局转换器:
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;

@Mapper(
    componentModel = MappingConstants.ComponentModel.SPRING,
    uses = GlobalDateMapper.class, // 关联全局转换器
    unmappedTargetPolicy = ReportingPolicy.IGNORE
)
public interface UserHistoryMapper {
    UserHistoryTarget map(UserHistorySource source);
}

MapStruct会自动识别所有嵌套对象、集合中的OffsetDateTime字段,自动套用全局转换逻辑,无需为每个子类单独编写映射器。

方案2:ModelMapper全局类型转换器

ModelMapper可注册全局类型转换器,配置一次后自动递归处理所有嵌套对象的类型转换:

  1. 配置ModelMapper并注册转换器:
import org.modelmapper.ModelMapper;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class ModelMapperConfig {
    public static ModelMapper getModelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        
        // 注册OffsetDateTime转String的转换规则
        modelMapper.addConverter(context -> {
            OffsetDateTime source = context.getSource();
            return source != null ? source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) : null;
        }, OffsetDateTime.class, String.class);
        
        return modelMapper;
    }
}
  1. 使用时直接调用映射:
ModelMapper modelMapper = ModelMapperConfig.getModelMapper();
UserHistoryTarget target = modelMapper.map(userHistorySource, UserHistoryTarget.class);

框架会自动处理所有层级的对象复制与类型转换,无需编写任何子类映射规则。

方案3:通用工具类+BeanUtils(无框架依赖)

如果不想引入第三方框架,可结合Apache Commons BeanUtils和自定义转换工具实现:

  1. 编写日期转换工具类:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class DateConvertUtil {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

    public static String offsetDateTimeToString(OffsetDateTime dateTime) {
        return dateTime != null ? dateTime.format(FORMATTER) : null;
    }
}
  1. 实现对象转换逻辑:
import org.apache.commons.beanutils.BeanUtils;
import java.util.stream.Collectors;

public class ObjectConverter {
    public static UserHistoryTarget convert(UserHistorySource source) throws Exception {
        UserHistoryTarget target = new UserHistoryTarget();
        BeanUtils.copyProperties(target, source);
        
        DetailsTarget detailsTarget = new DetailsTarget();
        BeanUtils.copyProperties(detailsTarget, source.getDetails());
        
        // 批量转换Balance列表
        detailsTarget.setBalance(source.getDetails().getBalance().stream()
            .map(balanceSource -> {
                BalanceTarget balanceTarget = new BalanceTarget();
                try {
                    BeanUtils.copyProperties(balanceTarget, balanceSource);
                    balanceTarget.setDatetime(DateConvertUtil.offsetDateTimeToString(balanceSource.getDatetime()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return balanceTarget;
            }).collect(Collectors.toList()));
        
        // 同理转换Rewards和Logs列表
        detailsTarget.setRewards(source.getDetails().getRewards().stream()
            .map(rewardsSource -> {
                RewardsTarget rewardsTarget = new RewardsTarget();
                try {
                    BeanUtils.copyProperties(rewardsTarget, rewardsSource);
                    rewardsTarget.setDatetime(DateConvertUtil.offsetDateTimeToString(rewardsSource.getDatetime()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return rewardsTarget;
            }).collect(Collectors.toList()));
        
        detailsTarget.setLogs(source.getDetails().getLogs().stream()
            .map(logsSource -> {
                LogsTarget logsTarget = new LogsTarget();
                try {
                    BeanUtils.copyProperties(logsTarget, logsSource);
                    logsTarget.setDatetime(DateConvertUtil.offsetDateTimeToString(logsSource.getDatetime()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return logsTarget;
            }).collect(Collectors.toList()));
        
        target.setDetails(detailsTarget);
        return target;
    }
}

该方案无需第三方框架,但需手动处理集合转换,适合字段较少的简单场景。


内容的提问来源于stack exchange,提问作者user641887

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:36:02