
本文详细阐述了如何在groovy中将扁平化的数据列表根据共同的键进行分组,并将其重构为具有清晰父子层级关系的嵌套结构。通过利用groovy强大的`groupby`和`collect`方法,本教程提供了一种高效且灵活的数据转换方案,适用于需要对数据进行聚合和结构化展示的场景,从而提升数据的可读性和可用性。
在数据处理和分析中,我们经常会遇到需要将一系列扁平化记录根据某个共同属性进行分组,并将其组织成更具结构化的父子关系。例如,您可能有一个包含多条保险或健康记录的列表,希望根据“险种类型”将它们归类,并将每种险种下的所有相关记录作为其“子项”进行嵌套。Groovy作为一种动态语言,提供了简洁而强大的集合操作方法,能够高效地实现这种数据重构。
初始数据结构
假设我们有以下列表,其中每个元素都是一个Map,代表一条记录:
def fakeList = [
[coverageType: 'health', amount: 9, expireDate: 2020],
[coverageType: 'insurance', amount: 10, expireDate: 2020],
[coverageType: 'health', amount: 9, expireDate: 2021],
]我们的目标是将这些记录按照coverageType字段进行分组,并生成一个包含parent(父级类型)和children(子级记录列表)的嵌套结构。
期望的输出结构
我们希望得到的最终数据结构如下所示:
[
[
parent: 'health',
children: [
[
coverageType: 'health',
amount: '9',
expireDate: '2020'
],
[
coverageType: 'health',
amount: '9',
expireDate: '2021'
],
]
],
[
parent: 'insurance',
children: [
[
coverageType: 'insurance',
amount: '10', // 注意这里是10,不是9
expireDate: '2020'
]
]
],
]或者,如果子项不需要保留原始键,也可以简化为:
[
[
parent: 'health',
children: [
['health', '9', '2020'],
['health', '9', '2021'],
]
],
[
parent: 'insurance',
children: [
['insurance', '10', '2020']
]
],
]本教程将重点实现第一种更详细的嵌套结构。
Groovy解决方案:利用 groupBy 和 collect
Groovy的groupBy方法是实现数据分组的关键,它能够根据指定的闭包(closure)将列表中的元素分组到一个Map中,其中键是闭包的返回值,值是对应分组的元素列表。接着,我们可以使用collect方法遍历这个分组后的Map,将其转换为我们期望的父子结构列表。
以下是实现上述目标的Groovy代码:
def fakeList = [
[coverageType: 'health', amount: 9, expireDate: 2020],
[coverageType: 'insurance', amount: 10, expireDate: 2020],
[coverageType: 'health', amount: 9, expireDate: 2021],
]
// 步骤1: 使用 groupBy 将列表按 coverageType 分组
def groupedData = fakeList.groupBy { it.coverageType }
/*
groupedData 的中间结果大致如下:
[
health: [
[coverageType: 'health', amount: 9, expireDate: 2020],
[coverageType: 'health', amount: 9, expireDate: 2021]
],
insurance: [
[coverageType: 'insurance', amount: 10, expireDate: 2020]
]
]
*/
// 步骤2: 使用 collect 转换分组后的数据为期望的父子结构
def resultList = groupedData.collect { coverageType, items ->
def parentChildMap = [:]
parentChildMap.parent = coverageType // 设置父级类型
parentChildMap.children = items.collect { item ->
// 为每个子项创建一个新的Map,并进行必要的类型转换
[
coverageType: item.coverageType,
amount: item.amount as String, // 将数字转换为字符串
expireDate: item.expireDate as String // 将数字转换为字符串
]
}
parentChildMap
}
println resultList代码解析
-
fakeList.groupBy { it.coverageType }:
- groupBy方法接收一个闭包作为参数。
- 闭包{ it.coverageType }指定了分组的依据,即每个Map的coverageType字段。
- groupBy会返回一个Map,其中键是coverageType的值(例如'health'、'insurance'),值是所有具有该coverageType的原始Map组成的列表。
-
groupedData.collect { coverageType, items -> ... }:
- collect方法在这里用于遍历groupBy生成的Map。它会迭代Map的每个条目,其中coverageType是键(例如'health'),items是对应的值(一个列表,包含所有属于该coverageType的原始记录)。
- 对于collect的每一次迭代,我们构建一个新的Map,它将成为最终resultList中的一个元素。
-
def parentChildMap = [:]:
- 在collect的闭包内部,我们初始化一个空的Map来存储当前父子结构的元素。
-
parentChildMap.parent = coverageType:
- 将当前分组的键(即coverageType的值)赋给parent字段。
-
parentChildMap.children = items.collect { item -> ... }:
- 这里再次使用了collect方法,这次是作用于当前分组的items列表。
- 对于items列表中的每个原始记录item,我们创建一个新的Map作为子项。
- [coverageType: item.coverageType, amount: item.amount as String, expireDate: item.expireDate as String]:这里明确地构造了子项的结构,并演示了如何将原始数据中的数字类型(amount和expireDate)转换为字符串类型,以满足特定输出格式的需求。as String是Groovy中进行类型转换的简洁方式。
运行结果
执行上述代码,将得到以下输出,这与我们期望的带有详细子项的父子结构完全一致:
[[parent:health, children:[[coverageType:health, amount:9, expireDate:2020], [coverageType:health, amount:9, expireDate:2021]]], [parent:insurance, children:[[coverageType:insurance, amount:10, expireDate:2020]]]]
为了更清晰地展示,可以对其进行格式化输出:
import groovy.json.JsonOutput println JsonOutput.prettyPrint(JsonOutput.toJson(resultList))
输出:
[
{
"parent": "health",
"children": [
{
"coverageType": "health",
"amount": "9",
"expireDate": "2020"
},
{
"coverageType": "health",
"amount": "9",
"expireDate": "2021"
}
]
},
{
"parent": "insurance",
"children": [
{
"coverageType": "insurance",
"amount": "10",
"expireDate": "2020"
}
]
}
]注意事项与总结
- groupBy的强大之处:groupBy是Groovy集合操作中非常实用的一个方法,它能以任意逻辑对集合进行分类。闭包的返回值决定了分组的键。
- collect的灵活性:collect(或map)方法用于将集合中的每个元素转换为另一个元素,并返回一个新的集合。它在数据转换和重塑中扮演着核心角色。
- 数据类型转换:在构建子项时,我们使用了as String进行类型转换。这在处理不同系统间数据交互(例如JSON序列化)时非常常见,确保数据格式的一致性。
- 不可变性:Groovy的集合方法(如groupBy和collect)通常返回新的集合,而不会修改原始集合,这有助于保持代码的纯净性和可预测性。
- 性能考量:对于非常大的数据集,虽然groupBy和collect非常方便,但在极端性能敏感的场景下,可能需要考虑更底层的迭代或流式处理方式。然而,对于大多数常见的数据转换任务,它们的性能是完全足够的。
通过本教程,您应该已经掌握了在Groovy中利用groupBy和collect方法将扁平数据重构为父子层级结构的技巧。这种模式在数据聚合、报表生成以及API数据准备等场景中都非常有用,极大地简化了复杂的数据转换逻辑。










