如何按前缀或键将JSON键分组为新键?求JavaScript高效实现方案
问题:如何高效转换Knex查询结果的嵌套结构?
我用Knex查询后得到的结果是扁平化的对象数组,键名带点号:
const response = [ {id: 1, source: 'mobile', 'ms_payment.id': 111, 'ms_payment.total': 100}, {id: 2, source: 'mobile', 'ms_payment.id': 112, 'ms_payment.total': 210}, // ...更多项 ];而我的REST API需要返回嵌套结构的数组:
const result = [ {id: 1, source: 'mobile', ms_payment: { id: 111, total: 100 }}, {id: 2, source: 'mobile', ms_payment: { id: 112, total: 210 }}, // ...更多项 ];目前我是用
Array.map()手动处理的,但想找更高效的JavaScript算法,求解答。
解答
嘿,这个需求我之前也碰到过!首先得说:你用Array.map()的思路本身没毛病,但确实可以优化得更灵活,或者从源头减少转换成本。我给你分享几个实用的方案:
1. 通用化的map优化版(兼顾效率和灵活性)
如果你还是想在JS层面处理,这个通用版的转换函数比手动逐个赋值更灵活,而且性能和原生map持平:
const flattenToNested = (data) => { return data.map(item => { const nestedItem = {}; // 遍历每个键值对 for (const [key, value] of Object.entries(item)) { if (key.includes('.')) { // 拆分父键和子键 const [parentKey, childKey] = key.split('.'); // 初始化父对象(如果不存在) nestedItem[parentKey] = nestedItem[parentKey] || {}; nestedItem[parentKey][childKey] = value; } else { // 非嵌套键直接赋值 nestedItem[key] = value; } } return nestedItem; }); }; // 使用示例 const result = flattenToNested(response);
这个方法的时间复杂度是O(n*m)(n是数组长度,m是每个对象的键数量),这已经是理论最优了——毕竟每个键都得处理一次。而且它能适配更多嵌套字段的情况,比如以后加个ms_payment.status也不用改代码。
2. 从Knex查询阶段直接生成嵌套结构(性能最优)
其实最高效的方式是避免在JS层面做转换,让Knex配合数据库直接返回嵌套结构。比如如果用PostgreSQL,可以利用它的JSON函数:
const nestedResults = await knex('your_table_name') .select( 'id', 'source', // 用SQL直接构建嵌套的JSON对象 knex.raw(`json_build_object( 'id', ms_payment_id, 'total', ms_payment_total ) AS ms_payment`) ) .then(rows => { // 把数据库返回的JSON字符串解析成对象 return rows.map(row => ({ ...row, ms_payment: JSON.parse(row.ms_payment) })); });
这样查询出来的结果直接就是你要的嵌套格式,省去了JS层面的循环转换,性能会更好——毕竟数据库处理这类结构化数据的效率通常比JS高。
如果是MySQL,也可以用JSON_OBJECT函数来实现类似效果:
knex.raw(`JSON_OBJECT('id', ms_payment_id, 'total', ms_payment_total) AS ms_payment`)
3. 超大数据量下的reduce方案
如果你的数据量特别大(比如几十万条),reduce和map的性能差异几乎可以忽略,但写法上可以这样:
const flattenToNested = (data) => { return data.reduce((acc, item) => { const nestedItem = {}; for (const [key, value] of Object.entries(item)) { if (key.includes('.')) { const [parentKey, childKey] = key.split('.'); nestedItem[parentKey] = { ...(nestedItem[parentKey] || {}), [childKey]: value }; } else { nestedItem[key] = value; } } acc.push(nestedItem); return acc; }, []); };
不过实际测试中,map和reduce在处理数组时的性能表现几乎一致,除非你在极端场景下,否则没必要特意切换。
总结
- 如果数据量不大,通用版的
map方案完全够用,灵活又好维护; - 如果追求极致性能,优先在Knex查询阶段用数据库的JSON函数生成嵌套结构;
- 手动逐个字段赋值的
map(你原来的方法)虽然稍微快一点,但灵活性差,字段变化时要改代码。
内容的提问来源于stack exchange,提问作者Evan Gunawan
相关产品推荐
相关产品推荐

