Reputation: 51
Below is my input:
input1 =
[{
"201609": 5,
"201610": 7,
"201611": 9,
"201612": 10,
"FY17": 24,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 3,
"201610": 6,
"201611": 9,
"201612": 8
"FY17": 18,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 1,
"201610": 2,
"201611": 3,
"201612": 5
"FY17": 6,
"careerLevel": "CL1"
},
{
"201609": 2,
"201610": 4,
"201611": 6,
"201612": 9
"FY17": 12,
"careerLevel": "CL2"
}
]
}]
}]
input2 =
[{
"201609": 4,
"201610": 8,
"201611": 12,
"FY17": 24,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 9,
"201610": 2,
"201611": 7,
"FY17": 18,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 3,
"201610": 6,
"201611": 9,
"FY17": 18,
"careerLevel": "CL1"
},
{
"201609": 7,
"201610": 8,
"201611": 9,
"FY17": 24,
"careerLevel": "CL2"
}
]
}]
}]
output = input1 + input2.
My output should have the sum of all the numeric value. If it doesn't find the matching key like "201612" it should still keep that key in output.
[{
"201609": 9,
"201610": 15,
"201611": 21,
"201612": 10,
"FY17": 48,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 12,
"201610": 8,
"201611": 16,
"201612": 8,
"FY17": 24,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 4,
"201610": 8,
"201611": 12,
"201612": 5,
"FY17": 24,
"careerLevel": "CL1"
},
{
"201609": 9,
"201610": 12,
"201611": 15,
"201612": 9,
"FY17": 36,
"careerLevel": "CL2"
}
]
}]
}]
Below is what iam trying to do :
var output = [{}];
for(let i in input){
for (let key in input[i]){
if (output[0].hasOwnProperty(key)) {
output[0][key]+=input[i][key];
}else{
output[0][key]=input[i][key];
}
}
}
console.log(JSON.stringify(output)); // errors out
But this is not giving me desired result as it is not able to sum the hierarchical json structure as above.
Upvotes: 0
Views: 892
Reputation: 3180
Let's start by writing the main merge
function:
function merge(x, y, fn) {
if (isNotCompound(x) && isNotCompound(y)) {
return fn(x, y);
}
if (isArray(x) && isArray(y)) {
return mergeArray(x, y, fn);
}
if (isObject(x) && isObject(y)) {
return mergeObject(x, y, fn);
}
throw new Error('the two input values are not of the same compound type');
}
The merge
function amounts to a dispatching on the type of its input values x
and y
. If they are both found to be primitive types (i.e., they are not arrays nor objects) they are merged according to the fn
function. Otherwise, the proper helper function is selected for proceeding with the traversing.
Let's therefore implement the type predicates necessary for the dispatch:
const isArray = x => Array.isArray(x);
const isObject = x => Object.prototype.toString.call(x) === '[object Object]';
const isNotCompound = x => !isArray(x) && !isObject(x);
It is now a matter of writing mergeArray
and mergeObject
. One possible implementation for mergeArray
is as follows:
function mergeArray(xs, ys, fn) {
if (xs.length !== ys.length) throw new Error('the two arrays must be of the same size');
let r = [];
for (let i = 0; i < xs.length; i++) {
r.push(merge(xs[i], ys[i], fn));
}
return r;
}
As shown, mergeArray
requires the two arrays of being of the same size. As for mergeObject
, one way of implementing it is as follows:
function mergeObject(obj1, obj2, fn) {
let r = {};
for (let key of Object.keys(obj1)) {
r[key] = obj2[key] ? merge(obj1[key], obj2[key], fn) : obj1[key];
}
for (let key of Object.keys(obj2)) {
if (r[key]) continue;
r[key] = obj2[key];
}
return r;
}
Notably, values are combined for common keys, while unshared properties are directly propagated to the resulting object.
At last, the desired merging strategy can be obtained by passing the following input function:
const isNumber = x => typeof x === 'number';
const add = (x, y) => isNumber(x) && isNumber(y) ? x + y : (x || y);
If the two input, primitive values are both numbers, it returns the sum of them, otherwise it returns whatever of the two happens to be defined.
We can finally execute:
console.log(JSON.stringify(merge(input1, input2, add)));
The whole source code follows.
const isArray = x => Array.isArray(x);
const isObject = x => Object.prototype.toString.call(x) === '[object Object]';
const isNotCompound = x => !isArray(x) && !isObject(x);
function merge(x, y, fn) {
if (isNotCompound(x) && isNotCompound(y)) {
return fn(x, y);
}
if (isArray(x) && isArray(y)) {
return mergeArray(x, y, fn);
}
if (isObject(x) && isObject(y)) {
return mergeObject(x, y, fn);
}
throw new Error('the two input values are not of the same compound type');
};
function mergeArray(xs, ys, fn) {
if (xs.length !== ys.length) throw new Error('the two arrays must be of the same size');
let r = [];
for (let i = 0; i < xs.length; i++) {
r.push(merge(xs[i], ys[i], fn));
}
return r;
}
function mergeObject(obj1, obj2, fn) {
let r = {};
for (let key of Object.keys(obj1)) {
r[key] = obj2[key] ? merge(obj1[key], obj2[key], fn) : obj1[key];
}
for (let key of Object.keys(obj2)) {
if (r[key]) continue;
r[key] = obj2[key];
}
return r;
}
let input1 = [{
"201609": 5,
"201610": 7,
"201611": 9,
"201612": 10,
"FY17": 24,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 3,
"201610": 6,
"201611": 9,
"201612": 8,
"FY17": 18,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 1,
"201610": 2,
"201611": 3,
"201612": 5,
"FY17": 6,
"careerLevel": "CL1"
},
{
"201609": 2,
"201610": 4,
"201611": 6,
"201612": 9,
"FY17": 12,
"careerLevel": "CL2"
}
]
}]
}];
let input2 = [{
"201609": 4,
"201610": 8,
"201611": 12,
"FY17": 24,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 9,
"201610": 2,
"201611": 7,
"FY17": 18,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 3,
"201610": 6,
"201611": 9,
"FY17": 18,
"careerLevel": "CL1"
}, {
"201609": 7,
"201610": 8,
"201611": 9,
"FY17": 24,
"careerLevel": "CL2"
}]
}]
}];
const isNumber = x => typeof x === 'number';
const add = (x, y) => isNumber(x) && isNumber(y) ? x + y : (x || y);
console.log(JSON.stringify(merge(input1, input2, add)));
Upvotes: 1
Reputation: 50291
Create a recursive function and inside that use forEach
to iterate the array
The flow is like this instead of looping both the input1
& input2
array , loop over one of them and if the value of the key is an number then add it to the value of same key in another array.If the value of a key is an array then call the same recursive function with new arguuments
var input1 = [{
"201609": 5,
"201610": 7,
"201611": 9,
"201612": 10,
"FY17": 24,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 3,
"201610": 6,
"201611": 9,
"201612": 8,
"FY17": 18,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 1,
"201610": 2,
"201611": 3,
"201612": 5,
"FY17": 6,
"careerLevel": "CL1"
},
{
"201609": 2,
"201610": 4,
"201611": 6,
"201612": 9,
"FY17": 12,
"careerLevel": "CL2"
}
]
}]
}]
var input2 = [{
"201609": 4,
"201610": 8,
"201611": 12,
"FY17": 24,
"metric": "metric1",
"careerLevelGroups": [{
"201609": 9,
"201610": 2,
"201611": 7,
"FY17": 18,
"careerLevel": "Senior Managing Director",
"careerLevels": [{
"201609": 3,
"201610": 6,
"201611": 9,
"FY17": 18,
"careerLevel": "CL1"
},
{
"201609": 7,
"201610": 8,
"201611": 9,
"FY17": 24,
"careerLevel": "CL2"
}
]
}]
}]
// A function which will take input2 and input1 array
function loopArray(arrayToBeLooped, addingArray) {
// now looping over the array
arrayToBeLooped.forEach(function(item, index) {
// item is each object,here checking if the value of object key is array or number
for (let keys in item) {
// if number then find the same key from other array and add the value
if (typeof item[keys] === 'number') {
addingArray[index][keys] = addingArray[index][keys] + item[keys]
}
if (Array.isArray(item[keys])) {
// if the type of value is another array for example
// careerLevelGroups & careerLevels then it is basically same
// as looping over array input1 & input2
// so calling the same loopArray function and passing different
// parameter but the object remains same
loopArray(arrayToBeLooped[index][keys], addingArray[index][keys])
}
}
})
}
loopArray(input2, input1)
console.log(input1)
Upvotes: 0