如何 reduce/transform 一个对象数组以得到每个 属性 的总和(最好是 lodash)
How to reduce/transform an array of objects to result in the aggregate sum of each property (lodash ideally)
我有一组看起来像这样的营养对象:
[
{
calories: {total: 0, fat: 0},
vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
fats: {total: 0},
minerals: {calcium: 0}
},
{
calories: {total: 150, fat: 40},
vitamins: {a: {total: 100}, b6: 30, c: 200},
fats: {total: 3}
},
{
calories: {total: 100, fat: 60},
vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
minerals: {calcium: 20}
}
]
请注意,我添加了一个 0 填充对象,因此没有属性为 null 或未定义;并非所有属性都存在于后续对象上;这是一个非常深的对象(即 obj.vitamins.a.total)
结果应该是
{
calories: {total: 250, fat: 100},
vitamins: {a: {total: 220, retinol: 10}, b6: 30, c: 400},
fats: {total: 3},
minerals: {calcium: 20}
}
我知道 lodash
的 _.reduce()
和 _.transform()
函数,但我不确定如何访问 accumulator
上的属性。 我怎样才能达到预期的结果,最好使用函数式编程和 lodash。
我可以给你一个灵感来自 Monoids 的答案。在不深入研究范畴论的情况下,Monoid 的工作需要两件事
- 表示您的 Monoid 类型 empty(或 neutral)的某个值
- 以及一些将您的类型的两个 Monoid 组合在一起的函数
和幺半群
首先,让我们看看 Sum
幺半群。在 JavaScript 中有无数种方法可以实现这一点,但我们只看这个,这样您就可以大致了解
const Sum = x => ({
constructor: Sum,
value: x,
concat: ({value:y}) => Sum(x + y),
fold: f => f (x)
})
Sum.empty = Sum(0)
Sum(3).concat(Sum(4)).fold(x => console.log(x)) // 7
Sum(3).concat(Sum(4)).concat(Sum(9)).fold(x => console.log(x)) // 16
下一级幺半群
使用 Sum
幺半群,我们可以轻松组合数字,但如果我们的数据更复杂呢?例如,如果我们的数据是一个对象呢?
假设我们有 x
和 y
并希望合理地组合它们
let x = SumObject({ a: Sum(1), b: Sum(2) })
let y = SumObject({ b: Sum(3), c: Sum(4) })
x.concat(y)
// => SumObject({ a: Sum(1), b: Sum(5), c: Sum(4) })
那就太好了!现在让我们实现 SumObject
幺半群 - 抱歉,我真的想不出更好的名字了!
const SumObject = x => ({
constructor: SumObject,
value: x,
concat: ({value:y}) => {
return SumObject(Object.keys(y).reduce((acc, k) => {
if (acc[k] === undefined)
return Object.assign(acc, { [k]: y[k] })
else
return Object.assign(acc, { [k]: <b>acc[k].concat(y[k])</b> })
}, Object.assign({}, x)))
},
fold: f => f(x)
})
SumObject.empty = SumObject({})
我的天啊!那个有点难,但我希望你能看到它。注意 粗体 代码。我们对 SumObject
的实现假设 属性 值 也将是幺半群——这意味着值将具有 .concat
方法。
让我们一起健身吧!
为了让幺半群能够处理您的数据,我们需要将您的原始 Object
和 Number
类型转换为相应的 Monoid 类型。
最后,为了得到原始类型的答案,我们必须从 Monoid 类型返回转换为原始类型。
如果一切顺利,生成的表达式将如下所示
<b>sumToPrimitive</b>(data.map(x =>
<b>primitiveToSum</b>(x)).reduce((acc, x) =>
acc.concat(x), SumObject.empty))
现在让我们制作将原语转换为 Monoid 类型的函数
const primitiveToSum = x => {
switch (x.constructor) {
case Object:
return SumObject(Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: primitiveToSum(x[k]) }), {}))
case Number:
return Sum(x)
default:
throw Error(`unsupported type: ${x}`)
}
}
然后我们将制作一个将 Monoid 转换回原始类型
const sumToPrimitive = x => {
switch (x.constructor) {
case SumObject:
return x.fold(x =>
Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: sumToPrimitive(x[k]) }), {}))
case Sum:
return x.fold(identity)
default:
throw Error(`unsupported type: ${x}`)
}
}
"So that's it?"
好吧,因为您支持 嵌套 对象,它有点混乱,但恐怕这真的是我们能做的最好的事情。如果您愿意将数据限制为平面对象,事情会更清晰。
尽管如此,让我们看看这一切是否正常
const SumObject = x => ({
constructor: SumObject,
value: x,
concat: ({value:y}) => {
return SumObject(Object.keys(y).reduce((acc, k) => {
if (acc[k] === undefined)
return Object.assign(acc, { [k]: y[k] })
else
return Object.assign(acc, { [k]: acc[k].concat(y[k]) })
}, Object.assign({}, x)))
},
fold: f => f(x)
})
SumObject.empty = SumObject({})
const Sum = x => ({
constructor: Sum,
value: x,
concat: ({value:y}) => Sum(x + y),
fold: f => f (x)
})
Sum.empty = Sum(0)
const primitiveToSum = x => {
switch (x.constructor) {
case Object:
return SumObject(Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: primitiveToSum(x[k]) }), {}))
case Number:
return Sum(x)
default:
throw Error(`unsupported type: ${x}`)
}
}
const sumToPrimitive = x => {
switch (x.constructor) {
case SumObject:
return x.fold(x =>
Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: sumToPrimitive(x[k]) }), {}))
case Sum:
return x.fold(identity)
default:
throw Error(`unsupported type: ${x}`)
}
}
const identity = x => x
const data = [
{
calories: {total: 0, fat: 0},
vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
fats: {total: 0},
minerals: {calcium: 0}
},
{
calories: {total: 150, fat: 40},
vitamins: {a: {total: 100}, b6: 30, c: 200},
fats: {total: 3}
},
{
calories: {total: 100, fat: 60},
vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
minerals: {calcium: 20}
}
]
const result = sumToPrimitive(data.map(x =>
primitiveToSum(x)).reduce((acc, x) =>
acc.concat(x), SumObject.empty))
console.log(result)
输出
{ calories: { total: 250, fat: 100 },
vitamins: { a: { total: 220, retinol: 10 }, b6: 30, c: 400 },
fats: { total: 3 },
minerals: { calcium: 20 } }
备注
幺半群是维护泛型、可重用组合术语的一种很好的接口——这就是函数式编程的全部意义所在。但是,由于您的数据嵌套复杂(并且本机 JavaScript 缺少自定义类型),需要您预先进行更多手动编码。
我无法想象 Lodash 解决方案会是什么样子——但我可以告诉你的是,它会抛弃泛型和可重用性 window——尤其是考虑到你的数据结构。
您可以使用 _.mergeWith()
来完成。我正在使用 _.spread()
创建一个 _.mergeWith()
来处理数组而不是单个参数。
var data = [{"calories":{"total":0,"fat":0},"vitamins":{"a":{"total":0,"retinol":0},"b6":0,"c":0},"fats":{"total":0},"minerals":{"calcium":0}},{"calories":{"total":150,"fat":40},"vitamins":{"a":{"total":100},"b6":30,"c":200},"fats":{"total":3}},{"calories":{"total":100,"fat":60},"vitamins":{"a":{"total":120,"retinol":10},"b6":0,"c":200},"minerals":{"calcium":20}}];
var mergeWith = _.spread(_.mergeWith);
function deepMerge(objs) {
var args = [{}].concat(objs, function(objValue, srcValue) {
if(_.isNumber(objValue)) {
return objValue + srcValue;
}
});
return mergeWith(args);
}
var result = deepMerge(data);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
使用spread语法的ES6版本:
var data = [{"calories":{"total":0,"fat":0},"vitamins":{"a":{"total":0,"retinol":0},"b6":0,"c":0},"fats":{"total":0},"minerals":{"calcium":0}},{"calories":{"total":150,"fat":40},"vitamins":{"a":{"total":100},"b6":30,"c":200},"fats":{"total":3}},{"calories":{"total":100,"fat":60},"vitamins":{"a":{"total":120,"retinol":10},"b6":0,"c":200},"minerals":{"calcium":20}}];
function deepMerge(objs) {
return _.mergeWith({}, ...objs, (objValue, srcValue) => {
if (_.isNumber(objValue)) {
return objValue + srcValue;
}
});
}
var result = deepMerge(data);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
根据,我们还可以将定制器提取为外部方法,因此您可以决定非对象值的合并方式。
var data = [{"calories":{"total":0,"fat":0},"vitamins":{"a":{"total":0,"retinol":0},"b6":0,"c":0},"fats":{"total":0},"minerals":{"calcium":0}},{"calories":{"total":150,"fat":40},"vitamins":{"a":{"total":100},"b6":30,"c":200},"fats":{"total":3}},{"calories":{"total":100,"fat":60},"vitamins":{"a":{"total":120,"retinol":10},"b6":0,"c":200},"minerals":{"calcium":20}}];
var mergeWith = _.spread(_.mergeWith);
function customizer(objValue, srcValue) {
if(_.isNumber(objValue)) {
return objValue + srcValue;
}
}
function deepMerge(objs, customizer) {
var args = [{}].concat(objs, function(objValue, srcValue) {
return customizer(objValue, srcValue);
});
return mergeWith(args);
}
var result = deepMerge(data, customizer);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
我更喜欢用 Ramda 以无点方式做这件事,但 OP 在 Lodash 中请求,所以我们开始吧,简单的递归解决方案:
let data = [{calories: {total: 0, fat: 0},vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},fats: {total: 0},minerals: {calcium: 0}},{calories: {total: 150, fat: 40},vitamins: {a: {total: 100}, b6: 30, c: 200},fats: {total: 3}},{calories: {total: 100, fat: 60},vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},minerals: {calcium: 20}}];
const recursor = result => (value, key) => {
if ( _.isNumber(value) ) {
result[key] = ((result[key] || (result[key] = 0)) + value);
return result[key];
} else {
return _.mapValues(value, recursor(result[key] || (result[key] = {}) ));
}
}
const reducer = (result, value, key) => _.mapValues(value, recursor(result));
let final = _.transform(data, reducer, {});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
虽然 already specified the direction of using mergeWith,但他的用法在于创建一个递归调用mergeWith
的递归函数。该解决方案忽略了 mergeWith
本质上是递归合并的事实,其中定制程序只会转换结果值,如果它 returns 不是 undefined
的任何值。此外,另一个问题是 mergeWith
改变了第一个对象参数,这在您想要保留源对象状态的情况下可能并不理想。
以下解决方案使用Function.prototype.apply with mergeWith
instead of wrapping it in a spread,您可以根据自己的喜好决定将其切换为spread
。为了解决源对象突变的问题,我们提供一个空对象作为第一个参数,然后将它与其余的源对象连接起来。最后,我们连接自定义程序函数,如果 value
是一个数字,returns 参数的总和。
var result = _.mergeWith.apply(
null,
[{}].concat(source).concat(function(value, src) {
if(_.isNumber(value)) {
return value + src;
}
})
);
var source = [
{
calories: {total: 0, fat: 0},
vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
fats: {total: 0},
minerals: {calcium: 0}
},
{
calories: {total: 150, fat: 40},
vitamins: {a: {total: 100}, b6: 30, c: 200},
fats: {total: 3}
},
{
calories: {total: 100, fat: 60},
vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
minerals: {calcium: 20}
}
];
var result = _.mergeWith.apply(
null,
[{}].concat(source).concat(function(value, src) {
if(_.isNumber(value)) {
return value + src;
}
})
);
console.log(result);
body>div {
min-height: 100%;
top: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
我有一组看起来像这样的营养对象:
[
{
calories: {total: 0, fat: 0},
vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
fats: {total: 0},
minerals: {calcium: 0}
},
{
calories: {total: 150, fat: 40},
vitamins: {a: {total: 100}, b6: 30, c: 200},
fats: {total: 3}
},
{
calories: {total: 100, fat: 60},
vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
minerals: {calcium: 20}
}
]
请注意,我添加了一个 0 填充对象,因此没有属性为 null 或未定义;并非所有属性都存在于后续对象上;这是一个非常深的对象(即 obj.vitamins.a.total)
结果应该是
{
calories: {total: 250, fat: 100},
vitamins: {a: {total: 220, retinol: 10}, b6: 30, c: 400},
fats: {total: 3},
minerals: {calcium: 20}
}
我知道 lodash
的 _.reduce()
和 _.transform()
函数,但我不确定如何访问 accumulator
上的属性。 我怎样才能达到预期的结果,最好使用函数式编程和 lodash。
我可以给你一个灵感来自 Monoids 的答案。在不深入研究范畴论的情况下,Monoid 的工作需要两件事
- 表示您的 Monoid 类型 empty(或 neutral)的某个值
- 以及一些将您的类型的两个 Monoid 组合在一起的函数
和幺半群
首先,让我们看看 Sum
幺半群。在 JavaScript 中有无数种方法可以实现这一点,但我们只看这个,这样您就可以大致了解
const Sum = x => ({
constructor: Sum,
value: x,
concat: ({value:y}) => Sum(x + y),
fold: f => f (x)
})
Sum.empty = Sum(0)
Sum(3).concat(Sum(4)).fold(x => console.log(x)) // 7
Sum(3).concat(Sum(4)).concat(Sum(9)).fold(x => console.log(x)) // 16
下一级幺半群
使用 Sum
幺半群,我们可以轻松组合数字,但如果我们的数据更复杂呢?例如,如果我们的数据是一个对象呢?
假设我们有 x
和 y
并希望合理地组合它们
let x = SumObject({ a: Sum(1), b: Sum(2) })
let y = SumObject({ b: Sum(3), c: Sum(4) })
x.concat(y)
// => SumObject({ a: Sum(1), b: Sum(5), c: Sum(4) })
那就太好了!现在让我们实现 SumObject
幺半群 - 抱歉,我真的想不出更好的名字了!
const SumObject = x => ({
constructor: SumObject,
value: x,
concat: ({value:y}) => {
return SumObject(Object.keys(y).reduce((acc, k) => {
if (acc[k] === undefined)
return Object.assign(acc, { [k]: y[k] })
else
return Object.assign(acc, { [k]: <b>acc[k].concat(y[k])</b> })
}, Object.assign({}, x)))
},
fold: f => f(x)
})
SumObject.empty = SumObject({})
我的天啊!那个有点难,但我希望你能看到它。注意 粗体 代码。我们对 SumObject
的实现假设 属性 值 也将是幺半群——这意味着值将具有 .concat
方法。
让我们一起健身吧!
为了让幺半群能够处理您的数据,我们需要将您的原始 Object
和 Number
类型转换为相应的 Monoid 类型。
最后,为了得到原始类型的答案,我们必须从 Monoid 类型返回转换为原始类型。
如果一切顺利,生成的表达式将如下所示
<b>sumToPrimitive</b>(data.map(x =>
<b>primitiveToSum</b>(x)).reduce((acc, x) =>
acc.concat(x), SumObject.empty))
现在让我们制作将原语转换为 Monoid 类型的函数
const primitiveToSum = x => {
switch (x.constructor) {
case Object:
return SumObject(Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: primitiveToSum(x[k]) }), {}))
case Number:
return Sum(x)
default:
throw Error(`unsupported type: ${x}`)
}
}
然后我们将制作一个将 Monoid 转换回原始类型
const sumToPrimitive = x => {
switch (x.constructor) {
case SumObject:
return x.fold(x =>
Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: sumToPrimitive(x[k]) }), {}))
case Sum:
return x.fold(identity)
default:
throw Error(`unsupported type: ${x}`)
}
}
"So that's it?"
好吧,因为您支持 嵌套 对象,它有点混乱,但恐怕这真的是我们能做的最好的事情。如果您愿意将数据限制为平面对象,事情会更清晰。
尽管如此,让我们看看这一切是否正常
const SumObject = x => ({
constructor: SumObject,
value: x,
concat: ({value:y}) => {
return SumObject(Object.keys(y).reduce((acc, k) => {
if (acc[k] === undefined)
return Object.assign(acc, { [k]: y[k] })
else
return Object.assign(acc, { [k]: acc[k].concat(y[k]) })
}, Object.assign({}, x)))
},
fold: f => f(x)
})
SumObject.empty = SumObject({})
const Sum = x => ({
constructor: Sum,
value: x,
concat: ({value:y}) => Sum(x + y),
fold: f => f (x)
})
Sum.empty = Sum(0)
const primitiveToSum = x => {
switch (x.constructor) {
case Object:
return SumObject(Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: primitiveToSum(x[k]) }), {}))
case Number:
return Sum(x)
default:
throw Error(`unsupported type: ${x}`)
}
}
const sumToPrimitive = x => {
switch (x.constructor) {
case SumObject:
return x.fold(x =>
Object.keys(x).reduce((acc, k) =>
Object.assign(acc, { [k]: sumToPrimitive(x[k]) }), {}))
case Sum:
return x.fold(identity)
default:
throw Error(`unsupported type: ${x}`)
}
}
const identity = x => x
const data = [
{
calories: {total: 0, fat: 0},
vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
fats: {total: 0},
minerals: {calcium: 0}
},
{
calories: {total: 150, fat: 40},
vitamins: {a: {total: 100}, b6: 30, c: 200},
fats: {total: 3}
},
{
calories: {total: 100, fat: 60},
vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
minerals: {calcium: 20}
}
]
const result = sumToPrimitive(data.map(x =>
primitiveToSum(x)).reduce((acc, x) =>
acc.concat(x), SumObject.empty))
console.log(result)
输出
{ calories: { total: 250, fat: 100 },
vitamins: { a: { total: 220, retinol: 10 }, b6: 30, c: 400 },
fats: { total: 3 },
minerals: { calcium: 20 } }
备注
幺半群是维护泛型、可重用组合术语的一种很好的接口——这就是函数式编程的全部意义所在。但是,由于您的数据嵌套复杂(并且本机 JavaScript 缺少自定义类型),需要您预先进行更多手动编码。
我无法想象 Lodash 解决方案会是什么样子——但我可以告诉你的是,它会抛弃泛型和可重用性 window——尤其是考虑到你的数据结构。
您可以使用 _.mergeWith()
来完成。我正在使用 _.spread()
创建一个 _.mergeWith()
来处理数组而不是单个参数。
var data = [{"calories":{"total":0,"fat":0},"vitamins":{"a":{"total":0,"retinol":0},"b6":0,"c":0},"fats":{"total":0},"minerals":{"calcium":0}},{"calories":{"total":150,"fat":40},"vitamins":{"a":{"total":100},"b6":30,"c":200},"fats":{"total":3}},{"calories":{"total":100,"fat":60},"vitamins":{"a":{"total":120,"retinol":10},"b6":0,"c":200},"minerals":{"calcium":20}}];
var mergeWith = _.spread(_.mergeWith);
function deepMerge(objs) {
var args = [{}].concat(objs, function(objValue, srcValue) {
if(_.isNumber(objValue)) {
return objValue + srcValue;
}
});
return mergeWith(args);
}
var result = deepMerge(data);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
使用spread语法的ES6版本:
var data = [{"calories":{"total":0,"fat":0},"vitamins":{"a":{"total":0,"retinol":0},"b6":0,"c":0},"fats":{"total":0},"minerals":{"calcium":0}},{"calories":{"total":150,"fat":40},"vitamins":{"a":{"total":100},"b6":30,"c":200},"fats":{"total":3}},{"calories":{"total":100,"fat":60},"vitamins":{"a":{"total":120,"retinol":10},"b6":0,"c":200},"minerals":{"calcium":20}}];
function deepMerge(objs) {
return _.mergeWith({}, ...objs, (objValue, srcValue) => {
if (_.isNumber(objValue)) {
return objValue + srcValue;
}
});
}
var result = deepMerge(data);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
根据
var data = [{"calories":{"total":0,"fat":0},"vitamins":{"a":{"total":0,"retinol":0},"b6":0,"c":0},"fats":{"total":0},"minerals":{"calcium":0}},{"calories":{"total":150,"fat":40},"vitamins":{"a":{"total":100},"b6":30,"c":200},"fats":{"total":3}},{"calories":{"total":100,"fat":60},"vitamins":{"a":{"total":120,"retinol":10},"b6":0,"c":200},"minerals":{"calcium":20}}];
var mergeWith = _.spread(_.mergeWith);
function customizer(objValue, srcValue) {
if(_.isNumber(objValue)) {
return objValue + srcValue;
}
}
function deepMerge(objs, customizer) {
var args = [{}].concat(objs, function(objValue, srcValue) {
return customizer(objValue, srcValue);
});
return mergeWith(args);
}
var result = deepMerge(data, customizer);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
我更喜欢用 Ramda 以无点方式做这件事,但 OP 在 Lodash 中请求,所以我们开始吧,简单的递归解决方案:
let data = [{calories: {total: 0, fat: 0},vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},fats: {total: 0},minerals: {calcium: 0}},{calories: {total: 150, fat: 40},vitamins: {a: {total: 100}, b6: 30, c: 200},fats: {total: 3}},{calories: {total: 100, fat: 60},vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},minerals: {calcium: 20}}];
const recursor = result => (value, key) => {
if ( _.isNumber(value) ) {
result[key] = ((result[key] || (result[key] = 0)) + value);
return result[key];
} else {
return _.mapValues(value, recursor(result[key] || (result[key] = {}) ));
}
}
const reducer = (result, value, key) => _.mapValues(value, recursor(result));
let final = _.transform(data, reducer, {});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
虽然mergeWith
的递归函数。该解决方案忽略了 mergeWith
本质上是递归合并的事实,其中定制程序只会转换结果值,如果它 returns 不是 undefined
的任何值。此外,另一个问题是 mergeWith
改变了第一个对象参数,这在您想要保留源对象状态的情况下可能并不理想。
以下解决方案使用Function.prototype.apply with mergeWith
instead of wrapping it in a spread,您可以根据自己的喜好决定将其切换为spread
。为了解决源对象突变的问题,我们提供一个空对象作为第一个参数,然后将它与其余的源对象连接起来。最后,我们连接自定义程序函数,如果 value
是一个数字,returns 参数的总和。
var result = _.mergeWith.apply(
null,
[{}].concat(source).concat(function(value, src) {
if(_.isNumber(value)) {
return value + src;
}
})
);
var source = [
{
calories: {total: 0, fat: 0},
vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
fats: {total: 0},
minerals: {calcium: 0}
},
{
calories: {total: 150, fat: 40},
vitamins: {a: {total: 100}, b6: 30, c: 200},
fats: {total: 3}
},
{
calories: {total: 100, fat: 60},
vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
minerals: {calcium: 20}
}
];
var result = _.mergeWith.apply(
null,
[{}].concat(source).concat(function(value, src) {
if(_.isNumber(value)) {
return value + src;
}
})
);
console.log(result);
body>div {
min-height: 100%;
top: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>