简体   繁体   中英

Transform array of objects using lodash or ramda

This is the data structure that I got after doing query database.

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

I want to transform it into:

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

That data structure is needed in order to build chart using react-chartkick .

Can anyone give me suggestion to solve this? (Especially with lodash or ramda)

I would use following approach based on reduce and find (ES6) methods:

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;
}, []);

If you don't want ES6, both methods have lodash versions: reduce , find .

You can accumulate an object with 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) 

This approach avoids using Array#find() which is O(n) time complexity and uses name in acc to check for existence and acc[name] for access, which are both O(1) time complexity, making this approach O(n) overall.

In contrast, the other answer is O(n 2 ) time complexity overall.

For a solution using lodash, you can use its _.map , _.groupBy , and _.merge methods to accomplish this.

I find it's easier to follow using the _.chain style, but you can easily convert it to a normal set of nested calls if you like.

 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's 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 will get the result into the shape you want.

 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM