Compare two nested array and objects for find difference - javascript

I have two JSON data with multi-stage nesting. I want compare it by following conditions:
1) if name in the first object equal to name in the second object compare them prop arrays, else if nothing equal names in two object return empty array;
2) compare objects into two prop arrays and find difference;
3) return new object with difference from first and second arrays.
const p1 = [{
name: 'B [1]', // name equals and prop differnce, then comparing it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
C: { C: 78, D: 4, T: 7, } }],
}, {
name: 'B [2]', // name equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
D: { C: 3, D: 4, Y: 13 } }],
}, {
name: 'B [3]', // name equals and prop differnce, then comparing it
prop: [{
E: { A: 1, B: 2 },
R: { A: 1, B: 2 },
T: { C: 3, D: 4, } }],
}, {
name: 'B [4]', // name and prop equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
S: { A: 1, B: 2 },
D: { C: 3, D: 4, } }],
}]
const p2 = [{
name: 'B [1]', // name equals and prop differnce, then comparing it
prop: [{
A: { A: 1, B: 8 },
B: { A: 1, B: 2 },
C: { C: 3, T: 7, O: 9 } }],
}, {
name: 'B [6]', // name not equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
D: { C: 3, D: 4 } }],
}, {
name: 'B [3]', // name equals and prop differnce, then comparing it
prop: [{
E: { A: 1, B: 2 },
R: { A: 1, B: 2, U: 150 },
T: { C: 3, D: 4, } }],
}, {
name: 'B [4]', // name and prop equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
S: { A: 1, B: 2 },
D: { C: 3, D: 4, } }],
}]
The result should look like this:
const result = [{
name: 'B [1]',
propOne: [{
A: { B: 2 },
C: { C: 78, D: 4, O: 'Missing' }
}],
propTwo: [{
A: { B: 8 },
C: { C: 3, D: 'Missing', O: 9 }
}],
},{
name: 'B [3]',
propOne: [{
R: { U: 'Missing' }
}],
propTwo: [{
R: { U: 150 }
}]
}]
I also bitterly attach my worthless code here, which does nothing.
const compare = (p1, p2) => {
return p1.reduce((acc, curr) => {
p2.reduce((acc2, curr2) => {
if (curr.name === curr2.name) {
const keys1 = R.fromPairs(Object.keys(curr.prop[0]).map(x => ([x, curr.prop[0][x]])));
const keys2 = R.fromPairs(Object.keys(curr2.prop[0]).map(x => ([x, curr2.prop[0][x]])));
}
return acc;
}, [])
return acc;
}, [])
}
I would be extremely grateful for any help and advice.

All the difficulty resides in specing the expected behaviour of the comparison function:
for two objects (that I refer as values) a and b: {A:1,B:2} and {A:1,B:3,C:4}
the output of cmp(a,b) shall be:
foreach key of a:
if a[key] != b[key] (or b does not have k prop)
diff[key] = a[key]
else (value is equal, no diff)
foreach key of b not in a
diff[key] = Missing
hence (e.g) {B:2, C:'Missing'}
when comparing the values, if diff is empty, you can skip the current prop and when comparing props if the diff is empty skip the record (as if names were different)
function cmp(x,y){
let a = x.prop[0];
let b = y.prop[0];
return Object.keys(a).reduce((o,k)=>{
//compare the right value (such as { A: 1, B: 2 }). assumes primitive types
let u = a[k];
let v = b[k];
let diff = Object.keys(u).reduce((o,k)=>{
return u[k]==v[k]?o:(o[k] = u[k],o)
},{})
Object.keys(v).reduce((o,k)=>{
return u.hasOwnProperty(k)?o:(o[k]='Missing',o);
}, diff);
if(Object.keys(diff).length){
o[k] = diff;
}
return o;
},{});
}
function diff(p1,p2){
return p1.flatMap((o,i)=>{
if(p2[i].name != p1[i].name){
return []
}
let a = p1[i];
let b = p2[i];
let res = cmp(a,b);
if(!Object.keys(res).length){
return [];
}
return {name: a.name, propOne:res, propTwo:cmp(b,a)}
})
};
const p1 = [{
name: 'B [1]', // name equals and prop differnce, then comparing it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
C: { C: 78, D: 4, T: 7, } }],
}, {
name: 'B [2]', // name equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
D: { C: 3, D: 4, Y: 13 } }],
}, {
name: 'B [3]', // name equals and prop differnce, then comparing it
prop: [{
E: { A: 1, B: 2 },
R: { A: 1, B: 2 },
T: { C: 3, D: 4, } }],
}, {
name: 'B [4]', // name and prop equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
S: { A: 1, B: 2 },
D: { C: 3, D: 4, } }],
}]
const p2 = [{
name: 'B [1]', // name equals and prop differnce, then comparing it
prop: [{
A: { A: 1, B: 8 },
B: { A: 1, B: 2 },
C: { C: 3, T: 7, O: 9 } }],
}, {
name: 'B [6]', // name not equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
D: { C: 3, D: 4 } }],
}, {
name: 'B [3]', // name equals and prop differnce, then comparing it
prop: [{
E: { A: 1, B: 2 },
R: { A: 1, B: 2, U: 150 },
T: { C: 3, D: 4, } }],
}, {
name: 'B [4]', // name and prop equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
S: { A: 1, B: 2 },
D: { C: 3, D: 4, } }],
}];
console.log('result', JSON.stringify(diff(p1,p2),null,2))

It's kinda late, and i don't have time anymore for extensive tests right now, so this may have some bugs for unexpected cases (i hope not though). It's way too long for a comment, and discarding it would be a bit of a waste.
The below code contains two ways: one constructive, starting with empty objects, building up, and one the other way around, starting with the full objects, and then deleting.
Note, that in your data structures, there are several "one element arrays". If these can contain more than one element (they don't really make much sense to me, it's already objects within arrays within arrays, giving plenty of room for additional props), there needs to be one or two extra map steps, no big issue though.
const p1 = [{
name: 'B [1]', // name equals and prop differnce, then comparing it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
C: { C: 78, D: 4, T: 7, }
}],
}, {
name: 'B [2]', // name equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
D: { C: 3, D: 4, Y: 13 }
}],
}, {
name: 'B [3]', // name equals and prop differnce, then comparing it
prop: [{
E: { A: 1, B: 2 },
R: { A: 1, B: 2 },
T: { C: 3, D: 4, }
}],
}, {
name: 'B [4]', // name and prop equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
S: { A: 1, B: 2 },
D: { C: 3, D: 4, }
}],
}];
const p2 = [{
name: 'B [1]', // name equals and prop differnce, then comparing it
prop: [{
A: { A: 1, B: 8 },
B: { A: 1, B: 2 },
C: { C: 3, T: 7, O: 9 }
}],
}, {
name: 'B [6]', // name not equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
B: { A: 1, B: 2 },
D: { C: 3, D: 4 }
}],
}, {
name: 'B [3]', // name equals and prop differnce, then comparing it
prop: [{
E: { A: 1, B: 2 },
R: { A: 1, B: 2, U: 150 },
T: { C: 3, D: 4, }
}],
}, {
name: 'B [4]', // name and prop equals, then skiping it
prop: [{
A: { A: 1, B: 2 },
S: { A: 1, B: 2 },
D: { C: 3, D: 4, }
}],
}];
const result = [{
name: 'B [1]',
propOne: [{
A: { B: 2 },
C: { C: 78, D: 4, O: 'Missing' }
}],
propTwo: [{
A: { B: 8 },
C: { C: 3, D: 'Missing', O: 9 }
}],
},{
name: 'B [3]',
propOne: [{
R: { U: 'Missing' }
}],
propTwo: [{
R: { U: 150 }
}]
}]
const diffDestructive = (a, b) => {
/**
* Copy the objects, remove all identical properties recursively,
* then add "Missing" properties for all properties from either side
* that doesn't exist on the other.
*/
const remove = (x, y) => {
for (let key of Object.keys(x)) {
// hasOwnProperty is only for the degenerate case { prop: undefined }
if (x[key] === y[key] && y.hasOwnProperty(key)) {
delete x[key];
delete y[key];
} // typeof null === "object", therefore an additional check is needed
else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object") {
remove(x[key], y[key]);
if ([x, y].every(e => Object.keys(e[key]).length === 0)) {
delete x[key];
delete y[key];
}
}
}
};
const addMissingNotes = (x, y) => {
for (let key of Object.keys(x)) {
if (!(y.hasOwnProperty(key))) y[key] = "Missing";
else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object")
addMissingNotes(x[key], y[key]);
}
};
// quick and dirty object deep-copy
let [modA, modB] = [a, b].map(e => JSON.parse(JSON.stringify(e)));
remove(modA, modB);
addMissingNotes(modA, modB);
addMissingNotes(modB, modA);
return [modA, modB];
};
const diffConstructive = (a, b) => {
/**
* Add differing properties to the result step by step.
* Nested objects are handled recursively.
*/
let diffA = {}, diffB = {};
for (let key of Object.keys(a)) {
//properties that a and b share
if (b.hasOwnProperty(key)) {
if (a[key] && typeof a[key] === "object" && b[key] && typeof b[key] === "object") {
let subDiffs = diffConstructive(a[key], b[key]);
// The way the construction works, Object.keys(subDiffs[0]).length !== 0 would be enough.
if (subDiffs.some(e => Object.keys(e).length !== 0)) {
[diffA[key], diffB[key]] = subDiffs;
}
} else if (a[key] !== b[key]) {
diffA[key] = a[key];
diffB[key] = b[key];
}
} // properties that a has but b doesn't
else {
diffA[key] = a[key];
diffB[key] = "Missing";
}
}
// properties that b has but a doesn't
for (let key of Object.keys(b)) {
if (!a.hasOwnProperty(key)) {
diffB[key] = b[key];
diffA[key] = "Missing";
}
}
return [diffA, diffB];
};
const compare = (a, b, method) => a
.map((e, i) => [e, b[i]])
//same name only
.filter(([a, b]) => a.name === b.name)
// formatting
.map(([a, b]) => {
const [diffA, diffB] = method(a.prop[0], b.prop[0]);
return {
name: a.name,
propOne: [diffA],
propTwo: [diffB]
};
})
// There must be a difference
.filter(e => [e.propOne[0], e.propTwo[0]].some(e => Object.keys(e).length !== 0));
const destructive = compare(p1, p2, diffDestructive);
const constructive = compare(p1, p2, diffConstructive);
console.log(`Constructive method gives the wanted result: ${_.isEqual(result, destructive)}`);
console.log(`Destructive method gives the wanted result: ${_.isEqual(result, constructive)}`);
<!--
this is only for a deepequals function, _.isEqual,
and only used for checking the results. I could have copied
one into the code, but why make this even longer...
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.core.min.js"></script>

Related

duplicate object inside array of objects conditionally in Javascript

I want to duplicate specific objects inside an array.
I have the array1 and I want to get the array2.
Example:
const array1 = [
{ a: String, b: Number, c: [3]},
{ a: String, b: Number, c: [12, 13]},
{ a: String, b: Number, c: [ 4, 5, 6]}
]
array2 = [
{ a: String, b: Number, c: [3]},
{ a: String, b: Number, c: [12]},
{ a: String, b: Number, c: [13]},
{ a: String, b: Number, c: [4]},
{ a: String, b: Number, c: [5]},
{ a: String, b: Number, c: [6]},
]
Array#reduce may come handy
const array1 = [
{ a: 'String', b: 'Number', c: [3]},
{ a: 'String', b: 'Number', c: [12, 13]},
{ a: 'String', b: 'Number', c: [ 4, 5, 6]}
];
const r = array1.reduce((s, a) =>
(s.push(a.c.length > 1 ? a.c.map((t) => ({ ...a, c: [t] })) : a), s), []);
document.write('<pre>' + JSON.stringify(r, null, 2) + '</pre>');
const array1 = [
{ a: String, b: Number, c: [3] },
{ a: String, b: Number, c: [12, 13] },
{ a: String, b: Number, c: [4, 5, 6] }
];
var array2 = [];
array1.map(obj => {
var obj2 = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (!Array.isArray(obj[key])) {
obj2[key] = obj[key];
} else {
obj[key].map(item => {
obj2[key] = item;
array2.push({ ...obj2 });
});
} // end if-else
} // end hasOwnProperty
} // end for
});
console.log(array2);

How to "uniqueify" a javascript array of objects?

How does one remove objects in an array that match across all keys and values? I've seen variations of this question but only for a particular field.
For example, given the following input the output would look like so:
> var a = [
{a:1, b:'x'},
{a:1, b:'y'},
{a:1, b:'y'},
{a:2, b:'x'},
{a:2, b:'y'},
{a:2, b:'y'},
{a:2, b:'x'},
{b:'y', a:2},
{a:2, b:'y', c:'surprise!'}
]
> blackbox(a)
[
{a:1, b:'x'},
{a:1, b:'y'},
{a:2, b:'x'},
{a:2, b:'y'},
{a:2, b:'y', c:'surprise!'}
]
Ideally blackbox isn't hard-coded with the keys.
BTW, here the hack that I have now. It turns each (sorted) object into a string and checks to see if it's seen that string before.
Regardless, there's got to be a more elegant solution out there!
> function uniq(a) {
var keys = new Set();
return a.filter(function(row) {
var key = Object.entries(row).sort().toString();
var uniq = !keys.has(key);
keys.add(key);
return uniq;
});
}
> a =
[ { a: 1, b: 'x' },
{ a: 1, b: 'y' },
{ a: 1, b: 'y' },
{ a: 2, b: 'x' },
{ a: 2, b: 'y' },
{ a: 2, b: 'y' },
{ a: 2, b: 'x' },
{ b: 'y', a: 2 },
{ a: 2, b: 'y', c: 'surprise!' },
{ c: 'surprise!', a: 2, b: 'y' } ]
> uniq(a)
[ { a: 1, b: 'x' },
{ a: 1, b: 'y' },
{ a: 2, b: 'x' },
{ a: 2, b: 'y' },
{ a: 2, b: 'y', c: 'surprise!' } ]
> b =
[ { a: { b: 1, c: 2 }, b: 1 },
{ b: 1, a: { c: 2, b: 1 } },
{ a: { b: 1, c: 2 }, b: 2 } ]
> uniq(b) // works because nested objects happen to be identical
[ { a: { b: 1, c: 2 }, b: 1 }, { a: { b: 1, c: 2 }, b: 2 } ]
> c =
[ { a: { b: 1, c: 2 }, b: 1 },
{ b: 1, a: { c: 2, b: 1 } },
{ a: { b: 1, c: 2 }, b: 2 },
{ a: { b: 2, c: 1 }, b: 2 } ]
> uniq(c) // fail on nested object
[ { a: { b: 1, c: 2 }, b: 1 }, { a: { b: 1, c: 2 }, b: 2 } ]

How to join two JSON Array objects in Node

How to join two JSON Array objects in Node.
I want to join obj1 + obj2 so I can get the new JSON object:
obj1 = [ { t: 1, d: 'AAA', v: 'yes' },
{ t: 2, d: 'BBB', v: 'yes' }]
obj2 = [ { t: 3, d: 'CCC', v: 'yes' },
{ t: 4, d: 'DDD', v: 'yes' }]
output = [ { t: 1, d: 'AAA', v: 'yes' },
{ t: 2, d: 'BBB', v: 'yes' },
{ t: 3, d: 'CCC', v: 'yes' },
{ t: 4, d: 'DDD', v: 'yes' }]
var output = obj1.concat(obj2);
obj1 = [ { t: 1, d: 'AAA', v: 'yes' },
{ t: 2, d: 'BBB', v: 'yes' }]
obj2 = [ { t: 3, d: 'CCC', v: 'yes' },
{ t: 4, d: 'DDD', v: 'yes' }]
var output = obj1.concat(obj2);
console.log(output);
try
Object.assign(obj1, obj2);
For Details check Here
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
It can be done easily using ES6,
const output = [...obj1, ...obj2];
i already got an answer from the link provided by Pravin
var merge = function() {
var destination = {},
sources = [].slice.call( arguments, 0 );
sources.forEach(function( source ) {
var prop;
for ( prop in source ) {
if ( prop in destination && Array.isArray( destination[ prop ] ) ) {
// Concat Arrays
destination[ prop ] = destination[ prop ].concat( source[ prop ] );
} else if ( prop in destination && typeof destination[ prop ] === "object" ) {
// Merge Objects
destination[ prop ] = merge( destination[ prop ], source[ prop ] );
} else {
// Set new values
destination[ prop ] = source[ prop ];
}
}
});
return destination;
};
console.log(JSON.stringify(merge({ a: { b: 1, c: 2 } }, { a: { b: 3, d: 4 } })));
you can use jmerge package.
npm i jmerge
const jm = require('jmerge')
jm(obj1,obj2,obj3,...) //merging json data
I simply convert the arrays to strings, join them crudely with a comma, and then parse the result to JSON:
newJson=JSON.parse(
JSON.stringify(copyJsonObj).substring(0,JSON.stringify(copyJsonObj).length-1) +
',' +
JSON.stringify(jsonObj).substring(1)
)

How to convert an array of dictionaries to an array of arrays?

How can I easily convert this (array of dictionaries):
[ { a: 1,
b: 1,
c: 'something',
d: 1 },
{ a: 23443,
b: 2111,
c: 'something 2',
d: 1456 }
]
to this (array of arrays):
[ [ 1,
1,
'something',
1 ],
[ 23443,
2111,
'something2',
1456 ]
]
Use Array.prototype.map and for-in loop
The map() method creates a new array with the results of calling a provided function on every element in this array
The for...in statement iterates over the enumerable properties of an object
Try this:
var input = [{
a: 1,
b: 1,
c: 'something',
d: 1
}, {
a: 23443,
b: 2111,
c: 'something2',
d: 1456
}];
var op = input.map(function(inp) {
var arr = [];
for (var i in inp) {
arr.push(inp[i]);
}
return arr;
});
console.log(op);
OR use Object.keys(YOUR_OBJECT):
var input = [{
a: 1,
b: 1,
c: 'something',
d: 1
}, {
a: 23443,
b: 2111,
c: 'something2',
d: 1456
}];
var op = input.map(function(inp) {
return Object.keys(inp).map(function(key) {
return inp[key];
})
});
console.log(op);

Node.js object array data joins

Are there any libraries or efficient techniques to perform array joins in node JS such that,
A = [ { a: 1, b: 'a' }, { a: 2, b:'b' }, { a: 3, b: 'a' }, { a: 4, b: 'b' } ]
B = [ { a: 1, c: true }, { a: 2, c: true }, { a: 3, c: false } ]
could be joined such that the following results could be produced:
# Intersection on a
C = [ { a: 1, b: 'a', c: true }, { a: 2, b: 'b', c: true }, { a: 3, b: 'a', c: false } ]
# Union on a
D = [ { a: 1, b: 'a', c: true }, { a: 2, b: 'b', c: true }, { a: 3, b: 'a', c: false }, { a: 4, b: 'b' } ]
Is array.map the best solution to this problem?
efficiency is paramount here, since it could be handling huge arrays in production
You're not very specific about how you identify and merge your object.
Using Underscore, the result can be obtained as follow:
_u=require("underscore")
A = [ { a: 1, b: 'a' }, { a: 2, b:'b' }, { a: 3, b: 'a' }, { a: 4, b: 'b' } ]
B = [ { a: 1, c: true }, { a: 2, c: true }, { a: 3, c: false } ]
D = _u.zip(A,B).map(
function(x){
return _u.extend(x[0],x[1]);
}
);
C = _u.zip(A,B).filter(
function(x){
return !!x[1];
}
).map(
function(x){
return _u.extend(x[0],x[1]);
}
);
Is that what you're looking for ?

Categories