使用 lodash 或 ramda 转换对象数组

Transform array of objects using lodash or ramda

这是我查询数据库得到的数据结构

[
    { name: 'xx1', date: '2017-09-03', value: 49 },
    { name: 'xx2', date: '2017-10-23', value: 67 },
    { name: 'xx2', date: '2017-12-01', value: 70 },
    ...
]

我想把它改造成:

[
    { name: 'xx1', data: { '2017-09-03': 49 } },
    { name: 'xx2', data: { '2017-10-23': 67, '2017-12-01': 70 } },
    ...
]

需要该数据结构才能使用 react-chartkick 构建图表。

谁能给我建议来解决这个问题? (尤其是 lodash 或 ramda)

我会使用以下基于 reducefind (ES6) 方法的方法:

const data = [
  { name: 'xx1', date: '2017-09-03', value: 49 },
  { name: 'xx2', date: '2017-10-23', value: 67 },
  { name: 'xx2', date: '2017-12-01', value: 70 }
];

const result = data.reduce((acc, item) => {
  const found = acc.find(a => a.name === item.name);
  if (found) {
     found.data[item.date] = item.value;
  } else {
    acc.push({
      name: item.name,
      data: {
        [item.date]: item.value
      }
    });
  }
  return acc;
}, []);

如果你不想要 ES6,两种方法都有 lodash 版本:reduce, find.

你可以累积一个对象 Array#reduce() and return its Object.values():

const data = [
  { name: 'xx1', date: '2017-09-03', value: 49 },
  { name: 'xx2', date: '2017-10-23', value: 67 },
  { name: 'xx2', date: '2017-12-01', value: 70 }
]
const map = data.reduce((acc, { name, date, value }) => {
  if (!(name in acc)) {
    acc[name] = { name, data: {} }
  }

  acc[name].data[date] = value

  return acc
}, {})
const result = Object.values(map)

console.log(result)

这种方法避免了使用 Array#find() 的 O(n) 时间复杂度,使用 name in acc 来检查是否存在,使用 acc[name] 来访问,两者都是 O(1) 时间复杂性,使这种方法整体复杂度为 O(n)。

相比之下,总体上是O(n2)时间复杂度。

对于使用 lodash 的解决方案,您可以使用其 _.map_.groupBy_.merge 方法来完成此操作。

我发现使用 _.chain 样式更容易遵循,但如果您愿意,您可以轻松地将其转换为一组普通的嵌套调用。

const data = [
    { name: 'xx1', date: '2017-09-03', value: 49 },
    { name: 'xx2', date: '2017-10-23', value: 67 },
    { name: 'xx2', date: '2017-12-01', value: 70 },
]

const result = _.chain(data)
  .map(val => ({name: val.name, data: {[val.date]: val.value}})) // convert objects to desired format
  .groupBy("name")                                               // group objects by name
  .map(v => _.merge(...v))                                       // merge objects in each group
  .value();                                                      // done!
  
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>

Ramda 的 R.reduceBy or R.groupBy can help you here. In the example below R.reduceBy groups the objects in the array by their name property and then combines the grouped elements by building up the new object with R.assoc. The remaining combination of R.toPairs, R.map and R.zipObj 会将结果变成你想要的形状。

const fn = R.pipe(
  R.reduceBy((res, {date, value}) => R.assoc(date, value, res), {}, R.prop('name')),
  R.toPairs,
  R.map(R.zipObj(['name', 'data']))
)

const data = [
    { name: 'xx1', date: '2017-09-03', value: 49 },
    { name: 'xx2', date: '2017-10-23', value: 67 },
    { name: 'xx2', date: '2017-12-01', value: 70 }
]

console.log(fn(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>