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

将DTO对象列表转换为嵌套有序映射及有序列表

DTO转嵌套映射的排序与顺序保留问题

需求说明

需要将一组DTO转换为深度嵌套的映射结构:

源DTO格式

[
    Dto(validationType=BASIC, source=BASE, address=A4, error=Client is a required field.),
    Dto(validationType=BASIC, source=BASE, address=A4, error=Client must be one of 'I', 'G' or 'W'.),
    Dto(validationType=BASIC, source=BASE, address=B5, error=Individuals require First and Last Names.),
    Dto(validationType=BASIC, source=BASE, address=D6, error=Individuals require First and Last Names.),
    Dto(validationType=BASIC, source=BASE, address=J7, error=First is a required field.),
    Dto(validationType=BASIC, source=BASE, address=L8, error=Last is a required field.),
    Dto(validationType=BASIC, source=BASE, address=M9, error=Name is a required field.),
    Dto(validationType=BASIC, source=BASE, address=N10, error=Status is a required field.),
    Dto(validationType=BASIC, source=BASE, address=N10, error=Status must be one of 'A' or 'P'.),
    Dto(validationType=BASIC, source=BASE, address=F16, error=Groups require a Group Name.)
]

目标映射格式

{
    BASIC = {
        BASE = {
            A4 = [ "Client is a required field.", "Client must be one of 'I', 'G' or 'W'." ],
            B5 = [ "Individuals require First and Last Names." ],
            D6 = [ "Individuals require First and Last Names." ],
            J7 = [ "First is a required field." ],
            L8 = [ "Last is a required field." ],
            M9 = [ "Name is a required field." ],
            N10 = [ "Status is a required field.", "Status must be one of 'A' or 'P'." ],
            F16 = [ "Groups require a Group Name." ]
        }
    }
}

现有代码问题

当前使用的转换代码存在两个问题:

final Map<String, Map<String, Map<String, ArrayList<String>>>> collect =
    dtoList.stream()
        .collect(
            Collectors.groupingBy(
                Dto::getValidationType,
                Collectors.groupingBy(
                    Dto::getSource,
                    Collectors.groupingBy(
                        Dto::getAddress,
                        Collectors.mapping(
                            Dto::getError,
                            Collectors.collectingAndThen(
                                Collectors.toSet(),
                                ArrayList::new))))));
  1. 地址键未排序:BASE下的A4、B5等键是无序的,默认groupingBy使用HashMap,不保证顺序。
  2. 错误列表顺序丢失:使用Collectors.toSet()会打乱原始DTO中的错误顺序,因为Set不保留元素顺序。

解决方案

关键修改点

  • 用TreeMap替代HashMap:TreeMap会按照键的自然顺序排序,满足地址键排序需求。
  • 直接收集到ArrayList:跳过Set,直接将错误信息按原始顺序收集到列表中。

修改后的代码

final Map<String, Map<String, Map<String, List<String>>>> collect =
    dtoList.stream()
        .collect(
            // 第一层:按validationType分组,用TreeMap排序
            Collectors.groupingBy(
                Dto::getValidationType,
                TreeMap::new,
                // 第二层:按source分组,用TreeMap排序
                Collectors.groupingBy(
                    Dto::getSource,
                    TreeMap::new,
                    // 第三层:按address分组,用TreeMap排序
                    Collectors.groupingBy(
                        Dto::getAddress,
                        TreeMap::new,
                        // 直接收集错误到ArrayList,保留原始顺序
                        Collectors.mapping(
                            Dto::getError,
                            Collectors.toCollection(ArrayList::new))
                    )
                )
            )
        );

代码说明

  1. 排序处理:每一层groupingBy都指定TreeMap::new作为Map的实现,这样validationType、source、address的键都会按照自然字符串顺序排列。
  2. 顺序保留:内层使用Collectors.toCollection(ArrayList::new)直接将错误信息按DTO的原始顺序收集到列表中,不再经过无序的Set,完全保留源数据的顺序。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:15:05