Nard Dog
Nard Dog

Reputation: 916

Flatten an array of objects and get unique keys and values by a repeated date with Lodash?

I've been playing around with Lodash and not getting close to a solution that doesn't involve a lot of extra looping and overhead.

data: [
    {
      name: "FirstResult", values: [
        {
          value: { NameCount: 1, OtherCount: 1 },
          date: 2019-05-15T07:00:00+0000
        },
        {
          value: { NameCount: 1 },
          date: 2019-05-16T07:00:00+0000
        }
      ]
    },
    {
      name: "SecondResult",
      values: [
        {
          value: { NameCount: 1 },
          date: 2019-05-15T07:00:00+0000
        },
        {
          value: { BuyCount: 2, SellCount: 1 },
          date: 2019-05-16T07:00:00+0000
        }
      ]
    }
  ]

I'd like to flatten this and have it combined and aggregated by using the date as the key returning some configuration like:

[ 
  { date: 2019-05-15T07:00:00+0000, values: { NameCount: 2, OtherCount: 1 } },
  { date: 2019-05-16T07:00:00+0000, values: { NameCount: 1, BuyCount: 2, SellCount: 1 } }
]

Or even just a flat object array is fine like:

[ 
  { date: 2019-05-15T07:00:00+0000, NameCount: 2, OtherCount: 1 },
  { date: 2019-05-16T07:00:00+0000, NameCount: 1, BuyCount: 2, SellCount: 1 }
]

Does anyone have any ideas on how to do this with either a Lodash or Vanilla solution?

Upvotes: 0

Views: 744

Answers (2)

Ori Drori
Ori Drori

Reputation: 191976

You can use a lodash's chain to flatten, group by the date, and then map and merge each group to a single object:

const fn = data => _(data)
  .flatMap('values') // flatten to array of objects
  .groupBy(o => o.date.toISOString()) // group by the iso representation 
  .map(group => { // map the groups by merging, and converting to require format
    const { date, value } = _.mergeWith({}, ...group, (objValue, srcValue) =>
      _.isNumber(objValue) ? objValue + srcValue : undefined // combine numeric values
    )
    
    return {
      date,
      ...value,
    }
  })
  .value()

const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]

const result = fn(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Or you can use _.flow() to generate the function (I'm using lodash/fp here):

const { flow, flatMap, groupBy, map, mergeAllWith, cond, isNumber, add } = _

const fn = flow(
  flatMap('values'), // flatten to array of objects
  groupBy(o => o.date.toISOString()), // group by the iso representation
  map(mergeAllWith(cond([[isNumber, add]]))), // combine numeric values
  map(({ date, value }) => ({ date, ...value })) // format the objects
)

const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]

const result = fn(data)

console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>

Upvotes: 2

Akrion
Akrion

Reputation: 18515

Here is pure ES6 solution based on Array.reduce and Array.forEach for the object keys:

const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]

let result = data.reduce((r, { values }) => {
  values.forEach(({ value, date }) => {
    let keys = Object.keys(value), d = date.toISOString()
    r[d] = r[d] || Object.assign({}, ...keys.map(x => ({ date: d, [x]: 0 })))
    keys.forEach(k => r[d][k] = (r[d][k] || 0) + value[k])
  })
  return r
}, {})

console.log(Object.values(result))

The main idea is to get the keys of the value object white iterating and compose an object with them while grouping by the date. Then last forEach just sums each result object value.

Upvotes: 0

Related Questions