Find the difference between two complex objects containing array of objects - javascript

let baseObj = {
place: {city: 'Bangalore', pin: 123456},
office: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
}
let updatedObj = {
place: {city: 'Bangalore', pin: 99999},
office: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'Peter', age: 16, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
}
expected result = {
place: {city: 'Bangalore', pin: 99999},
office: [
{ name: 'Peter', age: 16, salutation: { title: 'Mr'}}
]
}
Note: comparison can be done by finding the properties of object and values but no comparison should be done hardcoding the properties
tried comparing the object but when we have an array of object i.e office, comparing with index(i.e 0,1) doesn't help as the array might not be sorted so couldn't proceed much
have tried the below code but it fails to get the desired output if the objects in an array are in different sequence as compared to the other array
ex. office1: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
office2: [
{ name: 'Tom', age: 22, salutation: { title: 'Mr'}},
{ name: 'Peter', age: 16, salutation: { title: 'Mr'}},
{ name: 'John', age: 31, salutation: { title: 'Mr'}}
]
function findDiff(obj1, obj2) {
var diffObj = Array.isArray(obj2) ? [] : {}
Object.getOwnPropertyNames(obj2).forEach(function(prop) {
if(prop !=='lenght' ){
if (typeof obj2[prop] === 'object') {
diffObj[prop] = obj1[prop]== undefined? obj2[prop]: findDiff(obj1[prop], obj2[prop])
if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
delete diffObj[prop]
}
}} else if(prop !=='lenght') {
if(obj1[prop] !== obj2[prop]){
diffObj[prop] = obj2[prop]
}
}
});
return diffObj
}

This compare function seems to achieve exactly what you want :
const baseObj = {"grade":"A","queue":"1","myCollections":{"myCollection":[{"commonCollection":[{"winterCollection":[{"name":"ICE","isChilled":"true"}]}]}]},"remarks":{"remark":[{"name":"GOOD","category":"A","text":{"value":"Very Good"},"indicator":"good"}]}}
const updatedObj = {"grade":"A","queue":"1","myCollections":{"myCollection":[{"commonCollection":[{"winterCollection":[{"name":"ICE","isChilled":"true"},{"code":"SNOW","isChilled":"true"}]}]}]},"remarks":{"remark":[{"name":"GOOD","category":"A","text":{"value":"Very Good"},"indicator":"good"},{"name":"BEST","text":{"value":"Test"},"category":"O","indicator":"outstanding"}]}}
const compare = (a, b, allObj = true) => {
if (typeof a !== 'object') return a === b ? null : b
if (Array.isArray(a)) {
const arr = []
b.forEach(e => {
if (a.every(el => compare(el, e, true) !== null))
arr.push(e)
});
return arr.length === 0 ? null : arr
} else {
const keys = Object.keys(b) // assuming a and b have the same properties
const obj = allObj ? b : {}
let changed = false
keys.map(key => {
const compared = compare(a[key], b[key], true)
if (compared) {
obj[key] = compared
changed = true
}
})
return changed ? obj : null
}
}
const expectedResult = {"grade":"A","queue":"1","myCollections":{"myCollection":[{"commonCollection":[{"winterCollection":[{"code":"SNOW","isChilled":"true"}]}]}]},"remarks":{"remark":[{"name":"BEST","text":{"value":"Test"},"category":"O","indicator":"outstanding"}]}}
const result = compare(baseObj, updatedObj)
console.log(result)
console.log(expectedResult)
console.log(JSON.stringify(result) === JSON.stringify(expectedResult))
PS: I compared each pair but it is O(n^2). The best way is if you had an id property on every of array children.

Related

How to loop through an object and then use array methods on its keys?

I see that all the loops for objects returns the key as string and the value, but I want to operate on the keys of the object itself. If I have this object:
const data = {
person1: [
{ id: 1, name: Mike, age: 24 },
{ id: 2, name: Bob, age: 31 }
],
person2: [
{ id: 3, name: Christin, age: 21 },
{ id: 4, name: Michelle, age: 33 }
],
}
const removePersonById = (id) => {
// Check which person the id belongs to and remove that person
const persons = Object.keys(data).map(person => ...)
}
I wanted to loop through data and run .includes on each person in order to remove them by the id, but I am at a loss on how to do that.
You can loop through all keys and delete that the person you want by id using the filter() method
const removePersonById = (id) => {
var all = Object.keys(data);
for(let person of all){
data[person] = data[person].filter(a => a.id!=id);
}
}
You could get the values and find the index. Then splice.
const
removePersonById = id => {
Object.values(data).forEach(a => {
const index = a.findIndex(o => o.id === id);
if (index !== 0) a.splice(index, 1);
});
};
You could use .some()
const data = {
person1: [
{ id: 1, name: "Mike", age: 24 },
{ id: 2, name: "Bob", age: 31 }
],
person2: [
{ id: 3, name: "Christin", age: 21 },
{ id: 4, name: "Michelle", age: 33 }
],
}
const removePersonById = (id) => {
// Check which person the id belongs to and remove that person
Object.keys(data).map(person => {
if (data[person].some(p => p.id === id)) delete data[person];
})
}
removePersonById(3)
console.log(data)
Use Object.entries() so you can iterate over the keys and values together. Then you can test the value to see if the id is found.
Then use the delete operator to remove that key from the object.
const removePersonById = id => {
delete data[id];
};
const data = {
person1: [
{ id: 1, name: "Mike", age: 24 },
{ id: 2, name: "Bob", age: 31 }
],
person2: [
{ id: 3, name: "Christin", age: 21 },
{ id: 4, name: "Michelle", age: 33 }
],
};
const removePersonById = (id) =>
Object.entries(data).forEach(([key, value]) => {
if (value.some(({id: personid}) => personid == id)) {
delete data[key];
}
});
removePersonById(3);
console.log(data);
let data = {
person1: [
{ id: 1, name: "Mike", age: 24 },
{ id: 2, name: "Bob", age: 31 }
],
person2: [
{ id: 3, name: "Christin", age: 21 },
{ id: 4, name: "Michelle", age: 33 }
],
}
const removePersonById = (id) => {
// Check which person the id belongs to and remove that person
data = Object.keys(data).reduce((acc,key) => {
if(data[key].some(person=>person.id===id)) return acc
acc[key]= data[key]
return acc
}, {})
console.log(`Remove person with ID ${id}: `,data)
}
removePersonById(1)

Angular/JS/Typescript- Recursively access properties in an object

Suppose I got an object defined as such:
const me = {
id: 1,
name: 'James',
age: 40,
family: {
mother: {
id: 101,
name: 'Bea',
age: 66
},
father: {
id: 102,
name: 'Martin',
age: 69
},
children: [
{
id: 11,
name: 'Tom',
age: 18,
},
{
id: 12,
name: 'Nancy',
age: 13,
},
],
},
}
How does one easily access a value by just giving an array of strings of the chained properties' names?
For example, calling:
search(me, ['family', 'father', 'age'])
which would be the same as:
me['family']['father']['age']
would return 69.
PS:
What about having search(me, ['family', 'children', 'name']) return ['Tom', 'Nancy']?
PSS:
Or even search(me, ['family', 'children', ['name', 'age']]) returning
[
{
name: 'Tom',
age: 18
},
{
name: 'Nancy',
age: 13
}
]
EDIT:
I went checking out lodash/deepdash libraries, but couldn't really figure it out by myself.
You can do that with this simple recursive function which takes an array as the query:
const me = {
id: 1,
name: 'James',
age: 40,
family: {
mother: {
id: 101,
name: 'Bea',
age: 66
},
father: {
id: 102,
name: 'Martin',
age: 69
},
children: [
{
id: 11,
name: 'Tom',
age: 18,
},
{
id: 12,
name: 'Nancy',
age: 13,
},
],
},
}
function search(obj, [first, ...rest]) {
return rest.length ? search(obj[first], rest) : obj[first];
}
const result = search(me, ['family', 'father', 'age']);
console.log(result);
Though it's bit lengthy, you could try this for all your combinations.
const me={id:1,name:'James',age:40,family:{mother:{id:101,name:'Bea',age:66},father:{id:102,name:'Martin',age:69},children:[{id:11,name:'Tom',age:18,},{id:12,name:'Nancy',age:13,},],},}
function search(data, searchPattern) {
const keys = [...searchPattern];
//picking last key and it's corresponding value
const lastKey = keys.pop();
const resultInst = keys.reduce((acc,key)=>{
return acc[key];
}, data);
// if it's array iterating it further to construct the response
if (Array.isArray(resultInst)) {
return resultInst.map(inst => {
if (Array.isArray(lastKey)) {
return lastKey.reduce((accInner,key) => {
accInner[key] = inst[key];
return accInner;
}, {});
} else {
return inst[lastKey];
}
});
} else {
// else just returning property's value
return resultInst[lastKey];
}
}
console.log(search(me, ['family', 'father', 'age']))
console.log(search(me, ['family', 'children', 'name']))
console.log(search(me, ['family', 'children', ['name', 'age']]))
Some interesting requirements you have there! Here is an all-in-one answer using object-scan and lodash
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/lodash#4.17.21"></script>
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.1.2/lib/index.min.js';
const myData = { id: 1, name: 'James', age: 40, family: { mother: { id: 101, name: 'Bea', age: 66 }, father: { id: 102, name: 'Martin', age: 69 }, children: [{ id: 11, name: 'Tom', age: 18 }, { id: 12, name: 'Nancy', age: 13 }] } };
const search = (data, needle) => objectScan([needle], {
reverse: false,
rtn: ['key', 'value'],
useArraySelector: false,
afterFn: (state) => {
if (state.result.length === 0) {
state.result = undefined;
} else if (needle.includes('{')) {
const prefixLength = lodash
.zip(...state.result.map(([k]) => k))
.findIndex((e) => new Set(e).size > 1);
const result = [];
state.result.forEach(([k, v]) => lodash.set(result, k.slice(prefixLength), v));
state.result = result;
} else if (
state.result.length === 1
&& state.result.every(([k]) => k.every((p) => typeof p === 'string'))
) {
state.result = state.result[0][1];
} else {
state.result = state.result.map(([k, v]) => v);
}
}
})(data);
console.log(search(myData, 'family.father.age'));
// => 69
console.log(search(myData, 'family.children.name'));
// => [ 'Tom', 'Nancy' ]
console.log(search(myData, 'family.children.{age,name}'));
// => [ { name: 'Tom', age: 18 }, { name: 'Nancy', age: 13 } ]
console.log(search(myData, 'family.father.unknown'));
// => undefined
</script>
Disclaimer: I'm the author of object-scan

How can I minimize a code function that finds repeated values in an array of objects in JavaScript?

I need to fix this function, which must find two similar names in an array of objects.
I tried to do this, and it's working, but the test tells me that it should be just only one loop and one if
function searchByName() {
const values = [
{ name: 'Johny Walker', birthDate: '1995-12-17' },
{ name: 'Andrew', birthDate: '2001-10-29' },
{ name: 'Viktor', birthDate: '1998-11-09' },
{ name: 'Andrew', birthDate: '2011-05-09' }
];
for (let obj of values) {
for (let elem of values) {
if (obj == elem)
continue;
if (elem.name === obj.name && elem.age === obj.age) {
console.log(obj);
break;
}
}
}
};
Here is the example that must come out:
[
{ name: 'Andrew', birthDate: '2001-10-29' },
{ name: 'Andrew', birthDate: '2011-05-09' }
]
Just push names to the array and stop when there is a duplicate:
let hasDuplicates = [];
values.forEach((e, idx) => {
if(!hasDuplicates.includes(e.name))
if(idx !== values.length-1) { hasDuplicates.push(e.name); }
else { hasDuplicates = false; }
else { hasDuplicates = e; }
});
And then you can use that variable:
if(hasDuplicates) {...}
You can find the duplication count by name key using Array.reduce function.
And from the duplication result, you can filter the duplicated ones only and show them using Array.filter & Array.map.
const values = [
{ name: 'Johny Walker', birthDate: '1995-12-17' },
{ name: 'Andrew', birthDate: '2001-10-29' },
{ name: 'Viktor', birthDate: '1998-11-09' },
{ name: 'Andrew', birthDate: '2011-05-09' }
];
let duplicates = values.reduce((acc, cur) => {
if (acc[cur.name]) {
acc[cur.name].push(cur.birthDate);
} else {
acc[cur.name] = [ cur.birthDate ];
}
return acc;
}, {});
duplicates = Object.entries(duplicates).filter(([name, birth]) => birth.length > 1).map(([name, birth]) => {
return birth.map((item) => ({
name,
birthDate: item
}));
});
console.log(duplicates);
You can use the reduce method to iterate over the array and push items into the array. Then we just filter the array by its length. So if the length is greater than 1, it means we have found a duplicate:
const result = values.reduce((a, c)=> {
a[c.name] = a[c.name] || {...c, items: []} ;
a[c.name].items.push(c);
return a;
}, { });
let result = Object.values(result).filter(f => f.items.length > 1).flatMap(s => s.items);
An example:
const values = [
{ name: 'Johny Walker', birthDate: '1995-12-17' },
{ name: 'Andrew', birthDate: '2001-10-29' },
{ name: 'Viktor', birthDate: '1998-11-09' },
{ name: 'Andrew', birthDate: '2011-05-09' }
];
const result = values.reduce((a, c)=> {
a[c.name] = a[c.name] || {...c, items: []} ;
a[c.name].items.push(c);
return a;
}, { });
console.log(Object.values(result).filter(f => f.items.length > 1).flatMap(s => s.items));

filter an arrays of objects from an array of strings

I'm aware there are similar questions but none have been able to help me thus far - filtering an array of objects from and array of strings relies on you knowing the key value pair you would like to match, and same with this question here
Say I have an array of objects like so..
let users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike';
},
favouriteFood: 'apples'
},
{
name: 'Steve',
age: 32,
pets null
favouriteFood: 'icecream'
},
{
name: 'Jason',
age: 31,
pets: null
favouriteFood: 'tacos'
},
{
name: 'Jason',
age: 31,
pets: {
cat: 'Jerry'
},
favouriteFood: 'bread'
},
]
now I would like to be able to filter the array of users by matching a string in any of the objects keys. For example I want to filter out anyone whos name isnt 'steve' - keep in mind I may also want to filter out anyone who isnt 42 or who's favourite food isn't 'apples'
filter(term) {
return objects.filter(x => {
for(let key of Object.keys(x)) {
if(typeof(x[key]) === 'object') {
return JSON.stringify(x[key]).toLowerCase().includes(t);
} else {
return x[key].toString().toLowerCase().includes(t);
}
}
});
}
now this function works but for only a single filter term
so If I ran filter('steve') I would then get
users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike';
},
favouriteFood: 'apples'
},
{
name: 'Steve',
age: 32,
pets null
favouriteFood: 'icecream'
}
]
as my result, but what If I wanted to filter out steve whos favourite food is apples?
I have tried to update my function as follows to loop through an array of terms and filter according to all the strings in the array
So I have tried
function filter(terms) {
return term.forEach((t) => {
return objects.filter(x => {
for(let key of Object.keys(x)) {
if(typeof(x[key]) === 'object') {
return JSON.stringify(x[key]).toLowerCase().includes(t);
} else {
return x[key].toString().toLowerCase().includes(t);
}
}
});
});
but when I run filter(['steve', 'apples'])
I get undefined
My desired result would be
users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike';
},
favouriteFood: 'apples'
}
]
I'm not entirely sure what I'm doing wrong or how I could fix this function so it works correctly.
Any help would be appreciated.
Filter by whether .every value in the array of values needed is included in the Object.values of a given user:
const filter = arrOfValsNeeded => users.filter(user => {
const vals = Object.values(user).map(val => typeof val === 'string' ? val.toLowerCase() : val);
return arrOfValsNeeded.every(needed => vals.includes(needed.toLowerCase()));
});
let users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike'
},
favouriteFood: 'apples'
},
{
name: 'Steve',
age: 32,
pets: null,
favouriteFood: 'icecream'
},
{
name: 'Jason',
age: 31,
pets: null,
favouriteFood: 'tacos'
},
{
name: 'Jason',
age: 31,
pets: {
cat: 'Jerry'
},
favouriteFood: 'bread'
},
]
console.log(filter(['steve', 'apples']));
Or, if you need to recursively find all primitive values:
const allPrimitives = obj => {
const primitives = [];
JSON.stringify(obj, (key, val) => {
if (typeof val !== 'object' || val === null) {
primitives.push(typeof val === 'string' ? val.toLowerCase() : val);
}
return val;
});
return primitives;
};
const filter = arrOfValsNeeded => users.filter(user => {
const vals = allPrimitives(user);
return arrOfValsNeeded.every(needed => vals.includes(needed.toLowerCase()));
});
let users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike'
},
favouriteFood: 'apples'
},
{
name: 'Steve',
age: 32,
pets: null,
favouriteFood: 'icecream'
},
{
name: 'Jason',
age: 31,
pets: null,
favouriteFood: 'tacos'
},
{
name: 'Jason',
age: 31,
pets: {
cat: 'Jerry'
},
favouriteFood: 'bread'
},
]
console.log(filter(['steve', 'apples']));
If you need partial matches as well, use vals.some instead of vals.includes so you can identify substrings:
const allStrings = obj => {
const strings = [];
JSON.stringify(obj, (key, val) => {
if (typeof val === 'string') {
strings.push(val.toLowerCase());
}
return val;
});
return strings;
};
const filter = arrOfValsNeeded => {
const lowerVals = arrOfValsNeeded.map(str => str.toLowerCase());
return users.filter(user => {
const existingStrings = allStrings(user);
return lowerVals.every(
lowerNeeded => existingStrings.some(
existingString => existingString.includes(lowerNeeded)
)
);
});
};
let users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike'
},
favouriteFood: 'apples'
},
{
name: 'Steve',
age: 32,
pets: null,
favouriteFood: 'icecream'
},
{
name: 'Jason',
age: 31,
pets: null,
favouriteFood: 'tacos'
},
{
name: 'Jason',
age: 31,
pets: {
cat: 'Jerry'
},
favouriteFood: 'bread'
},
]
console.log(filter(['steve', 'apples']));
Sorry for the late response. I kept trying to come up with recursive code that would work in almost any situation. I eventually found some really cool snippets here, which is where I derived the similar function from equals, with slight considerations for compatibility.
function similar(a, b){
if(a === b){
return true;
}
if(a instanceof Date && b instanceof Date){
return a.getTime() === b.getTime();
}
if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')){
return a === b;
}
if (a === null || a === undefined || b === null || b === undefined || a.prototype !== b.prototype){
return false;
}
return Object.keys(b).every(function(k){
return similar(a[k], b[k]);
});
}
let users = [
{
name: 'Steve',
age: 42,
pets: {
dog: 'spike'
},
favouriteFood: 'apples'
},
{
name: 'Steve',
age: 32,
pets: null,
favouriteFood: 'icecream'
},
{
name: 'Jason',
age: 31,
pets: null,
favouriteFood: 'tacos'
},
{
name: 'Jason',
age: 31,
pets: {
cat: 'Jerry'
},
favouriteFood: 'bread'
}
]
var testObj = {name:'Jason', age: 31, pets:{cat:'Jerry'}};
for(var i=0,u,l=users.length; i<l; i++){
u = users[i];
if(similar(u, testObj)){
console.log('contains testObj');
console.log(u);
}
else if(!similar(u, {pets:null}) && !similar(u, {pets:{dog:'spot'}})){
console.log('not spot');
console.log(u);
}
}
similar will see if anything matches exactly that is not an Object, or if it is an Object it will see if a contains b, considering that a and b have properties and values at the same depth and b doesn't consist of properties that do not exist in a.

How to find the value inside an array with objects with the most occuring value (deep mode)?

Let's say I have an array with objects like this:
const persons = [
{
name: "Erik",
age: 45
},
{
name: "Bob",
age: 37
},
{
name: "Erik",
age: 28
},
{
name: "Jasper",
age: 29
},
{
name: "Erik",
age: 34
}
];
How do I find the value based on a key with the most occuring value?
In this example, when passing name as key that would be Erik.
Something like this:
const deepMode = (array, key) => {
// Perhaps something with .reduce?
}
And when called, it should return:
deepMode(persons, "name"); // Returns "Erik"
You could take a Map, count the ocurrences and reduce the key/value pairs for getting the max valaue. Return the key without iterating again.
const
deepMode = (array, key) => Array
.from(array.reduce((m, o) => m.set(o[key], (m.get(o[key]) || 0) + 1), new Map))
.reduce((a, b) => a[1] > b[1] ? a : b)[0],
persons = [{ name: "Erik", age: 45 }, { name: "Bob", age: 37 }, { name: "Erik", age: 28 }, { name: "Jasper", age: 29 }, { name: "Erik", age: 34 }];
console.log(deepMode(persons, 'name'));
you can count keys by adding them in object and checking if key exists in object,then increment value, if not then add key into object, after that with Object.keys get keys of object sort them and get first element which is most occurring
const persons = [
{
name: "Erik",
age: 45
},
{
name: "Bob",
age: 37
},
{
name: "Erik",
age: 28
},
{
name: "Jasper",
age: 29
},
{
name: "Erik",
age: 34
}
];
const deepMode = (array, key) => {
const obj = {};
array.forEach(v => {
if (obj[v[key]]) {
obj[v[key]] += 1;
} else {
obj[v[key]] = 1;
}
});
return Object.keys(obj).sort((a,b) => obj[b]-obj[a])[0];
}
console.log(deepMode(persons, 'name'));
You could reduce into a Map, find the max, then find and return it.
function findMostOccuringKeyValue(arr, key) {
const grouped = arr.reduce((a, b) => a.set(b[key], (a.get(b[key]) || 0) + 1), new Map);
const max = Math.max(...grouped.values());
return [...grouped].find(([k, v]) => v === max)[0];
}
console.log(findMostOccuringKeyValue(persons, 'name'));
<script>
const persons = [
{
name: "Erik",
age: 45
},
{
name: "Bob",
age: 37
},
{
name: "Erik",
age: 28
},
{
name: "Jasper",
age: 29
},
{
name: "Erik",
age: 34
}
];
</script>

Categories

Resources