What's the most efficient way to compare values of two objects? - javascript

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))

Related

Implementing a set that treats object equality on value not reference

I want to be able to implement a Set in javascript that allows me to do something like this:
const s = Set([[1,2,3], [1,2,3], 1, 2, 1]);
s.add([1,2,3]);
console.log(s);
// {[1,2,3], 1, 2}
Of course, since the === operator is used on the set, any object will not equal itself unless a reference to the same object is passed, and so instead of the above we would currently get:
Set(5) { [ 1, 2, 3 ], [ 1, 2, 3 ], 1, 2, [ 1, 2, 3 ] }
Does the following seem like a good way to implement this? What might I be missing or can improve on?
class MySet extends Set {
constructor(...args) {
super();
for (const elem of args) {
if (!this.has(elem)) super.add(elem);
}
}
has(elem) {
if (typeof elem !== 'object') return super.has(elem);
for (const member of this) {
if (typeof member !== 'object') continue;
if (JSON.stringify(member) === JSON.stringify(elem))
return true;
}
return false;
}
add(elem) {
return (this.has(elem)) ? this : super.add(elem);
}
delete(elem) {
if (typeof elem !== 'object') return super.delete(elem);
for (const member of this) {
if (typeof member !== 'object') continue;
if (JSON.stringify(member) === JSON.stringify(elem))
return super.delete(member);
}
return false;
}
}
Assuming the provided objects don't contain values that cannot be stringified to JSON (function, undefined, symbol, etc.) You can use JSON.stringify().
One problem you might encounter is that stringifying { a: 1, b: 2 } doesn't produce the same result as { b: 2, a: 1 }. A fairly easy way to solve this would be to stringify the object and make sure the resulting JSON has properties placed in alphabetical order.
For this we can look to the answer provided in sort object properties and JSON.stringify.
I also think you are over complicating things by only stringifying values if they are an object. Instead you could just stringfy everything, null would result in "null", "string" would result in '"string"', etc. This simplifies the code by a lot. The only restriction then becomes that all values must be a valid JSON value.
// see linked answer
function JSONstringifyOrder(obj, space)
{
const allKeys = new Set();
JSON.stringify(obj, (key, value) => (allKeys.add(key), value));
return JSON.stringify(obj, Array.from(allKeys).sort(), space);
}
class MySet extends Set {
// The constructor makes uses of add(), so we don't need
// to override the constructor.
has(item) {
return super.has(JSONstringifyOrder(item));
}
add(item) {
return super.add(JSONstringifyOrder(item));
}
delete(item) {
return super.delete(JSONstringifyOrder(item));
}
}
const set = new MySet([[1,2,3], [1,2,3], 1, 2, 1]);
set.add([1,2,3]);
set.add({ a: { s: 1, d: 2 }, f: 3 });
set.add({ f: 3, a: { d: 2, s: 1 } });
// Stack Overflow snippets cannot print Set instances to the console
console.log(Array.from(set));
// or unserialized
Array.from(set, json => JSON.parse(json)).forEach(item => console.log(item));

Compare objects and remove equal values

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.

Nest/Group an array of objects by attribute, ignoring null/undefined and using additional property as label

I'm trying to nest-group an array of objects.
The function provided by this gist almost works as intended and uses lodash as basis:
https://gist.github.com/joyrexus/9837596
const _ = require('lodash');
function nest(seq, keys) {
if (!keys.length) return seq;
let [first, ...rest] = keys;
return _.mapValues(_.groupBy(seq, first), value => nest(value, rest));
}
This recursively,
However, there are two problems I face.
if a parameter is set to null or undefined, it is used as a group, instead the
an optional object attribute should be used as the final object key, so there are only objects, no arrays. This attribute always has to be unique in order to work correctly.
Is it possible to combine or extend the existing nest function to solve the above points?
The pros of this method is, that instead of the keys, I can also use an array of functions (p => p.parameterGroup1) to return the parameter. So instead of a last optional parameter, I could also use p => p.parameterGroup1 ? p.parameterGroup1 : p.label
I prepared a little test, to give you a better idea of what is expected:
test('nest array of objects by groups as keys, stopping at null and using a final label param', t => {
let properties = [
{
parameterGroup1: 'first',
parameterGroup2: 'second',
parameterGroup3: 'third',
label: 'A'
},
{
parameterGroup1: 'first',
parameterGroup2: 'second',
parameterGroup3: null,
label: 'B'
},
{
parameterGroup1: 'a',
parameterGroup2: 'b',
parameterGroup3: undefined,
label: 'C'
},
]
let expected = {
first: {
second: {
third: {
A: {
parameterGroup1: 'first',
parameterGroup2: 'second',
parameterGroup3: 'third',
label: 'A'
}
},
B: {
parameterGroup1: 'first',
parameterGroup2: 'second',
parameterGroup3: null,
label: 'B'
}
}
},
a: {
b: {
C: {
parameterGroup1: 'a',
parameterGroup2: 'b',
parameterGroup3: undefined,
label: 'C'
}
}
}
}
let grouped = nest(properties, ['parameterGroup1', 'parameterGroup2', 'parameterGroup3'], 'label')
t.deepEqual(grouped, expected)
})
Thank you in advance!
Here is a way to do it in vanilla js. We construct the result object by reduceing the array seq: For each object obj in the array seq, we walk the result object level by level using the values from obj of the keys from keys. If the value is null or undefined, we skip (won't go down another level). If the value exist we go down a level, creating a level (object) if it doen't already exist. We do that repeatedly using a reduce on the keys array untill we find the leaf object (last level), to which we assign our current object under the key obtained evaluating obj[last]:
function nest(seq, keys, last) {
return seq.reduce((result, obj) => {
// First we find the (last level) object to which we will assign our current object to, as a child
let lastLevel = keys.reduce((res, key) => { // for each key in keys
let value = obj[key]; // get the value from our current object obj for that key key
if(value == null) return res; // if the value is null or undefined, skip
if(res[value]) return res[value]; // if the level for value exists return it
return res[value] = {}; // if it doesn't, create a new level, assing it to result and return it
}, result);
// then we assign it using the value of the key last
lastLevel[obj[last]] = obj; // we found the last possible level, assign obj to it under the key obj[last]
return result;
}, {});
}
Example:
function nest(seq, keys, last) {
return seq.reduce((result, obj) => {
let lastLevel = keys.reduce((res, key) => {
let value = obj[key];
if(!value) return res;
if(res[value]) return res[value];
return res[value] = {};
}, result);
lastLevel[obj[last]] = obj;
return result;
}, {});
}
let properties = [{parameterGroup1: 'first',parameterGroup2: 'second',parameterGroup3: 'third',label: 'A'},{parameterGroup1: 'first',parameterGroup2: 'second',parameterGroup3: null,label: 'B'},{parameterGroup1: 'a',parameterGroup2: 'b',parameterGroup3: undefined,label: 'C'}];
let grouped = nest(properties, ['parameterGroup1', 'parameterGroup2', 'parameterGroup3'], 'label');
console.log(grouped);

_.assign only if property exists in target object

My need is to do something like an _.assign, but only if the target object already has the property being assigned. Think of it like the source objects may have some properties to contribute, but also some properties that I don't want to mix in.
I haven't ever used _.assign's callback mechanism, but tried the following. It 'worked', but it still assigned the property to the dest object (as undefined). I don't want it to assign at all.
_.assign(options, defaults, initial, function (destVal, sourceVal) {
return typeof destVal == 'undefined' ? undefined : sourceVal;
});
I wrote the following function to do this, but wondering if lodash already has something baked in that is more elegant.
function softMerge (dest, source) {
return Object.keys(dest).reduce(function (dest, key) {
var sourceVal = source[key];
if (!_.isUndefined(sourceVal)) {
dest[key] = sourceVal;
}
return dest;
}, dest);
}
You could take just the keys from the first object
var firstKeys = _.keys(options);
Then take a subset object from the second object, taking only those keys which exist on the first object :
var newDefaults = _.pick(defaults, firstKeys);
Then use that new object as your argument to _.assign :
_.assign(options, newDefaults);
Or in one line :
_.assign(options, _.pick(defaults, _.keys(options)));
Seemed to work when I tested it here : http://jsbin.com/yiyerosabi/1/edit?js,console
Here is a immutable deep version, I call it "merge that retains the shape", in TypeScript that uses lodash:
function _mergeKeepShapeArray(dest: Array<any>, source: Array<any>) {
if (source.length != dest.length) {
return dest;
}
let ret = [];
dest.forEach((v, i) => {
ret[i] = _mergeKeepShape(v, source[i]);
});
return ret;
}
function _mergeKeepShapeObject(dest: Object, source: Object) {
let ret = {};
Object.keys(dest).forEach((key) => {
let sourceValue = source[key];
if (typeof sourceValue !== "undefined") {
ret[key] = _mergeKeepShape(dest[key], sourceValue);
} else {
ret[key] = dest[key];
}
});
return ret;
}
function _mergeKeepShape(dest, source) {
// else if order matters here, because _.isObject is true for arrays also
if (_.isArray(dest)) {
if (!_.isArray(source)) {
return dest;
}
return _mergeKeepShapeArray(dest, source);
} else if (_.isObject(dest)) {
if (!_.isObject(source)) {
return dest;
}
return _mergeKeepShapeObject(dest, source);
} else {
return source;
}
}
/**
* Immutable merge that retains the shape of the `existingValue`
*/
export const mergeKeepShape = <T>(existingValue: T, extendingValue): T => {
return _mergeKeepShape(existingValue, extendingValue);
}
And a simple test to see how I vision such merge should work:
let newObject = mergeKeepShape(
{
a : 5,
// b is not here
c : 33,
d : {
e : 5,
// f is not here
g : [1,1,1],
h : [2,2,2],
i : [4,4,4],
}
},
{
a : 123,
b : 444,
// c is not here
d : {
e : 321,
f : 432,
// g is not here
h : [3,3,3],
i : [1,2],
}
}
);
expect(newObject).toEqual({
a : 123,
// b is not here
c : 33,
d : {
e : 321,
// f is not here,
g : [1,1,1],
h : [3,3,3],
i : [4,4,4]
}
});
I used seamless-immutable myself in the test, but didn't see a need to put it in this answer.
I hereby place this in the Public Domain.
Another way to accomplish this is by combining _.mapObject with _.has
_.mapObject(object1, function(v, k) {
return _.has(object2, k) ? object2[k] : v;
});
Explanation:
Traverse all key/value pairs of object1 using _.mapObject
Using _.has, check if property name k also exists in object2.
If it does, copy the value assigned to key object2's k back to object1, else, just return the existing value of object1 (v).
Plunkr
Following #svarog's answer I came up with this (lodash version 4.17.15):
const mergeExistingProps = (target, source) => _.mapValues(target, (value, prop) => _.get(source, prop, value));
I recently have the same need in my personal project, I need to fill the value from one object(SOURCE) to another object(TARGET) but don't expand its property. Also, some additional requirements should be met:
Any property with a null value in the source will not update to the target;
Any value from the source can be updated into target if such property in target has null value.
The property that holds an array in the target will be loaded based on data from the source, but all entries of the array will remain the same as the target array (so an empty array in the target will not get any data since the item has no property)
Property of the target holding a 2-d array (array has another array as its item) will not be updated, since the meaning of merging two 2-d arrays with a different shape is not clear to me.
Below is an example (Detailed explained in the code):
Assume you have a resume object holding all the data about you, you want to fill the data into the company's application form (also an object). You want the result to have the identical shape of the application form since the company doesn't care about other things, then you can think your resume is SOURCE and the application form is TARGET.
Note that the "additional" field in TARGET is null, which means anything can be updated here based on SOURCE data (As rule #2)
The console output is in JSON format, copy it to some JSON to JS-OBJ converter such as
https://www.convertsimple.com/convert-json-to-javascript/
to have a better view
const applicationForm = {
name: 'Your Name',
gender: 'Your Gender',
email: 'your#email.com',
birth: 0,
experience: [ // employer want you list all your experience
{
company: 'Some Company',
salary: 0,
city: ['', '', ''], // list all city worked for each company
}
],
language: { // employer only care about 2 language skills
english: {
read: false,
write: false,
speak: 'Speak Level'
},
chinese: {
read: false,
write: false,
speak: 'Speak Level'
}
},
additional: null // add anything you want the employer to know
}
const resume = {
name: 'Yunfan',
gender: 'Male',
birth: 1995,
phone: '1234567',
email: 'example#gmail.com',
experience: [
{
company: 'Company A',
salary: 100,
city: ['New York', 'Chicago', 'Beijing'],
id: '0001',
department: 'R&D'
},
{
company: 'Company B',
salary: 200,
city: ['New York'],
id: '0002',
department: 'HR'
},
{
company: 'Company C',
salary: 300,
city: ['Tokyo'],
id: '0003',
}
],
language: {
english: {
read: true,
write: true,
speak: 'Native Speaker'
},
chinese: {
read: true,
write: false,
speak: 'HSK Level 3'
},
spanish: {
read: true,
write: true,
speak: 'Native Speaker'
}
},
additional: {
music: 'Piano',
hometown: 'China',
interest: ['Cooking', 'Swimming']
}
}
function safeMerge(source, target) {
// traverse the keys in the source object, if key not found in target or with different type, drop it, otherwise:
// 1. Use object merge if the value is an object (Can go deeper inside the object and apply same rule on all its properties)
// 2. Use array merge if value is array (Extend the array item from source, but keep the obj format of target)
// 3. Assign the value in other case (For other type, no need go deeper, assign directly)
for (const key in source) {
let value = source[key]
const targetValueType = typeof target[key]
const sourceValueType = typeof value
// if key not found in target or type not match
if (targetValueType === 'undefined' || targetValueType !== sourceValueType) {
continue // property not found in target or type not match
}
// for both type in object, need additional check
else if (targetValueType === 'object' && sourceValueType === 'object') {
// if value in target is null, assign any value from source to target, ignore format
if (target[key] === null) {
target[key] = source[key]
}
// if value in target is array, merge the item in source to target using the format of target only if source value is array
else if (Array.isArray(target[key]) && Array.isArray(value)) {
target[key] = mergeArray(value, target[key])
}
// if value in target is 'real' object (not null or array)', use object merge to do recurring merge, keep target format
else if (!Array.isArray(target[key])){
if (!Array.isArray(value) && value !== null) {
safeMerge(value, target[key])
}
}
}
// if target value and source value has same type but not object, assign directly
else if (targetValueType === sourceValueType) {
target[key] = value
}
}
}
function mergeArray(sourceArray, targetArray) {
// the rule of array merge need additional declare, assume the target already have values or objects in save format in the property<Array>,
// otherwise will not merge item from source to target since cannot add item property,
// NOTE: the item in target array will be totally overwrite instead of append on the tail, only the format will be keep,
// so the lenth of this property will same as source, below is a example:
// target = [{a: 1, b: 2}, {a: 3, b: 4}] // Must in same format, otherwise the first one will be standard
// source = [{a: 5, b: 6, c: 7}]
// mergeArray(source, target) => [{a: 5, b: 6}] // use format of target, but data from source
// double check both of values are array
if (!Array.isArray(sourceArray) || !Array.isArray(targetArray)) {
return
}
// if target array is empty, don't push data in, since format is empty
if (targetArray.length === 0) {
return
}
let resultArray = [] // array to save the result
let targetFormat = targetArray[0]
let targetArrayType = typeof targetArray[0]
// assign value from source to target, if item in target array is not object
if (targetArrayType !== 'object'){
sourceArray.forEach((value) => {
// assign value directly if the type matched
if (targetArrayType === typeof value) {
resultArray.push(value)
}
})
}
// if the item in target is null, push anything in source to target (accept any format)
else if (targetArray[0] === null) {
sourceArray.forEach((value) => {
resultArray.push(value)
})
}
// if the item in target is array, drop it (the meaning of merge 2-d array to a 2-d array is not clear, so skip the situation)
else if (!Array.isArray(targetArray[0])){
// the item is a 'real' object, do object merge based on format of first item of target array
sourceArray.forEach((value) => {
safeMerge(value, targetFormat) // data in targetFormat keep changing, so need to save a independent copy to the result
resultArray.push(JSON.parse(JSON.stringify(targetFormat)))
})
}
else {
console.log('2-d array will be skipped')
}
// replace the value of target with newly built array (Assign result to target array will not work, must assign outside)
return resultArray
}
safeMerge(resume, applicationForm)
console.log(JSON.stringify(applicationForm))

how to check if all object keys has false values

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

Categories

Resources