I want to be able to compare two objects and remove the properties which values has not been changed.
prevObject = {
isActive: true,
userName: 'Patric',
settings: {
isUser: true
}
}
nextObject = {
isActive: true,
userName: 'Patric Surname',
settings: {
isUser: false
}
}
The result when comparing above should be:
{
userName: 'Patric Surname',
settings: {
isUser: false
}
}
I've tried some for in loops and such, but I haven't gotten it to work recursively. Sorry, I don't have any code to show that I've tried. I trashed it since I was a bit frustrated. I'm also stuck to pure ES5 on this one. So no lodash or underscore :)
Would be really glad for some help!
You'll need a recursive approach:
In ECMAScript-5 syntax:
function isObject(a) {
return typeof a === "object" && a;
}
function diff(a, b) {
var res = b instanceof Array ? [] : {};
for (var prop in b) {
if (!(prop in a) || (!isObject(a[prop]) || !isObject(b[prop])) && a[prop] !== b[prop]) {
res[prop] = b[prop];
} else {
var obj = diff(a[prop], b[prop]);
// If the recursive result is not an empty object, then use it
for (var _ in obj) {
res[prop] = obj;
break; // We only need 1 iteration
}
}
}
return res;
}
var prevObject = {isActive: true,userName: 'Patric',settings: {isUser: true }};
var nextObject = { isActive: true,userName: 'Patric Surname',settings: {isUser: false}};
console.log(diff(prevObject, nextObject));
This function has some limited support for arrays. Array elements are only considered the same if their content is the same and the index where they occur in the array. This means that a resulting array may have gaps ("sparse").
var prevObject = {
isActive: true,
userName: 'Patric',
settings: {
isUser: true
}
};
var nextObject = {
isActive: true,
userName: 'Patric Surname',
settings: {
isUser: false
}
};
var changes = Object.entries(nextObject).reduce((acc,cv)=>{
if(JSON.stringify(nextObject[cv[0]]) != JSON.stringify(prevObject[cv[0]]))
acc.push(cv);
return acc;
},[]).reduce((acc,cv)=>{
acc[cv[0]]=cv[1];
return acc;
},{});
console.log(changes);
Assuming both prevObject and nextObject have the same properties.
Object.entries of nextObject returns an Array of [key, value]s.
Initialize return value of reduce on that Array to an empty Array - [].
Fill it with properties if their JSON.stringify are different.
JSON.stringify comparison relieves us from checking type, etc. For example: [1,2,3] is NOT equal [1,2,3], but JSON.stringify([1,2,3])==JSON.stringify([1,2,3]) IS. Same for objects, etc.
We use reduce on the last created Array using {} as initial value, filling with properties (cv[0]) and their values (cv[1]) back into an Object.
Related
Let's say I have two objects, which might have some properties in common. The overall idea is that one is used as a schema to compare it to the other. I would like to compare values of common properties and construct a new object representing these comparisons of each individual property. In the result, any properties in common should have the value of comparing the source properties for equality (e.g. result.a.b.c = (obj1.a.b.c == obj2.a.b.c)). Any properties that exist on only one of the two objects would be copied to the result with their original values.
For example, consider the following objects to compare:
const schema = {
num: 123,
str: 'hello',
nested: {
num: 123,
str: 'hello',
},
},
doc = {
nums: [1,2,3],
str: 'hello',
nested: {
num: 123,
str: 'world',
foo: 'bar',
},
};
The comparison result should be:
{
nums: [1,2,3], // obj2.nums
num: 123, // obj1.num
str: true, // obj1.str == obj2.str
nested: {
num: true, // obj1.nested.num == obj2.nested.num
str: false, // obj1.nested.str == obj2.nested.str
foo: 'bar', // obj2.nested.foo
},
}
How would I perform this comparison in the most efficient time and space way?
Currently I have this implementation:
function deepCompare(obj1, obj2) {
const validatedObject = {}
Object.keys(obj1).forEach(e => {
if(object[e].constructor.name === 'Object') {
validatedObject[e] = deepCompare(obj1[e], obj2[e])
} else {
validatedObject[e] = obj1[e] === obj2[e]
}
})
return validatedObject
}
Are there any ways to do this more efficiently without using JSON.stringify, because I might have property that is missing, but I still want to return the fact that the values are correct despite the schema of those objects?
Maybe for in or for of loop? But how would I test their effectiveness? It's okay for small objects, but sometimes they get really big and I want to compare them in the most efficient way possible.
NOTE for editors.
I understand that this might seem like a duplicate, but it in fact is not, here I'm looking for a most efficient way to compare values, iteratively, recursively or some other way, not objects themselves
the logic is if obj1 has a key and obj2 has the same key, we compare the values of those keys
Then you should use
function compare(a, b) {
if (a === b) return true;
if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) {
return Object.keys(a).every(k =>
!(k in b) || compare(a[k], b[k])
);
}
return false;
}
I think your solution is good enough, it's only missing some default checks (you don't need to iterate through the objects keys if you have the same refference for example).
Also, there's this package that claims is the fastest deep equal checker.
You can look over the code to see if you missed any other condition (it's still a recursive solution for the objects, but there are some other data types treated there if you're interested).
There is also this article if you prefer written material instead of code that has more checks before recursive calls
What you want to do is first find the common object keys. Then as you loop through the properties you want to return false as soon as you find a mismatch. In the snippet below I'm using an every loop which will stop as soon as one iteration returns false. If you return a value in the if block you do not need the else block.
function deepCompare(obj1, obj2) {
const commonKeys = Object.keys(obj1).filter(x => Object.keys(obj2).includes(x));
let valid = true;
commonKeys.every(e => {
if (obj1[e].constructor.name === 'Object') {
console.log('object', e, obj1[e], obj2[e])
valid = deepCompare(obj1[e], obj2[e])
return valid // true goes to next, false ends the .every loop
}
console.log('not object', e, obj1[e] === obj2[e] ? 'match' : 'mismatch')
valid = obj1[e] === obj2[e]
return valid // true goes to next, false ends the .every loop
})
return valid // returns the first false or true if everything passes
}
const obj1 = {
num: 123,
str: 'hello',
nested: {
num: 123,
str: 'hello',
}
}
const obj3 = {
num: 1233,
str: 'hello'
}
const obj4 = {
num: 123,
str: 'hello',
nested: {
num: 123,
str: 'hellure',
}
}
console.log(deepCompare(obj1, obj1.nested))
console.log(deepCompare(obj1, obj3))
console.log(deepCompare(obj1, obj4))
How to filter index value in foreach Javascript with condition if
last_login = null
then store the filtered values to
this.searchLogin
Image below is the ouput of: console.log(obj);
Here' what I've done so far:
try {
this.searchTrigger = true
var obj = this.list;
this.searchLogin = [];
for (let i=0; i < obj.length; ++i){
if(obj.filter( x => obj[i].profile.last_login === null)){
this.searchLogin.push(obj[i]);
}
}
console.log('This are obj data passed to search login',this.searchLogin);
var obj = [
{ profile: { last_login: null } },
{ profile: { last_login: true } },
{ profile: { last_login: null } },
{ profile: { last_login: true } },
{ profile: { last_login: null } }]
//With filter and map
console.log("With filter and map: ",
obj.map((x, i) => ({ ...x.profile, i }))
.filter(x => !x.last_login)
.map(x => x.i));
//With reduce
console.log("With reduce: ",
obj.reduce((p, c, i) => (!c.profile.last_login && p.push(i), p), [])
)
I think no need to save the results explicitly to an array again, the filter on array by itself will create an results array based on the filter condition.
For more on filter,
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
In your case,
this.searchLogin = obj.filter( x => x.profile.last_login === null);
would do the trick.
There are some points that need to be noted
filter function will return an array and even if it empty (no matched results), it will still be evaluated as true i.e. your if condition will always be executed
The if condition has a filter function inside a for loop which means that you are unnecessarily checking condition for same object for all the objects in array i.e. for each object the filter function will either return empty array or array of all the objects.
You can use Array.reduce and store the last_login information in the resultant array
this.searchLogin = this.list.reduce((a,c) => c.profile.last_login === null ? a.concat(c.profile.last_login) : a, []);
I have a problem with filtering object's values by true/false, just like in this topic Get object keys for filtered values but without underscore, meaning using exclusively plain JS. There are many more or less similar questions on StackOverflow, but unfortunately I failed to set a working approach.
For example, I have an object:
var anObject = {first_property: false, second_property: true, third_property: false, fourth_property: false, fifth_property: false, sixth_property: true, seventh_property: false, eight_property: nine-nth_property: false}
I need to get a new object, featuring exclusively truthy values, like:
var newObject = { second_property: true, sixth_property: true }
To filter, I wrote the following filter:
function isValid(value) {
if (typeof value === "undefined" || value == null || value.length == 0 || value == false) {
return false;
} else {
return true;
}
}
Broke my head and spent a few hours trying different approach, however the result is unsatisfactory. How should I built the algorithm to do this, which custom/out-of-the-box functions is worth using here? Thanks in advance!
You need to loop over the key/value pairs and find out which are true. The solution works as follows:
Gets the key/value pairs from the object using Object.entries.
Iterates over the entries using Array.reduce.
In each iteration, checks if value is true. If so, then adds the key/value pair to the result.
var obj = {
first_property: false,
second_property: true,
third_property: false,
fourth_property: false,
fifth_property: false,
sixth_property: true,
seventh_property: false
};
var res = Object.entries(obj)
.reduce((result, [key, value]) => {
if (value) {
result[key] = value;
}
return result;
}, {})
console.log(res);
There are a few ways to do this using Object.entries() or Object.keys(), different array methods, etc, but I figured I'd provide one. Filter the keys down to those where the value is true, and then add those keys to the output.
var obj = {
first_property: false,
second_property: true,
third_property: false,
fourth_property: false,
fifth_property: false,
sixth_property: true,
seventh_property: false
};
var output = {};
Object.keys(obj) //Get all keys from the object ['first_property', 'second_property', ...]
.filter((key) => obj[key]) //Filter the list of keys down to only those where their value is true
.forEach((key) => output[key] = obj[key]); //For each key in our filtered list, add it to the output object
console.log(output);
Also it is possible by reducing Object.keys array (without additional filtering):
Object.keys(obj).reduce((acc, key) =>
((obj[key] ? acc[key] = obj[key] : null), acc)
, {});
// {second_property: true, sixth_property: true}
And a bit shorter version:
Object.keys(obj).reduce((acc, key) => (obj[key] && (acc[key] = obj[key]), acc), {});
You can filter (Array.prototype.filter) the truthy keys and create a new object (Array.prototype.reduce) with those:
var obj = {first_property: false,second_property: true,third_property: false,fourth_property: false,fifth_property: false,sixth_property: true,seventh_property: false};
var result = Object.keys(obj).filter(k => obj[k]).reduce((a,k) => (a[k] = obj[k], a), {});
console.log(result);
I'm trying to update an deeply nested object without overriding existing properties, but can't find an elegant way to do this, for example:
const data = {
items: {
one: {
active: false,
id: '123'
},
two: {
active: true
}
}
}
const updatedData = {
items: {
one: {
active: true
}
}
}
The end result should be:
{
items: {
one: {
active: true,
id: '123'
},
two: {
active: true
}
}
}
However, using Object.assign or spread operator, will replace items.one with only {active: true} and not retain the id. Is there a way to do this without recursively going through the object?
function merge(source, into) {
for(let key in into){
if(typeof into[key] === "object") {
merge(source[key] || (source[key] = {}), into[key]);
} else {
source[key] = into[key];
}
}
}
A recursive function makes it pretty simple. Iterate the properties of the new data. If both the target and source for a property reference an object, call the function recursively with the two objects. Otherwise just assign the source value to the target value.
const data = {
items: {
one: {
active: false,
id: '123'
},
two: {
active: true
}
}
}
const updatedData = {
items: {
one: {
active: true
}
}
}
updateWith(data, updatedData);
console.log(data);
function updateWith(target, newData) {
for (const [k, v] of Object.entries(newData)) {
if (typeof v === "object" && typeof target[k] === "object") {
updateWith(target[k], v);
} else {
target[k] = v;
}
}
}
Given that you reference an arbitrary data.items[key] object, you can do the following:
data.items[key] = Object.assign({}, data.items[key], updatedData.items[key]);
The above will replace the old object value with a new one, with properties copied from the original and ultimately replacing any properties provided by the updated data.
JS Object:
var saver = {
title: false,
preview: false,
body: false,
bottom: false,
locale: false
};
The question is how to check if all values is false?
I can use $.each() jQuery function and some flag variable, but there may be a better solution?
Updated version. Thanks #BOB for pointing out that you can use values directly:
Object.values(obj).every((v) => v === false)
Also, the question asked for comparison to false and most answers below return true if the object values are falsy (eg. 0, undefined, null, false), not only if they are strictly false.
This is a very simple solution that requires JavaScript 1.8.5.
Object.keys(obj).every((k) => !obj[k])
Examples:
obj = {'a': true, 'b': true}
Object.keys(obj).every((k) => !obj[k]) // returns false
obj = {'a': false, 'b': true}
Object.keys(obj).every((k) => !obj[k]) // returns false
obj = {'a': false, 'b': false}
Object.keys(obj).every((k) => !obj[k]) // returns true
Alternatively you could write
Object.keys(obj).every((k) => obj[k] == false)
Object.keys(obj).every((k) => obj[k] === false) // or this
Object.keys(obj).every((k) => obj[k]) // or this to return true if all values are true
See the Mozilla Developer Network Object.keys()'s reference for further information.
This will do the trick...
var result = true;
for (var i in saver) {
if (saver[i] === true) {
result = false;
break;
}
}
You can iterate objects using a loop, either by index or key (as above).
If you're after tidy code, and not repeating that then simply put it in a function...
Object.prototype.allFalse = function() {
for (var i in this) {
if (this[i] === true) return false;
}
return true;
}
Then you can call it whenever you need, like this...
alert(saver.allFalse());
Here's a working sample...
Object.prototype.allFalse = function() {
for (var i in this) {
if (this[i] === true) return false;
}
return true;
}
var saver = {
title: false,
preview: false,
body: false,
bottom: false,
locale: false
};
console.log("all are false - should alert 'true'");
console.log(saver.allFalse());
saver.body = true;
console.log("one is now true - should alert 'false'");
console.log(saver.allFalse());
In a comment you ask if you can avoid iteration. You can if you use a javascript library supporting a functional approach, like Underscore, Lodash or Sugar.
With Underscore and Lodash you can write something like this:
var result = _.every(_.values(saver), function(v) {return !v;});
With Sugar you can simply write:
var result = Object.all(saver,false);
Use array.some()
It's more clean and understandable! And it can save us running time, because once the function condition exist once, it goes out of the loop and returns true.
Object.values(obj).some(val => val)
if you actually need strict equality to false write this:
Object.values(obj).some(val => val !== false)
Object.values(obj) make an array with the values of each key.
Short and handy one-liner, fully supported by browsers:
Object.keys(saver).every(k => saver[k] === false);
or
Object.values(saver).every(v => v === false);
(careful tho, Object.values() is not supported by IE yet.)
This should work on all major browsers:
Object.keys(saver).every(key => saver[key] === false); // return true
Do like this,
for (var i in saver) {
if (saver[i]) {
return false; // here if any value is true it wll return as false /
}
}
return true; //here if all value is false it wll return as true
If you want to do it without external iteration (i.e. in your code), try mapping the properties to an array with $.map then using $.inArray to see if any true values exist:
var allFalse = $.inArray(true, $.map(saver, function(obj){return obj})) < 0;
JSFiddle: http://jsfiddle.net/TrueBlueAussie/FLhZL/1/
With lodash you could also do const allFalse = !_.some(saver);
Lodash (3.10.1+) makes this even cleaner to express explicitly:
_.every({a: false, b: false, c: false}, _.negate(Boolean)); // True
But using _.some per ngstschr's answer is more succinct.
✏️ This one-liner checks if there's a falsy value inside any object of an array of objects:
const hasFalsyValue = (list) =>
!list.every(obj => Object.values(obj).every(prop => prop))
I find this one useful to prevent null / falsy values from being passed further on:
const listA = [ { a:'🍎', b:100 }, { a:'🍌', b:200 } ]
const listB = [ { a:null, b:100 }, { a:'🍌', b:200 } ]
// hasFalsyValue(listA) === false
// hasFalsyValue(listB) === true
There's just one detail we should be aware of:
⚠️ In Javascript 0 and '' are Falsy values! ⚠️
So if hasFalsyValue() finds any zero value or any empty string value inside an object in the array, it will consider it a falsy value and thus return true!
...While this might be what you want, sometimes you might want to allow any particular Falsy value to be considered as Truthy.
✍🏽 Say you want to allow zero values in your objects, you can do the following:
const hasFalsyValue_ZeroAllowed =
(list) => !list.every(obj => Object.values(obj).every(prop => prop || prop === 0))
Now zero values won't be considered falsy anymore:
const listC = [ { a:0, b:100 }, { a:'🍌', b:200 } ]
const listD = [ { a: null, b:100 }, { a:'🍌', b:200 } ]
hasFalsyValue_ZeroAllowed(listC) // false
hasFalsyValue_ZeroAllowed(listD) // true
And you can keep on adding further conditions to the function for a tailored validation:
✍🏽 To allow null values:
const hasFalsyValue_NullAllowed =
(list) => !list.every(obj => Object.values(obj).every(prop => prop || prop === null))
const listE = [ { a: null, b:100 }, { a:'🍌', b:200 } ]
hasFalsyValue_NullAllowed(listE) // false
✍🏽 To allow empty string values:
const hasFalsyValue_EmptyStringAllowed =
(list) => !list.every(obj => Object.values(obj).every(prop => prop || prop === ''))
const listF = [ { a: '', b:100 }, { a:'🍌', b:200 } ]
hasFalsyValue_EmptyStringAllowed(listF) // false
As of Lodash 4.0, overEvery can be used
overEvery(saver, false) loops through every element & checks if its false
It returns true if every element is false otherwise returns false