Search code examples
javascriptarraysunderscore.jslodashreduce

javascript reduce over multiple keys of objects in an array


I have have map of Object of the following type (example data below)

{[date:string]:Array<{[type:string]:{amount:number, price:number}}>}

And I need to reduce the array to a single object of amount sum and price averaged

I have read a couple of post on how to reduce data over an object like

How to map and reduce over an array of objects?

Javascript reduce on array of objects

In my case the "type" is a ENUM of ["PV","BHKW","FALLBACK"] but it could be extended. Apart from writing declarative for in loops, i can't figure out how to use array.reduce to accomplish this more elegantly.

Any suggestions?

{
    "2018-08-01T11:00:00+02:00": [
        {
            "BHKW": {
                "amount": 131,
                "price": 85
            },
            "FALLBACK": {
                "amount": 84,
                "price": 1
            }
        },
        {
            "BHKW": {
                "amount": 307,
                "price": 58
            },
            "PV": {
                "amount": 4,
                "price": 60
            }
        }
    ],
    "2018-08-01T12:00:00+02:00": [
        {
            "BHKW": {
                "amount": 288,
                "price": 59
            },
            "PV": {
                "amount": 742,
                "price": 73
            }
        },
        {
            "BHKW": {
                "amount": 250,
                "price": 49
            },
            "PV": {
                "amount": 507,
                "price": 98
            }
        },
        {
            "PV": {
                "amount": 368,
                "price": 22
            },
            "BHKW": {
                "amount": 357,
                "price": 73
            }
        },
        {
            "FALLBACK": {
                "amount": 135,
                "price": 62
            },
            "BHKW": {
                "amount": 129,
                "price": 93
            }
        }
    ],

Solution

  • You can use lodash#mapValues to transform each value of the object data object. To combine all objects with the same key values, you can use lodash#mergeWith with the callback testing if the keys being transformed will be evaluated.

    const result = _.mapValues(data, collection => 
      _.mergeWith({}, ...collection, (a = 0, b = 0, key) =>
        ['amount', 'price'].includes(key)
          ? a + b
          : void 0 // is equivalent to undefined to retain current value
      ));
    

    const data = {
        "2018-08-01T11:00:00+02:00": [
            {
                "BHKW": {
                    "amount": 131,
                    "price": 85
                },
                "FALLBACK": {
                    "amount": 84,
                    "price": 1
                }
            },
            {
                "BHKW": {
                    "amount": 307,
                    "price": 58
                },
                "PV": {
                    "amount": 4,
                    "price": 60
                }
            }
        ],
        "2018-08-01T12:00:00+02:00": [
            {
                "BHKW": {
                    "amount": 288,
                    "price": 59
                },
                "PV": {
                    "amount": 742,
                    "price": 73
                }
            },
            {
                "BHKW": {
                    "amount": 250,
                    "price": 49
                },
                "PV": {
                    "amount": 507,
                    "price": 98
                }
            },
            {
                "PV": {
                    "amount": 368,
                    "price": 22
                },
                "BHKW": {
                    "amount": 357,
                    "price": 73
                }
            },
            {
                "FALLBACK": {
                    "amount": 135,
                    "price": 62
                },
                "BHKW": {
                    "amount": 129,
                    "price": 93
                }
            }
        ]
    };
    
    const result = _.mapValues(data, collection => 
      _.mergeWith({}, ...collection, (a = 0, b = 0, key) =>
        ['amount', 'price'].includes(key)
          ? a + b
          : void 0 // is equivalent to undefined to retain current value
      ));
      
    console.log(result);
    .as-console-wrapper{min-height:100%;top:0}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>