使用Ramda将扁平对象数组转换为嵌套对象数组
问题描述
我有如下数据结构:
[{fooId: '1', prop:'bla',prop2: 'bla', barId:'1', prop_1: 'bla', prop2_1: 'bla' }, {fooId: '1', prop:'bla',prop2: 'bla', barId:'2', prop_1: 'bla', prop2_1: 'bla' }, {fooId: '2', prop:'bla',prop2: 'bla', barId:'3', prop_1: 'bla', prop2_1: 'bla' }, {fooId: '2', prop:'bla',prop2: 'bla', barId:'4', prop_1: 'bla', prop2_1: 'bla' }]
需要用Ramda转换为如下结构:
[{fooId: '1', prop:'bla',prop2: 'bla', bars:[ {barId:'1', prop: 'bla', prop2: 'bla'}, {barId:'2', prop: 'bla', prop2: 'bla'}] }, {fooId: '2', prop:'bla',prop2: 'bla', bars:[ {barId:'3', prop: 'bla', prop2: 'bla'}, {barId:'4', prop: 'bla', prop2: 'bla'}] } ]
之前用R.pick实现过类似结构,但现在需要属性重命名,我写的reducer函数出了问题:所有Bar都被放到每个Foo里,而不是对应Foo。代码如下:
const transformQueryResult = r.pipe( r.groupBy( r.prop('fooId') ), r.map(r.reduce((enrichedFoo, fooWithBar) =>{ enrichedFoo.fooId = fooWithBars.barId // 拼写错误:fooWithBars应为fooWithBar enrichedFoo.prop1 = fooWithBars.prop1 // 同样拼写错误,且原始数据里是prop/prop2,不是prop1 enrichedFoo.prop2 = fooWithBars.prop2 enrichedFoo.bars = r.append( {barId: fooWithBars.barId, prop1:fooWithBars.prop1_1, // 目标是prop/prop2,不是prop1 prop2:fooWithBars.prop2_1 }, enrichedFoo.bars || [] ); return enrichedFoo; },{} ) ), r.values )
我是函数式编程新手,用循环很快能解决,但这里卡住了,求帮助。
解决方法
你的代码问题分析
- 拼写错误:reducer里把参数
fooWithBar写成了fooWithBars,导致引用错误,可能意外共享变量或出现未定义问题。 - 属性名不匹配:原始数据中Foo的属性是
prop/prop2,你代码里写的是prop1/prop2;目标结构里Bar的属性是prop/prop2,你代码里写的是prop1/prop2,完全不符合需求。 - reducer初始化逻辑粗糙:虽然用
groupBy按fooId分了组,但reducer初始化空对象后重复赋值,不如直接基于分组内第一个元素构建Foo基础属性,避免冗余操作。
正确的Ramda实现方案
拆分步骤让逻辑更清晰:
const R = require('ramda'); // 提取单个Foo的基础属性(排除Bar相关字段) const getFooBase = R.pick(['fooId', 'prop', 'prop2']); // 把原始条目转换为目标Bar对象(完成属性重命名) const toBar = R.converge(R.merge, [ R.pick(['barId']), R.pipe( R.pick(['prop_1', 'prop2_1']), R.renameKeys({ prop_1: 'prop', prop2_1: 'prop2' }) ) ]); // 处理同一fooId的分组数据 const processGroup = (items) => { const fooBase = getFooBase(R.head(items)); const bars = R.map(toBar, items); return R.assoc('bars', bars, fooBase); }; // 整体转换管道 const transformQueryResult = R.pipe( R.groupBy(R.prop('fooId')), R.map(processGroup), R.values );
代码解释
getFooBase:用R.pick提取Foo的核心属性,同一分组内的Foo属性完全一致,取第一个元素即可。toBar:通过R.converge合并两部分数据:一是原始的barId,二是把prop_1/prop2_1重命名为prop/prop2后的属性,最终得到目标Bar结构。processGroup:对每个分组,先构建Foo基础对象,再把分组内所有条目转成Bar数组,最后用R.assoc把bars数组挂载到Foo对象上。- 整体管道:先按
fooId分组,再逐个处理分组生成目标结构,最后用R.values把分组对象转成数组输出。
简化紧凑版本
如果想让代码更凝练,可以把逻辑合并到管道中:
const transformQueryResult = R.pipe( R.groupBy(R.prop('fooId')), R.map((group) => ({ ...R.pick(['fooId', 'prop', 'prop2'], R.head(group)), bars: R.map( R.pipe( R.converge(R.merge, [ R.pick(['barId']), R.pipe( R.pick(['prop_1', 'prop2_1']), R.renameKeys({ prop_1: 'prop', prop2_1: 'prop2' }) ) ]) ), group ) })), R.values );
这样就能得到符合需求的结构,每个Foo只会对应自己的Bar数组。
内容的提问来源于stack exchange,提问作者Whateverdev
相关产品推荐
相关产品推荐

