Comparing the keys of two JavaScript objects - javascript

I've got two sets of JavaScript objects. I want to compare object1 to object2, and then get a list of all the keys that are in object1, but not in object2. I've searching for resources to help me, but I've only ended up finding comparison functions for simple objects. The objects that I want to compare have a lot of nesting. I've included an example at the bottom.
How would I go about making a function for comparing these two objects? Is it possible to create a flexible function, that would also work if the objects were to change and contain more nesting?
const object1 = {
"gender": "man",
"age": 33,
"origin": "USA",
"jobinfo": {
"type": "teacher",
"school": "Wisconsin"
},
"children": [
{
"name": "Daniel",
"age": 12,
"pets": [
{
"type": "cat",
"name": "Willy",
"age": 2
},
{
"type": "dog",
"name": "jimmie",
"age": 5
}
]
},
{
"name": "Martin",
"age": 14,
"pets": [
{
"type": "bird",
"name": "wagner",
"age": 12
}
]
}
],
"hobbies": {
"type": "football",
"sponsor": {
"name": "Pepsi",
"sponsorAmount": 1000,
"contact": {
"name": "Leon",
"age": 59,
"children": [
{
"name": "James",
"pets": [
{
"type": "dog",
"age": 4
}
]
}
]
}
}
}
}
const object2 = {
"gender": "man",
"jobinfo": {
"type": "teacher"
},
"children": [
{
"name": "Daniel",
"age": 12,
"pets": [
{
"type": "cat",
"name": "Willy",
"age": 2
},
{
"type": "dog",
"name": "jimmie",
"age": 5
}
]
}
]
}
So what I want to achieve by comparing these two objects, is in this case to have an array return that consists of the keys that are in object1, but not object2. So the array would look something like this.
["age", "hobbies", "type", "sponsor", "name", "sponsorAmount", "contact", "name", "age", "children", "name", "pets", "type", "age"].
This is what I've gotten to so far. This is sort of working. But it's not printing out age for example, because age is a property that exists in multiple of the nested objects.
jsfiddle: https://jsfiddle.net/rqdgojq2/
I've had a look at the following resources:
https://stackoverflow.com/a/25175871/4623493
https://stackoverflow.com/a/21584651/4623493

Complex solution using Set object and custom getAllKeyNames() recursive function to get all unique key names from specified object:
var object1 = {"gender":"man","age":33,"origin":"USA","jobinfo":{"type":"teacher","school":"Wisconsin"},"children":[{"name":"Daniel","age":12,"pets":[{"type":"cat","name":"Willy","age":2},{"type":"dog","name":"jimmie","age":5}]},{"name":"Martin","age":14,"pets":[{"type":"bird","name":"wagner","age":12}]}],"hobbies":{"type":"football","sponsor":{"name":"Pepsi","sponsorAmount":1000,"contact":{"name":"Leon","age":59,"children":[{"name":"James","pets":[{"type":"dog","age":4}]}]}}}},
object2 = {"gender":"man","age":33,"origin":"USA","jobinfo":{"type":"teacher","school":"Wisconsin"},"children":[{"name":"Daniel","age":12,"pets":[{"type":"cat","name":"Willy","age":2},{"type":"dog","name":"jimmie","age":5}]}]};
function getAllKeyNames(o, res){
Object.keys(o).forEach(function(k){
if (Object.prototype.toString.call(o[k]) === "[object Object]") {
getAllKeyNames(o[k], res);
} else if (Array.isArray(o[k])) {
o[k].forEach(function(v){
getAllKeyNames(v, res);
});
}
res.add(k);
});
}
var o1Keys = new Set(), o2Keys = new Set();
getAllKeyNames(object1, o1Keys); // unique keys of object1
getAllKeyNames(object2, o2Keys); // unique keys of object2
// get a list of all the keys that are in object1, but not in object2
var diff = [...o1Keys].filter((x) => !o2Keys.has(x));
console.log(diff);

Thanks for the feedback.
I ended up solving it, with a lot of inspiration from Romans answer.
const compareObjects = (obj1, obj2) => {
function getAllKeyNames(o, arr, str){
Object.keys(o).forEach(function(k){
if (Object.prototype.toString.call(o[k]) === "[object Object]") {
getAllKeyNames(o[k], arr, (str + '.' + k));
} else if (Array.isArray(o[k])) {
o[k].forEach(function(v){
getAllKeyNames(v, arr, (str + '.' + k));
});
}
arr.push(str + '.' + k);
});
}
function diff(arr1, arr2) {
for(let i = 0; i < arr2.length; i++) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
return arr1;
}
const o1Keys = [];
const o2Keys = [];
getAllKeyNames(obj1, o1Keys, ''); // get the keys from schema
getAllKeyNames(obj2, o2Keys, ''); // get the keys from uploaded file
const missingProps = diff(o1Keys, o2Keys); // calculate differences
for(let i = 0; i < missingProps.length; i++) {
missingProps[i] = missingProps[i].replace('.', '');
}
return missingProps;
}
jsfiddle here: https://jsfiddle.net/p9Lm8b53/

You could use an object for counting.
function getCount(object, keys, inc) {
Object.keys(object).forEach(function (k) {
if (!Array.isArray(object)) {
keys[k] = (keys[k] || 0) + inc;
if (!keys[k]) {
delete keys[k];
}
}
if (object[k] && typeof object[k] === 'object') {
getCount(object[k], keys, inc)
}
});
}
var object1 = { gender: "man", age: 33, origin: "USA", jobinfo: { type: "teacher", school: "Wisconsin" }, children: [{ name: "Daniel", age: 12, pets: [{ type: "cat", name: "Willy", age: 2 }, { type: "dog", name: "jimmie", age: 5 }] }, { name: "Martin", age: 14, pets: [{ type: "bird", name: "wagner", age: 12 }] }], hobbies: { type: "football", sponsor: { name: "Pepsi", sponsorAmount: 1000, contact: { name: "Leon", age: 59, children: [{ name: "James", pets: [{ type: "dog", age: 4 }] }] } } } },
object2 = { gender: "man", jobinfo: { type: "teacher" }, children: [{ name: "Daniel", age: 12, pets: [{ type: "cat", name: "Willy", age: 2 }, { type: "dog", name: "jimmie", age: 5 }] }] },
count = {};
getCount(object1, count, 1);
getCount(object2, count, -1);
console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }

This recursive approach works best for me.
let object1 = {
a: 40,
b: 80,
c: 120,
xa: [
{
xc: 12,
xz: 12
}
],
rand: 12
};
let object2 = {
a: 20,
b: 30,
c: 40,
xa: [
{
xy: 12,
xz3: 12
}
]
};
function getObjDifferences(obj, obj2, propsMissing, keyName) {
Object.keys(obj).forEach(key => {
if(obj2[key] === undefined) {
if(keyName.length > 0) propsMissing.push(keyName+"->"+key);
else propsMissing.push(key)
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
if(obj2[key] !== undefined) {
if(keyName.length > 0) getObjDifferences(obj[key], obj2[key], propsMissing, keyName+"->"+key)
else getObjDifferences(obj[key], obj2[key], propsMissing, key)
} else {
propsMissing.push(key)
}
}
})
return propsMissing;
}
console.log(getObjDifferences(object1, object2, [], ''))
console.log(getObjDifferences(object2, object1, [], ''))

Related

New Map from JSON data with unique values

I'm trying to create a new map of [key:value] as [trait_type]:[{values}]
So taking the below data it should look like:
[Hair -> {"Brown", "White-Blue"},
Eyes -> {"Green", "Red"}
]
Heres my json obj
[
{
"name":"Charlie",
"lastname":"Gareth",
"date":1645462396133,
"attributes":[
{
"trait_type":"Hair",
"value":"Brown"
},
{
"trait_type":"Eyes",
"value":"Red"
}
]
},
{
"name":"Bob",
"lastname":"James",
"date":1645462396131,
"attributes":[
{
"trait_type":"Hair",
"value":"White-Blue"
},
{
"trait_type":"Eyes",
"value":"green"
}
]
}
];
To note: I'm also trying to make it the values unique so If I have 2 people with red eyes the value would only ever appear once.
This is what I have so far, kind of works but in the console window the values come out as a char array.
let newData = data.map((item) =>
item.attributes.map((ats) => {
return { [ats.trait_type]: [...ats.value] };
})
);
newData.map((newItem) => console.log(newItem));
You could take an object and iterate the nested data with a check for seen values.
const
data = [{ name: "Charlie", lastname: "Gareth", date: 1645462396133, attributes: [{ trait_type: "Hair", value: "Brown" }, { trait_type: "Eyes", value: "Red" }] }, { name: "Bob", lastname: "James", date: 1645462396131, attributes: [{ trait_type: "Hair", value: "White-Blue" }, { trait_type: "Eyes", value: "green" }] }],
result = data.reduce((r, { attributes }) => {
attributes.forEach(({ trait_type, value }) => {
r[trait_type] ??= [];
if (!r[trait_type].includes(value)) r[trait_type].push(value);
})
return r;
}, {});
console.log(result);
try this
const extractTrait = data =>
data.flatMap(d => d.attributes)
.reduce((res, {
trait_type,
value
}) => {
return {
...res,
[trait_type]: [...new Set([...(res[trait_type] || []), value])]
}
}, {})
const data = [{
"name": "Charlie",
"lastname": "Gareth",
"date": 1645462396133,
"attributes": [{
"trait_type": "Hair",
"value": "Brown"
},
{
"trait_type": "Eyes",
"value": "Red"
}
]
},
{
"name": "Bob",
"lastname": "James",
"date": 1645462396131,
"attributes": [{
"trait_type": "Hair",
"value": "White-Blue"
},
{
"trait_type": "Eyes",
"value": "green"
}
]
}
];
console.log(extractTrait(data))

How can I compare 2 nested objects, subtracting the differences & returning the equals?

I have the below 2 variables.
const val1 = {
students: {
grade: [
{
subject: "Math3",
name: "jack",
mark: 60,
attendance: 90,
abscent: 2,
},
],
class: [
{
className: "math3",
students: 50,
absenteeism: 10,
requirment: [
{
subject: "math1",
mark: 60,
pass: 51,
},
{
subject: "math2",
mark: 60,
pass: 51,
},
],
},
],
},
};
const val2 = {
students: {
grade: [
{
subject: "Math3",
name: "jack",
mark: 80
},
],
class: [
{
className: "math3",
students: 40,
absenteeism: 10,
requirment: [
{
subject: "math1",
mark: 75,
pass: 51,
},
{
subject: "math2",
mark: 90,
pass: 51,
},
],
},
],
},
};
I am trying to get the below result by returning the the key and value if they are the same, & if the key is a number, return the difference of that number & if the item doesn't exist, skip. Like so.
students: {
grade: [
{
subject: "Math3",
name: "jack",
diff: 20
},
],
class: [
{
className: "math3",
diff: 10,
requirment: [
{
subject: "math1",
diff: 15,
},
{
subject: "math2",
diff: 30,
},
],
},
],
},
};
Code so far. I get the values but not the way i want it. Also, is there a more cleaner way.
const changeTable = (table1, table2) => {
const result = {};
result["students"] = {};
let file1= table1.students
let file2=table2.students
for (let x in file1) {
if (file2[x]) {
result.students[x] =file1[x]
.map((y) => file2[x]
.map((z)=> Object.keys(y)
.map((key)=> {
if (y[key] === z[key]) {
return {[key]:y[key]}
}
if (y[key] !== z[key]) {
if (typeof y[key] === "number") {
const res= Number(z[key])-Number(y[key])
if (!isNaN(res)) {
return {[key]:res}
}
}
}
}
).filter(i =>i)))
}
}
return result
};
changeTable(val1, val2)
Any advice is much appreciated.
I tried to match your expected output as closely as possible. In general terms, this is not a task suitable for array methods like map because what you really need here is recursive traversal of objects. I added comments to the code to show what it's doing, in case something is not quite right.
const val1 = {
"students": {
"grade": [
{
"subject": "Math3",
"name": "jack",
"mark": 60,
"attendance": 90,
"abscent": 2
}
],
"class": [
{
"className": "math3",
"students": 50,
"absenteeism": 10,
"requirment": [
{
"subject": "math1",
"mark": 60,
"pass": 51
},
{
"subject": "math2",
"mark": 60,
"pass": 51
}
]
}
]
}
}
const val2 = {
"students": {
"grade": [
{
"subject": "Math3",
"name": "jack",
"mark": 80
}
],
"class": [
{
"className": "math3",
"students": 40,
"absenteeism": 10,
"requirment": [
{
"subject": "math1",
"mark": 75,
"pass": 51
},
{
"subject": "math2",
"mark": 90,
"pass": 51
}
]
}
]
}
}
function diffObject(obj1, obj2) {
let result = {}
for (let key in obj1) {
// ignore properties not present in both objects
if (!(key in obj2)) continue
// ignore not same type, such as a number and string
if (typeof obj1[key] !== typeof obj2[key]) continue
// when both are number, set the property to the difference of the two
if (typeof obj1[key] === 'number') {
// only use different numbers
if (obj1[key] !== obj2[key]) {
result[key] = obj2[key] - obj1[key]
// in case you really wanted the "diff" property
// result.diff = obj2[key] - obj1[key]
}
}
// recursively iterate over each member of array
else if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) {
result[key] = obj1[key].map((item, index) => diffObject(item, obj2[key][index]))
}
// recursively iterate objects
else if (typeof obj1[key] === 'object' && obj1[key]) {
result[key] = diffObject(obj1[key], obj2[key])
}
// copy equal data
else if (obj1[key] === obj2[key]) {
result[key] = obj1[key]
}
// anything else is ignored
}
return result
}
console.log(diffObject(val1, val2))
There are edge cases that this cannot solve, such as recursive structures or non-plain objects.
Also, in your desired output, you keep using diff property, but in some cases, the objects have two numeric properties, so it would be overriden, I kept the original property name instead.

combine the two JSON Objects depending on the ids

I am trying to combine two JSON objects using angular and create a single object. somehow i am able to do it but a small glitch not able to find what can be done for that .. below are the two JSON objects:
var array1 = [
{
"personId" : 7,
"batchNumber": 213,
"name": "Mike",
"company":"abc"
}];
var array2 = [
{
"batchNumber": 213,
"role": "engineer"
},
{
"batchNumber": 213,
"role": "architect"
}];
i want the resultant json to be
var result = [
{
"personId" : 7,
"batchNumber": 213,
"name": "Mike",
"company":"abc",
"role": "engineer"
}
{
"personId" : 7,
"batchNumber": 213,
"name": "Mike",
"company":"abc",
"role": "architect"
}]
I am able to create only one json out of it and then the loop is terminated/ Any help would be grateful thanks in advance.
var array1 = [{
"personId" : 7,
"batchNumber": 213,
"name": "Mike",
"company":"abc"
}, {
"personId" : 8,
"batchNumber": 218,
"name": "julie",
"company":"tyu"
}];
var array2 = [{
"batchNumber": 213,
"role": "engineer"
},
{
"batchNumber": 213,
"role": "architect"
},{
"batchNumber": 218,
"role": "BSA"
},
{
"batchNumber": 218,
"role": "Manager"
}];
var newArray = [];
for (var i = 0; i < array1.length; i++) {
var obj = array1[i];
if (array2[i] && obj.box == array2[i]._id) {
for (key in array2[i]) {
obj[key] = array2[i][key];
}
newArray.push(obj);
}
};
console.log(newArray);
I'm attaching the JS fiddle as well: https://jsfiddle.net/n9pv4pL3/
My Controller:
var array1 = [
{
"personId" : 7,
"batchNumber": 213,
"name": "Mike",
"company":"abc"
}];
var array2 = [
{
"batchNumber": 213,
"role": "engineer"
},
{
"batchNumber": 213,
"role": "architect"
}];
var newArray = [], i,key;
for (var i = 0; i < array2.length; i++) {
var obj = array2[i];
if (array1[i] && obj. INPT_FILE_ID == array1[i].INPT_FILE_ID) {
for (key in array1[i]) {
obj[key] = array1[i][key];
}
newArray.push(obj);
}
};
console.log(newArray);
var array1 = [
{
"personId": 7,
"batchNumber": 213,
"name": "Mike",
"company": "abc"
}];
var array2 = [
{
"batchNumber": 213,
"role": "engineer"
},
{
"batchNumber": 213,
"role": "architect"
}];
var finalData = [];
array1.forEach(data => {
//filter batchwise data
var dataFromArray2 = array2.filter(arry2 => {
return arry2.batchNumber === data.batchNumber });
//pushing data in main array
dataFromArray2.forEach(batchData => {
finalData.push({
personId: data.personId,
batchNumber: batchData.batchNumber,
name: data.name,
company: data.company,
role: batchData.role
});
});
});
console.log(finalData)
here it's run fine,please check it once again,I have also added snippet for that
and about filter method
which is Returns the new filtered array.
in our case it's return new array with all properties of array2
if you want to understand in deap then refer this link
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Ok if you want to aggregate all roles under one key you could do it with 2 reduce functions like this:
let workingArray = array1.reduce((prevVal, currVal, index) => {
let role = array2.reduce((rPrevVal, rCurrVal, idx) => {
if (currVal.batchNumber === rCurrVal.batchNumber) {
rPrevVal.push(rCurrVal.role);
}
return rPrevVal;
}, []);
prevVal.push({
...currVal,
role
});
return prevVal;
}, []);
console.log(workingArray);
There's probably less complex solution, but what can I say I like reduce function:) Check fiddle here (in console) I added 2 different objects in first array and more objects in second:
https://fiddle.jshell.net/pegla/v4qqs3dk/2/

how to merge arrays which contains objects?

I have more than two arrays with objects which have some similar properties,
and I want to merge all arrays to one array and sort theme as bellow:
array = [
[ { "name" : "Jack", count: 10 }, { "name": "Fred", "count": 20 } ],
[ { "name": "Jack", count: 3 }, { "name": "Sara", "count": 10 } ]
]
merged_array = [
{ "name": "Fred", "count": 20 },
{ "name": "Jack", "count": 13 },
{ "name": "Sara", "count": 10 }
]
array
.reduce(function(result, arrayItem) {
result = result || [];
// sort also can be implemented here
// by using `Insertion sort` algorithm or anyone else
return result.concat(arrayItem);
})
.sort(function(a,b) { return a.count < b.count; })
You can use two forEach() to get array with merged objects and then sort it with sort().
var array = [
[{
"name": "Jack",
count: 10
}, {
"name": "Fred",
"count": 20
}],
[{
"name": "Jack",
count: 3
}, {
"name": "Sara",
"count": 10
}]
]
var result = [];
array.forEach(function(a) {
var that = this;
a.forEach(function(e) {
if (!that[e.name]) {
that[e.name] = e;
result.push(that[e.name]);
} else {
that[e.name].count += e.count
}
})
}, {})
result.sort(function(a, b) {
return b.count - a.count;
})
console.log(result)
If you are open to, then do take a look at to underscorejs, it is a wonderful utility for working with Arrays and objects as such. This will allow you to write flexible and transparent logic. The documentation is also quite good.
var arrays = [
[{
"name": "Jack",
count: 10
}, {
"name": "Fred",
"count": 20
}],
[{
"name": "Jack",
count: 3
}, {
"name": "Sara",
"count": 10
}]
]
var result = _.chain(arrays)
.flatten()
.union()
// whatever is the "key" which binds all your arrays
.groupBy('name')
.map(function(value, key) {
var sumOfCount = _.reduce(value, function(entry, val) {
// the fields which will need to be "aggregated"
return entry + val.count;
}, 0);
return {
'name': key,
'count': sumOfCount
};
}
)
// you can have your own sorting here
.sortBy('count')
.value();
console.log(result.reverse());
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Slice properties from objects and counting

Is it possible to slice single property from array of objects like
[{"name":"Bryan","id":016, "counter":0}, {"name":"John","id":04, "counter":2}, {"name":"Alicia","id":07, "counter":6}, {"name":"Jenny","id":015, "counter":9}, {"name":"Bryan","id":016, "counter":0}, {"name":"Jenny","id":015, "counter":9}, {"name":"John","id":04, "counter":2}, {"name":"Jenny" ,"id":015, "counter":9}];
I'm trying to slice name from every object and count number of the same elements (there are 3 objects with name Jenny) in order to achieve the following structure:
[{"name":"Bryan","Number":2},
{"name":"John","Number":2},
{"name":"Alicia","Number":1},
{"name":"Jenny","Number":3}]
Do you want to ignore the id and counter props already present?
You could create an object to keep track of the unique names, and convert back to an array in the end:
var data = [{"name": "Bryan", "id": 016, "counter": 0}, { "name": "John", "id": 04, "counter": 2}, { "name": "Alicia", "id": 07, "counter": 6}, { "name": "Jenny", "id": 015, "counter": 9}, { "name": "Bryan", "id": 016, "counter ": 0}, { "name": "Jenny", "id": 015, "counter ": 9}, { "name": "John", "id": 04, "counter": 2}, { "name": "Jenny", "id": 015, "counter": 9}];
var result = data.reduce(function(result, item) {
if (!result[item.name]) {
result[item.name] = {
name: item.name,
counter: 0
};
}
result[item.name].counter += 1;
return result;
}, {});
console.log(Object.keys(result).map(function(key) { return result[key] }));
You could use a hash table as a reference to the counted names.
var data = [{ name: "Bryan", id: "016", counter: 0 }, { name: "John", id: "04", counter: 2 }, { name: "Alicia", id: "07", counter: 6 }, { name: "Jenny", id: "015", counter: 9 }, { name: "Bryan", id: "016", counter: 0 }, { name: "Jenny", id: "015", counter: 9 }, { name: "John", id: "04", counter: 2 }, { name: "Jenny", id: "015", counter: 9 }],
grouped = [];
data.forEach(function (a) {
if (!this[a.name]) {
this[a.name] = { name: a.name, Number: 0 };
grouped.push(this[a.name]);
}
this[a.name].Number++;
}, Object.create(null));
console.log(grouped);
Give this a shot. We create a dictionary of names with their counts called nameDict, and iterate through the list to count them.
var arr = [{"name":"Bryan","id":"016", "counter":0}, {"name":"John","id":"04", "counter":2}, {"name":"Alicia","id":"07", "counter":6}, {"name":"Jenny","id":"015", "counter":9}, {"name":"Bryan","id":"016", "counter":0}, {"name":"Jenny","id":"015", "counter":9}, {"name":"John","id":"04", "counter":2}, {"name":"Jenny","id":"015", "counter":9}];
var nameDict = {};
for(var i = 0; i < arr.length; i++)
{
var name = arr[i].name;
if(nameDict[name] == undefined){
//haven't encountered this name before so we need to create a new entry in the dict
nameDict[name] = 1
} else {
//otherwise increment the count
nameDict[name] += 1
}
}
console.log(JSON.stringify(nameDict));

Categories

Resources