You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Lodash/Underscore或JS数组函数实现多层数据分组?

问题需求

我们有一组来自TypeORM getRawMany 方法(或Sequelize原生查询)的扁平化对象数组,输入数据如下:

const rawData = [
  { "contents_id": "0e362978-ec0a-4851-9f58-e2ad23ec9ee9", "contents_title": "Content mock 1", "contents_created_at": "2020-11-28T23:42:23.717Z", "campaigns_id": "09e34ed1-0dfb-4723-abfb-eec21fc064bc", "campaigns_title": "First campaign title", "product_title": "product title one", "picture_upload": "image_text1.jpg", "views": "1" },
  { "contents_id": "0e362978-ec0a-4851-9f58-e2ad23ec9ee9", "contents_title": "Content mock 1", "contents_created_at": "2020-11-28T23:42:23.717Z", "campaigns_id": "09e34ed1-0dfb-4723-abfb-eec21fc064bc", "campaigns_title": "First campaign title", "product_title": "product title two", "picture_upload": "image_text2.jpg", "views": "1" },
  { "contents_id": "0e362978-ec0a-4851-9f58-e2ad23ec9ee9", "contents_title": "Content mock 1", "contents_created_at": "2020-11-28T23:42:23.717Z", "campaigns_id": "09e34ed1-0dfb-4723-abfb-eec21fc064bc", "campaigns_title": "First campaign title", "product_title": null, "picture_upload": null, "views": "1" },
  { "contents_id": "5b0bc1f1-62ad-4f3e-b2f6-c117a7b46e21", "contents_title": "Content mock 3", "contents_created_at": "2020-11-29T10:11:45.563Z", "campaigns_id": null, "campaigns_title": null, "product_title": null, "picture_upload": null, "views": null },
  { "contents_id": "6f211b84-4f76-4da7-9cc7-ab5fe9cc9807", "contents_title": "Content mock 2", "contents_created_at": "2020-11-29T10:07:55.090Z", "campaigns_id": null, "campaigns_title": null, "product_title": null, "picture_upload": null, "views": null },
  { "contents_id": "d1658dd6-2dd0-4b8b-b9b2-b3fcd1d2310a", "contents_title": "Content mock 1", "contents_created_at": "2020-11-29T10:07:55.090Z", "campaigns_id": null, "campaigns_title": null, "product_title": null, "picture_upload": null, "views": null }
];

需要将数据按contents进行分组,每个内容条目下嵌套非空的products数组和campaigns数组,最终得到如下格式的结果:

[
  { 
    "contents_id": "0e362978-ec0a-4851-9f58-e2ad23ec9ee9", 
    "contents_title": "Content mock 1", 
    "views": 1, 
    "products": [{ "product_title": "product title two", "picture_upload": "image_text2.jpg"}], 
    "campaigns": [{ "campaigns_id": "09e34ed1-0dfb-4723-abfb-eec21fc064bc", "campaigns_title": "First campaign title"}] 
  }, 
  { 
    "contents_id": "6f211b84-4f76-4da7-9cc7-ab5fe9cc9807", 
    "contents_title": "Content mock 2", 
    "products": [], 
    "campaigns": [] 
  }, 
  { 
    "contents_id": "5b0bc1f1-62ad-4f3e-b2f6-c117a7b46e21", 
    "contents_title": "Content mock 3", 
    "products": [], 
    "campaigns": [] 
  }
]

原生JavaScript实现

这是最轻量化的方案,利用数组reduce方法完成分组,同时处理嵌套数组的空值过滤与去重,无需依赖任何第三方库:

const result = Object.values(rawData.reduce((acc, item) => {
  // 以contents_id作为分组唯一标识
  const key = item.contents_id;
  
  // 初始化当前内容的基础结构
  if (!acc[key]) {
    acc[key] = {
      contents_id: item.contents_id,
      contents_title: item.contents_title,
      views: item.views ? Number(item.views) : undefined,
      products: [],
      campaigns: []
    };
  }

  // 处理产品:仅保留非空且未重复的条目
  if (item.product_title) {
    const isDuplicate = acc[key].products.some(p => p.product_title === item.product_title);
    if (!isDuplicate) {
      acc[key].products.push({
        product_title: item.product_title,
        picture_upload: item.picture_upload
      });
    }
  }

  // 处理活动:仅保留非空且未重复的条目
  if (item.campaigns_id) {
    const isDuplicate = acc[key].campaigns.some(c => c.campaigns_id === item.campaigns_id);
    if (!isDuplicate) {
      acc[key].campaigns.push({
        campaigns_id: item.campaigns_id,
        campaigns_title: item.campaigns_title
      });
    }
  }

  return acc;
}, {}));

Lodash实现

如果项目已经引入Lodash,可以借助它的链式调用能力让代码更简洁易读:

const _ = require('lodash');

const result = _.chain(rawData)
  // 按contents_id分组
  .groupBy('contents_id')
  .map(group => {
    // 提取内容基础信息
    const contentBase = _.pick(group[0], ['contents_id', 'contents_title']);
    contentBase.views = group[0].views ? Number(group[0].views) : undefined;
    
    // 生成去重后的非空产品数组
    contentBase.products = _.chain(group)
      .filter(item => item.product_title)
      .map(item => _.pick(item, ['product_title', 'picture_upload']))
      .uniqBy('product_title')
      .value();
    
    // 生成去重后的非空活动数组
    contentBase.campaigns = _.chain(group)
      .filter(item => item.campaigns_id)
      .map(item => _.pick(item, ['campaigns_id', 'campaigns_title']))
      .uniqBy('campaigns_id')
      .value();
    
    return contentBase;
  })
  .value();

实现说明

  • 两种方案都完成了空值过滤:仅保留有实际内容的产品和活动
  • 都处理了重复数据:同一个内容下的相同产品/活动只保留一次
  • 将字符串类型的views转换为数字类型,与期望输出格式完全匹配

内容的提问来源于stack exchange,提问作者Julien Ambrosio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:46:53