If I have an object like this (or similar):
sales = {
obs1:{
Sales1:{
Region:"North", Value: 200},
Sales2:{
Region:"South", Value:100}},
obs2:{
Sales1:{
Region:"North", Value: 50},
Sales2:{
Region:"South", Value:20}
}
}
How could I aggregate the sum of the property Value
by Region
? Answers could be in pure JavaScript or a library.
The end result should be something similar to this:
totals = {North: 250, South:120}
As others pointed out, there's no built-in JavaScript functions to do that (there are a few high-order functions like map
, but not enough for the task). However, some libraries such as Underscore.js provide many utilities to simplify this kind of task.
var totals = _
.chain(sales) // Wraps up the object in an "underscore object",
// so methods can be chained
// First: "flatten" the sales
.map(function(v) {
return _
.chain(v)
.map(function(v2) {
return v2;
})
.value();
})
.flatten()
// Second: group the sales by region
.groupBy('Region')
// Third: sum the groups and create the object with the totals
.map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return m + x.Value;
}, 0)
};
})
.value(); // Unwraps the "underscore object" back to a plain JS object
Source: this answer at SOpt
This answer assumes the structure of your data is known - contrary to the other answers, which focus on generalizing the structure. Though the code above can be generalized itself, by removing the hardcoded Region
and Value
and varying the nesting level to something other than two and the aggregation function to something other than sum - as long as the leaves contain both a property you want to group by, and a value you want to aggregate.
function aggregate(object, toGroup, toAggregate, fn, val0) {
function deepFlatten(x) {
if ( x[toGroup] !== undefined ) // Leaf
return x;
return _.chain(x)
.map(function(v) { return deepFlatten(v); })
.flatten()
.value();
}
return _.chain(deepFlatten(object))
.groupBy(toGroup)
.map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return fn(m, x[toAggregate]);
}, val0 || 0)
};
})
.value();
}
It's called like this:
function add(a,b) { return a + b; }
var totals = aggregate(sales, "Region", "Value", add);
Another example (finds minimum value by region):
function min(a,b) { return a < b ? a : b; }
var mins = aggregate(sales, "Region", "Value", min, 999999);
Are you looking for something like this (updated based on @Barmar's suggestion):
var totals = {};
function ExtractSales(obj) {
if (obj.Region && obj.Value) {
if (totals[obj.Region]) {
totals[obj.Region] += obj.Value;
} else {
totals[obj.Region] = obj.Value;
}
} else {
for (var p in obj) {
ExtractSales(obj[p]);
}
}
}
ExtractSales(sales);
console.log(totals);
http://jsfiddle.net/punF8/3/
What this will do, for a given root object, is walk down it's properties and try and find something with a Region
and a Value
property. If it finds them, it populates an object with your totals.
With this approach, you don't need to know anything about the nesting of objects. The only thing you need to know is that the objects you are looking for have Region
and Value
properties.
This can be optimized further and include some error checking (hasOwnProperty
, undefined
, circular references, etc), but should give you a basic idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With