原本数据格式:
const list = [{
"estate_id": 12,
"estate_name": "书舍楼",
"building_id": 0,
"building_name": "",
"building_floors": 0
}, {
"estate_id": 12,
"estate_name": "书舍楼",
"building_id": 90,
"building_name": "A20",
"building_floors": 2
}, {
"estate_id": 12,
"estate_name": "书舍楼",
"building_id": 91,
"building_name": "A30",
"building_floors": 3
}, {
"estate_id":11,
"estate_name": "测试楼",
"building_id": 0,
"building_name": "",
"building_floors": 0
}, {
"estate_id": 11,
"estate_name": "测试楼",
"building_id": 89,
"building_name": "1栋",
"building_floors": 5
}]
转换结果格式如下:
const list = [{
estate_name: '书舍楼',
estate_id: 12,
children: [
{
estate_name: 'A20',
estate_id: 90,
children: []
},{
estate_name: 'A30',
estate_id: 91,
children: []
},
],
},
{
estate_name: '测试楼',
estate_id: 11,
children: [
{
estate_name: '1栋',
estate_id: 89,
children: []
},
],
},
}]
每次这类问题都是求转换,但是到底要怎么转换,是否有把规则摸清楚呢?学门语言容易,抄代码更容易,但是要程序设计的重点是分析问题、解决问题的能力和程序设计的思维模式,这东西是需要不断的实践不断的尝试才能锻炼出来的!
正如 @拾肆 分析,这个问题要解决的把列表数据转换为树形数据,一种“数据结构”转换,而不是单纯的“属性结构”转换,我在「从列表生成树 (JavaScript/TypeScript)」一文讲到了根据
parentId
来从列表生成树,从解决思路上来说,有相似的地方,但不完全匹配。这里的问题是通过数据属性的值来判断数据的归属,严格的说是属于“分组”操作。
根据题意,分析得
estate_id
和estate_name
是成对属性,只要 id 一样,name 就一样。它们是第一次(层)分组的关键属性,所以第一次可以按estate_id
分组,也可以按estate_name
分组。由于id
通常用于唯一识别,而且数值计算通常会快一些,所以这里通常会选择按estate_id
分组。但是分组之后“书舍楼”有三条数据,其中有一条
building_name
是空值,对应的building_id
是0
值,所以这条数据看起来比较特殊,可以认为它不包含子节点信息(因为要挂一条没有 name 的子节点也确实不合常理)。为了证实这一猜想,往下观察,发现“测试楼”也存在这样的节点。从结果数据来看,子节点中也确实不存在空 name 的数组,所以这个猜想基本上就得到证实了。不过从结果看,第二层节点仍然拥有
children
数组属性,所以还可以猜想下面有可能存在第三层的数据,只是这里没列出来。不过不管怎么样,这已经不重要了。对于 JavaScript 来说,原生 Array 并未提供分组操作,但是常用库 Lodash 提供了有,可以直接用。不想用第三方库,自己与可以写一个(不要害怕
reduce
,它就是for
+ 聚合数据,下面的代码用一个for
循环加局部变量也是很容易写得出来的):分组之后会得到一个对象,这个对象的属性名是
estate_id
的值,属性值是一个数组,包含了每一个对应estate_id
的节点数据。我们要实际需的不是这个对象,而是将这个对象的属性枚举出来,并加上一些附加信息。枚举对象的 key 可以用Object.keys()
,但是我们还需要值,可以用Object.entries()
,它可以拿到[key, value][]
键值对数组。当然这个枚举结果还不是我们要的结果,这时候需要单纯的一个个进行对象转换。使用 Array 的
.map()
可以做到。转换有两件事要做estate_id
;value 是节点列表,但是这个节点列表里每一个都包含相同的estate_id
和estate_name
,我们只需要拿到一个就好value
去拿,把building_*
属性转换成estate_*
属性就好。但要记得过滤掉building_id
为0
的。到于building_floors
,这个属性实际被丢掉了。整个过程可以写成下面的代码
这个代码的输出会发现“测试楼”在“书舍楼”前面,与原数据顺序不符。原因是 JS 对象的属性,如果 key 是数值(或表示数值的字符串),会按数值顺序排;其它属性按加入的先后顺序排。
所以如果想按原来的顺序,只需要把按
estate_id
分组改为按estate_name
分组就好了。当然还有其他保持顺序的办法,学过数据结构应该会知道,这里就不多说了。
如果想把这个代码写得简单一点 —— 可以把各个逻辑处理合并起来。
这个代码没有 @琴亭夜雨 简洁,不过他那个代码使用了 Lodash,而且会遍历
list
很多次,效率不够高;还有一个问题是没有过滤掉building_id
为会0
的节点,但这算失误,filter
里加个条件就好了。