
本文详细介绍了如何在groovy中将扁平化的数据列表根据特定字段进行分组,并将其重构为具有父子关系的嵌套数据结构。通过利用groovy强大的`groupby`和`collect`方法,可以简洁高效地实现数据的分类、转换和格式化,从而满足复杂的数据组织需求。
在数据处理和转换中,我们经常需要将一个包含多个相同类型元素的扁平列表,按照某个共同的属性进行分组,并将其组织成一个更具层次感的结构,例如父子关系。这种结构化处理对于后续的数据展示、分析或API响应格式化至关重要。本文将以一个具体的示例,演示如何使用Groovy的集合操作,优雅地实现这一目标。
初始数据结构
假设我们有一个fakeList,其中包含多个表示不同保险覆盖类型的Map对象:
def fakeList = [
[coverageType: 'health', amount: 9, expireDate: 2020],
[coverageType: 'insurance', amount: 10, expireDate: 2020],
[coverageType: 'health', amount: 9, expireDate: 2021],
]我们的目标是将其转换为一个列表,其中每个元素代表一个唯一的coverageType(作为父级),并包含所有属于该coverageType的原始项(作为子级)。
目标数据结构
期望的输出格式如下所示,每个父级Map包含一个parent键和一个childs(或children)键,childs键的值是一个列表,包含了所有相关的子项:
[
[parent:'health', childs:[
[coverage:health, amount:9, expireDate:2020],
[coverage:health, amount:9, expireDate:2021]
]],
[parent:'insurance', childs:[
[coverage:insurance, amount:10, expireDate:2020]
]]
]Groovy解决方案:groupBy与collect的组合应用
Groovy提供了强大的集合处理方法,其中groupBy和collect是实现此目标的理想工具。
-
groupBy方法:groupBy方法用于根据闭包的返回值对集合元素进行分组。它返回一个Map,其中键是闭包的返回值(即分组依据),值是包含所有属于该组的元素的列表。
def groupedByCoverage = fakeList.groupBy { it.coverageType } // groupedByCoverage 的结果大致如下: // [ // health:[[coverageType:health, amount:9, expireDate:2020], [coverageType:health, amount:9, expireDate:2021]], // insurance:[[coverageType:insurance, amount:10, expireDate:2020]] // ] -
collect方法:collect方法用于将集合中的每个元素转换为一个新的元素,并返回一个包含所有新元素的列表。在这里,我们将对groupBy的结果(一个Map)使用collect,将每个键值对(coverageType和其对应的列表)转换为我们所需的父子结构Map。
def groupList = fakeList.groupBy { it.coverageType } // 首先按 coverageType 分组 .collect { coverageType, items -> // 对每个分组进行转换 def map = [:] // 创建一个空的Map来构建父级结构 map.'parent' = coverageType // 设置父级名称 map.'childs' = items.collect { item -> // 遍历当前分组的子项,构建子项列表 def childMap = [:] // 为每个子项创建一个Map childMap.'coverage' = coverageType // 添加子项的 coverage 类型 childMap.'amount' = item.amount as String // 转换并添加 amount childMap.'expireDate' = item.expireDate as String // 转换并添加 expireDate childMap // 返回子项Map } map // 返回父级Map }
完整示例代码
将上述步骤整合,完整的Groovy代码如下:
import java.util.stream.Collectors
// 原始数据列表
def fakeList = [
[coverageType: 'health', amount: 9, expireDate: 2020],
[coverageType: 'insurance', amount: 10, expireDate: 2020],
[coverageType: 'health', amount: 9, expireDate: 2021],
]
// 使用 groupBy 和 collect 实现分组和结构化
def groupList = fakeList.groupBy { it.coverageType }
.collect { coverageType, items ->
def parentMap = [:]
parentMap.'parent' = coverageType
parentMap.'childs' = items.collect { item ->
def childMap = [:]
childMap.'coverage' = coverageType
childMap.'amount' = item.amount as String // 注意:这里将数字转换为字符串
childMap.'expireDate' = item.expireDate as String // 注意:这里将数字转换为字符串
childMap
}
parentMap
}
// 打印结果
println groupList运行结果
执行上述代码,将得到以下输出,这正是我们期望的父子结构:
[
[parent:health, childs:[[coverage:health, amount:9, expireDate:2020], [coverage:health, amount:9, expireDate:2021]]],
[parent:insurance, childs:[[coverage:insurance, amount:10, expireDate:2020]]]
]注意事项与总结
- putAll的误用: 在原始问题中,用户尝试使用putAll来添加数组,但putAll通常用于将一个Map的所有键值对复制到另一个Map中,它会覆盖同名的键。对于列表的追加或嵌套,这种方法是不合适的。
- 类型转换: 在示例代码中,amount和expireDate字段被显式地转换为String类型(item.amount as String)。如果你的目标输出要求特定类型,务必进行相应的类型转换。
- Groovy的简洁性: groupBy和collect是Groovy集合操作的强大体现,它们允许用非常简洁和富有表达力的方式处理复杂的数据转换逻辑,避免了冗长的循环和条件判断。
- 灵活性: collect闭包内部可以根据需求进行任意复杂的转换逻辑。你可以选择性地包含或排除原始项的字段,甚至添加新的计算字段。
通过掌握groupBy和collect的组合使用,你将能够高效地在Groovy中处理各种数据分组和结构化任务,从而编写出更健壮、更易读的代码。










