Object's Deep Compare - javascript

I wrote this function to deeply compare two objects in JavaScript
Equal: function(obj1, obj2) {
var keys1 = Object.keys(obj1).sort();
var keys2 = Object.keys(obj2).sort();
if (keys1.length !== keys2.length) {
return false;
}
// first make sure have same keys.
if (!keys1.every(function(k, i) {
return (k === keys2[i]);
})) {
return false;
}
return keys1.every(function(kk) {
var v1 = obj1[kk];
var v2 = obj2[kk];
if (Array.isArray(v1)) {
return this.EqualArr(v1, v2);
} else if (typeof v1 === "object" && v1 !== null) {
return this.Equal(v1, v2);
} else {
return v1 === v2;
}
});
},
But I got this error:
Cannot convert undefined or null to object
The problem happens at this line:
var keys2 = Object.keys(obj2).sort();
Can anyone help me ?

Since you're already using the UI5 framework, you can make use of the built-in comparator jQuery.sap.equal.
jQuery.sap.equal(a, b, maxDepth?, contains?)
Compares the two given values for equality, especially takes care not to compare arrays and objects by reference, but compares their content.
Here is an example:
sap.ui.require([
"jquery.sap.global"
], jQuery => console.log(jQuery.sap.equal({
a: "I'm A",
b: {
property: "I'm B",
c: {
property: "I'm C",
array: [1, 2, 3],
date: new Date("2018-02-28"),
},
},
}, {
a: "I'm A",
b: {
property: "I'm B",
c: {
property: "I'm C",
array: [1, 2, 3],
date: new Date("2018-02-28"),
},
},
})));
console.clear();
<script id="sap-ui-bootstrap" src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"></script>
How UI5 actually compares the two given objects
Other test cases

The problem here is the invocation to Object.keys
This method does not accept null or undefined as argument. To avoid that you can check objects passed are not falsy.
if (!obj1 && !obj2) {
return false
}

Related

Comparing items in ojects in two arrays js [duplicate]

I want to compare 2 arrays of objects in JavaScript code. The objects have 8 total properties, but each object will not have a value for each, and the arrays are never going to be any larger than 8 items each, so maybe the brute force method of traversing each and then looking at the values of the 8 properties is the easiest way to do what I want to do, but before implementing, I wanted to see if anyone had a more elegant solution. Any thoughts?
As serialization doesn't work generally (only when the order of properties matches: JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1})) you have to check the count of properties and compare each property as well:
const objectsEqual = (o1, o2) =>
Object.keys(o1).length === Object.keys(o2).length
&& Object.keys(o1).every(p => o1[p] === o2[p]);
const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false
If you need a deep comparison, you can call the function recursively:
const obj1 = { name: 'John', age: 33, info: { married: true, hobbies: ['sport', 'art'] } };
const obj2 = { age: 33, name: 'John', info: { hobbies: ['sport', 'art'], married: true } };
const obj3 = { name: 'John', age: 33 };
const objectsEqual = (o1, o2) =>
typeof o1 === 'object' && Object.keys(o1).length > 0
? Object.keys(o1).length === Object.keys(o2).length
&& Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
: o1 === o2;
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false
Then it's easy to use this function to compare objects in arrays:
const arr1 = [obj1, obj1];
const arr2 = [obj1, obj2];
const arr3 = [obj1, obj3];
const arraysEqual = (a1, a2) =>
a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx]));
console.log(arraysEqual(arr1, arr2)); // true
console.log(arraysEqual(arr1, arr3)); // false
EDIT: You cannot overload operators in current, common browser-based implementations of JavaScript interpreters.
To answer the original question, one way you could do this, and mind you, this is a bit of a hack, simply serialize the two arrays to JSON and then compare the two JSON strings. That would simply tell you if the arrays are different, obviously you could do this to each of the objects within the arrays as well to see which ones were different.
Another option is to use a library which has some nice facilities for comparing objects - I use and recommend MochiKit.
EDIT: The answer kamens gave deserves consideration as well, since a single function to compare two given objects would be much smaller than any library to do what I suggest (although my suggestion would certainly work well enough).
Here is a naïve implemenation that may do just enough for you - be aware that there are potential problems with this implementation:
function objectsAreSame(x, y) {
var objectsAreSame = true;
for(var propertyName in x) {
if(x[propertyName] !== y[propertyName]) {
objectsAreSame = false;
break;
}
}
return objectsAreSame;
}
The assumption is that both objects have the same exact list of properties.
Oh, and it is probably obvious that, for better or worse, I belong to the only-one-return-point camp. :)
Honestly, with 8 objects max and 8 properties max per object, your best bet is to just traverse each object and make the comparisons directly. It'll be fast and it'll be easy.
If you're going to be using these types of comparisons often, then I agree with Jason about JSON serialization...but otherwise there's no need to slow down your app with a new library or JSON serialization code.
I know this is an old question and the answers provided work fine ... but this is a bit shorter and doesn't require any additional libraries ( i.e. JSON ):
function arraysAreEqual(ary1,ary2){
return (ary1.join('') == ary2.join(''));
}
I have worked a bit on a simple algorithm to compare contents of two objects and return an intelligible list of difference. Thought I would share. It borrows some ideas for jQuery, namely the map function implementation and the object and array type checking.
It returns a list of "diff objects", which are arrays with the diff info. It's very simple.
Here it is:
// compare contents of two objects and return a list of differences
// returns an array where each element is also an array in the form:
// [accessor, diffType, leftValue, rightValue ]
//
// diffType is one of the following:
// value: when primitive values at that index are different
// undefined: when values in that index exist in one object but don't in
// another; one of the values is always undefined
// null: when a value in that index is null or undefined; values are
// expressed as boolean values, indicated wheter they were nulls
// type: when values in that index are of different types; values are
// expressed as types
// length: when arrays in that index are of different length; values are
// the lengths of the arrays
//
function DiffObjects(o1, o2) {
// choose a map() impl.
// you may use $.map from jQuery if you wish
var map = Array.prototype.map?
function(a) { return Array.prototype.map.apply(a, Array.prototype.slice.call(arguments, 1)); } :
function(a, f) {
var ret = new Array(a.length), value;
for ( var i = 0, length = a.length; i < length; i++ )
ret[i] = f(a[i], i);
return ret.concat();
};
// shorthand for push impl.
var push = Array.prototype.push;
// check for null/undefined values
if ((o1 == null) || (o2 == null)) {
if (o1 != o2)
return [["", "null", o1!=null, o2!=null]];
return undefined; // both null
}
// compare types
if ((o1.constructor != o2.constructor) ||
(typeof o1 != typeof o2)) {
return [["", "type", Object.prototype.toString.call(o1), Object.prototype.toString.call(o2) ]]; // different type
}
// compare arrays
if (Object.prototype.toString.call(o1) == "[object Array]") {
if (o1.length != o2.length) {
return [["", "length", o1.length, o2.length]]; // different length
}
var diff =[];
for (var i=0; i<o1.length; i++) {
// per element nested diff
var innerDiff = DiffObjects(o1[i], o2[i]);
if (innerDiff) { // o1[i] != o2[i]
// merge diff array into parent's while including parent object name ([i])
push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + i + "]" + o[0]; return o; }));
}
}
// if any differences were found, return them
if (diff.length)
return diff;
// return nothing if arrays equal
return undefined;
}
// compare object trees
if (Object.prototype.toString.call(o1) == "[object Object]") {
var diff =[];
// check all props in o1
for (var prop in o1) {
// the double check in o1 is because in V8 objects remember keys set to undefined
if ((typeof o2[prop] == "undefined") && (typeof o1[prop] != "undefined")) {
// prop exists in o1 but not in o2
diff.push(["[" + prop + "]", "undefined", o1[prop], undefined]); // prop exists in o1 but not in o2
}
else {
// per element nested diff
var innerDiff = DiffObjects(o1[prop], o2[prop]);
if (innerDiff) { // o1[prop] != o2[prop]
// merge diff array into parent's while including parent object name ([prop])
push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + prop + "]" + o[0]; return o; }));
}
}
}
for (var prop in o2) {
// the double check in o2 is because in V8 objects remember keys set to undefined
if ((typeof o1[prop] == "undefined") && (typeof o2[prop] != "undefined")) {
// prop exists in o2 but not in o1
diff.push(["[" + prop + "]", "undefined", undefined, o2[prop]]); // prop exists in o2 but not in o1
}
}
// if any differences were found, return them
if (diff.length)
return diff;
// return nothing if objects equal
return undefined;
}
// if same type and not null or objects or arrays
// perform primitive value comparison
if (o1 != o2)
return [["", "value", o1, o2]];
// return nothing if values are equal
return undefined;
}
I tried JSON.stringify() and worked for me.
let array1 = [1,2,{value:'alpha'}] , array2 = [{value:'alpha'},'music',3,4];
JSON.stringify(array1) // "[1,2,{"value":"alpha"}]"
JSON.stringify(array2) // "[{"value":"alpha"},"music",3,4]"
JSON.stringify(array1) === JSON.stringify(array2); // false
There is a optimized code for case when function needs to equals to empty arrays (and returning false in that case)
const objectsEqual = (o1, o2) => {
if (o2 === null && o1 !== null) return false;
return o1 !== null && typeof o1 === 'object' && Object.keys(o1).length > 0 ?
Object.keys(o1).length === Object.keys(o2).length &&
Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
: (o1 !== null && Array.isArray(o1) && Array.isArray(o2) && !o1.length &&
!o2.length) ? true : o1 === o2;
}
Here is my attempt, using Node's assert module + npm package object-hash.
I suppose that you would like to check if two arrays contain the same objects, even if those objects are ordered differently between the two arrays.
var assert = require('assert');
var hash = require('object-hash');
var obj1 = {a: 1, b: 2, c: 333},
obj2 = {b: 2, a: 1, c: 444},
obj3 = {b: "AAA", c: 555},
obj4 = {c: 555, b: "AAA"};
var array1 = [obj1, obj2, obj3, obj4];
var array2 = [obj3, obj2, obj4, obj1]; // [obj3, obj3, obj2, obj1] should work as well
// calling assert.deepEquals(array1, array2) at this point FAILS (throws an AssertionError)
// even if array1 and array2 contain the same objects in different order,
// because array1[0].c !== array2[0].c
// sort objects in arrays by their hashes, so that if the arrays are identical,
// their objects can be compared in the same order, one by one
var array1 = sortArrayOnHash(array1);
var array2 = sortArrayOnHash(array2);
// then, this should output "PASS"
try {
assert.deepEqual(array1, array2);
console.log("PASS");
} catch (e) {
console.log("FAIL");
console.log(e);
}
// You could define as well something like Array.prototype.sortOnHash()...
function sortArrayOnHash(array) {
return array.sort(function(a, b) {
return hash(a) > hash(b);
});
}
My practice implementation with sorting, tested and working.
const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };
const equalObjs = ( obj1, obj2 ) => {
let keyExist = false;
for ( const [key, value] of Object.entries(obj1) ) {
// Search each key in reference object and attach a callback function to
// compare the two object keys
if( Object.keys(obj2).some( ( e ) => e == key ) ) {
keyExist = true;
}
}
return keyExist;
}
console.info( equalObjs( obj1, obj2 ) );
Compare your arrays
// Sort Arrays
var arr1 = arr1.sort(( a, b ) => {
var fa = Object.keys(a);
var fb = Object.keys(b);
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
});
var arr2 = arr2.sort(( a, b ) => {
var fa = Object.keys(a);
var fb = Object.keys(b);
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
});
const equalArrays = ( arr1, arr2 ) => {
// If the arrays are different length we an eliminate immediately
if( arr1.length !== arr2.length ) {
return false;
} else if ( arr1.every(( obj, index ) => equalObjs( obj, arr2[index] ) ) ) {
return true;
} else {
return false;
}
}
console.info( equalArrays( arr1, arr2 ) );
I am sharing my compare function implementation as it might be helpful for others:
/*
null AND null // true
undefined AND undefined // true
null AND undefined // false
[] AND [] // true
[1, 2, 'test'] AND ['test', 2, 1] // true
[1, 2, 'test'] AND ['test', 2, 3] // false
[undefined, 2, 'test'] AND ['test', 2, 1] // false
[undefined, 2, 'test'] AND ['test', 2, undefined] // true
[[1, 2], 'test'] AND ['test', [2, 1]] // true
[1, 'test'] AND ['test', [2, 1]] // false
[[2, 1], 'test'] AND ['test', [2, 1]] // true
[[2, 1], 'test'] AND ['test', [2, 3]] // false
[[[3, 4], 2], 'test'] AND ['test', [2, [3, 4]]] // true
[[[3, 4], 2], 'test'] AND ['test', [2, [5, 4]]] // false
[{x: 1, y: 2}, 'test'] AND ['test', {x: 1, y: 2}] // true
1 AND 1 // true
{test: 1} AND ['test', 2, 1] // false
{test: 1} AND {test: 1} // true
{test: 1} AND {test: 2} // false
{test: [1, 2]} AND {test: [1, 2]} // true
{test: [1, 2]} AND {test: [1]} // false
{test: [1, 2], x: 1} AND {test: [1, 2], x: 2} // false
{test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 5}]} // true
{test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 6}]} // false
*/
function is_equal(x, y) {
const
arr1 = x,
arr2 = y,
is_objects_equal = function (obj_x, obj_y) {
if (!(
typeof obj_x === 'object' &&
Object.keys(obj_x).length > 0
))
return obj_x === obj_y;
return Object.keys(obj_x).length === Object.keys(obj_y).length &&
Object.keys(obj_x).every(p => is_objects_equal(obj_x[p], obj_y[p]));
}
;
if (!( Array.isArray(arr1) && Array.isArray(arr2) ))
return (
arr1 && typeof arr1 === 'object' &&
arr2 && typeof arr2 === 'object'
)
? is_objects_equal(arr1, arr2)
: arr1 === arr2;
if (arr1.length !== arr2.length)
return false;
for (const idx_1 of arr1.keys())
for (const idx_2 of arr2.keys())
if (
(
Array.isArray(arr1[idx_1]) &&
this.is_equal(arr1[idx_1], arr2[idx_2])
) ||
is_objects_equal(arr1[idx_1], arr2[idx_2])
)
{
arr2.splice(idx_2, 1);
break;
}
return !arr2.length;
}
Please try this one:
function used_to_compare_two_arrays(a, b)
{
// This block will make the array of indexed that array b contains a elements
var c = a.filter(function(value, index, obj) {
return b.indexOf(value) > -1;
});
// This is used for making comparison that both have same length if no condition go wrong
if (c.length !== a.length) {
return 0;
} else{
return 1;
}
}
The objectsAreSame function mentioned in #JasonBunting's answer works fine for me. However, there's a little problem: If x[propertyName] and y[propertyName] are objects (typeof x[propertyName] == 'object'), you'll need to call the function recursively in order to compare them.
not sure about the performance ... will have to test on big objects .. however, this works great for me.. the advantage it has compared to the other solutions is, the objects/array do not have to be in the same order ....
it practically takes the first object in the first array, and scans the second array for every objects .. if it's a match, it will proceed to another
there is absolutely a way for optimization but it's working :)
thx to #ttulka I got inspired by his work ... just worked on it a little bit
const objectsEqual = (o1, o2) => {
let match = false
if(typeof o1 === 'object' && Object.keys(o1).length > 0) {
match = (Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p])))
}else {
match = (o1 === o2)
}
return match
}
const arraysEqual = (a1, a2) => {
let finalMatch = []
let itemFound = []
if(a1.length === a2.length) {
finalMatch = []
a1.forEach( i1 => {
itemFound = []
a2.forEach( i2 => {
itemFound.push(objectsEqual(i1, i2))
})
finalMatch.push(itemFound.some( i => i === true))
})
}
return finalMatch.every(i => i === true)
}
const ar1 = [
{ id: 1, name: "Johnny", data: { body: "Some text"}},
{ id: 2, name: "Jimmy"}
]
const ar2 = [
{name: "Jimmy", id: 2},
{name: "Johnny", data: { body: "Some text"}, id: 1}
]
console.log("Match:",arraysEqual(ar1, ar2))
jsfiddle: https://jsfiddle.net/x1pubs6q/
or just use lodash :))))
const _ = require('lodash')
const isArrayEqual = (x, y) => {
return _.isEmpty(_.xorWith(x, y, _.isEqual));
};
using _.some from lodash: https://lodash.com/docs/4.17.11#some
const array1AndArray2NotEqual =
_.some(array1, (a1, idx) => a1.key1 !== array2[idx].key1
|| a1.key2 !== array2[idx].key2
|| a1.key3 !== array2[idx].key3);
There`s my solution. It will compare arrays which also have objects and arrays. Elements can be stay in any positions.
Example:
const array1 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
const array2 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
const arraysCompare = (a1, a2) => {
if (a1.length !== a2.length) return false;
const objectIteration = (object) => {
const result = [];
const objectReduce = (obj) => {
for (let i in obj) {
if (typeof obj[i] !== 'object') {
result.push(`${i}${obj[i]}`);
} else {
objectReduce(obj[i]);
}
}
};
objectReduce(object);
return result;
};
const reduceArray1 = a1.map(item => {
if (typeof item !== 'object') return item;
return objectIteration(item).join('');
});
const reduceArray2 = a2.map(item => {
if (typeof item !== 'object') return item;
return objectIteration(item).join('');
});
const compare = reduceArray1.map(item => reduceArray2.includes(item));
return compare.reduce((acc, item) => acc + Number(item)) === a1.length;
};
console.log(arraysCompare(array1, array2));
This is work for me to compare two array of objects without taking into consideration the order of the items
const collection1 = [
{ id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" },
{ id: "2", name: "item 2", parentId: "1" },
{ id: "3", name: "item 3", parentId: "1" },
]
const collection2 = [
{ id: "3", name: "item 3", parentId: "1" },
{ id: "2", name: "item 2", parentId: "1" },
{ id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" },
]
const contains = (arr, obj) => {
let i = arr.length;
while (i--) {
if (JSON.stringify(arr[i]) === JSON.stringify(obj)) {
return true;
}
}
return false;
}
const isEqual = (obj1, obj2) => {
let n = 0
if (obj1.length !== obj2.length) {
return false;
}
for (let i = 0; i < obj1.length; i++) {
if (contains(obj2, obj1[i])) {
n++
}
}
return n === obj1.length
}
console.log(isEqual(collection1,collection2))
if you take into consideration the order of the items use built in function in lodash isEqual
comparing with json is pretty bad. try this package to compare nested arrays and get the difference.
https://www.npmjs.com/package/deep-object-diff
If you stringify them...
type AB = {
nome: string;
}
const a: AB[] = [{ nome: 'Célio' }];
const b: AB[] = [{ nome: 'Célio' }];
console.log(a === b); // false
console.log(JSON.stringify(a) === JSON.stringify(b)); // true

What is the fastest way of finding if object has a value (multidimensional object)

I have an multi-dimensional object that has a 3 level depth. I am trying to if the 3rd-level object has a given value. What comes to my mind is for-looping through each level and checking with (Object.values(obj).indexOf('red') > -1) but as far as I understand, for looping is a slow way.
For example, in the below object, what is the fastest way to check if any of the most inner values have red value or not, returning a boolean?
myObj: {
user1: {
apples: {
1: "red",
2: "green",
3: "black"
},
cherry: {
2: "green"
4: "dark"
}
},
user2: {
orange: {
1: "orange"
}
}
}
This is a recursive method that uses Oject.values() and Array.some() to check if a value exists in an object:
const obj = {"user1":{"apples":{"1":"red","2":"green","3":"black"},"cherry":{"2":"green","4":"dark"}},"user2":{"orange":{"1":"orange"}}};
const findValue = (o, val) => Object.values(o)
.some((v) => v && typeof(v) === 'object' ? findValue(v, val) : (v === val));
console.log(findValue(obj, 'red'));
console.log(findValue(obj, 'gold'));
You could use a depth-first search and look for nested objects.
function contains(object, value) {
return Object.values(object).some(
v => v && typeof v === 'object'
? contains(v, value) :
v === value
);
}
var myObj = { user1: { apples: { 1: "red", 2: "green", 3: "black" }, cherry: { 2: "green", 4: "dark" } }, user2: { orange: { 1: "orange" } } };
console.log(contains(myObj, 'red'));
console.log(contains(myObj, 42));
An other solution could be to use a stack an perform a linear search without a recusion.
This works as breadth-first-search.
function contains(object, value) {
var stack = Object.values(object),
v;
while (stack.length) {
v = stack.shift();
if (v && typeof v === 'object') {
stack.push(...Object.values(v));
continue;
}
if (v === value) {
return true;
}
}
return false;
}
var myObj = { user1: { apples: { 1: "red", 2: "green", 3: "black" }, cherry: { 2: "green", 4: "dark" } }, user2: { orange: { 1: "orange" } } };
console.log(contains(myObj, 'red'));
console.log(contains(myObj, 42));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use a recursive depth-first function to iterate through the keys, and then at the deepest level, just return true. One "gotcha" is to make sure that strings don't return single character strings when iterating since that will just recurse infinitely.
function hasKey(object, depth = 0) {
if (depth === 0) {
return true;
}
for (const key in Object(object)) {
const value = object[key];
// prevent nested checks of characters in strings
if (typeof value !== 'string' || value.length !== 1 || typeof object !== 'string') {
if (hasKey(value, depth - 1)) {
return true;
}
}
}
return false;
}
let myObj = {"user1":{"apples":{"1":"red","2":"green","3":"black"},"cherry":{"2":"green","4":"dark"}},"user2":{"orange":{"1":"orange"}}};
// has keys at depth 3
console.log(hasKey(myObj, 3));
// does not have keys at depth 4
console.log(hasKey(myObj, 4));
While this answer may be longer in line-count, it truly iterates the keys at each depth rather than buffering all the Object.values() into an array at each depth, which technically disqualifies the other answers from being able to claim a "depth-first" approach, since the buffering causes "breadth-first" behavior.

Check if an array contains a specified object

The following function searches an object recursively through an object that has nested arrays:
function findDeep(arr, obj) {
console.log(arr)
if (arr.indexOf(obj) !== -1) {
console.log(arr)
return arr
} else {
arr.forEach(item => {
if (item.children) findDeep(item.children, obj)
})
}
}
const colors = {
children: [
{
name: 'white',
},
{
name: 'yellow',
children: [
{
name: 'black'
}
]
}
]
}
const color = {
name: 'black'
}
findDeep(colors.children, color)
The first console.log(arr) do log the matched array:
[
{ name: 'black' }
]
But he second console.log(arr) doesn't log anything. Shouldn't arr.indexOf(obj) return 1, and therefore make the second console.log(arr) log the array?
Here's the CodePen.
You can not find index of object in array using indexOf unless both the objects(passed in indexOf to test and present in array) are pointing to the same reference.
For example:
var a = {
a: 10
};
var b = [{
a: 10
}, {
b: 20
}];
console.log(b.indexOf(a)); // Object `a` and Object in `0th` index of the array are having similar `key-values`
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
But,
var a = {
a: 10
};
var b = [a, {
b: 20
}];
//`0th` index in the array is nothing but a variable holding `object`
console.log(b.indexOf(a)); //Same variable is tested in `indexOf`
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
From the docs, indexOf() compares searchElement to elements of the Array using strict equality (the same method used by the === or triple-equals operator).
{} === {} will be evaluated as false because,
An expression comparing Objects is only true if the operands reference the same Object. If both operands are objects, then JavaScript compares internal references which are equal when operands refer to the same object in memory.[Ref]
There are few solutions and approaches but all of them will be doing iteration and comparing value of the key in object. Refer this answer

javascript filter array in array

I have the following code based on docs
var arr = [
{ id: 15 },
{ x: [{ id: 777 }, { id: 'xx' }, { notidproperty: 987 }]},
{ id: 1111 }
];
function filterByID(obj) {
if ('id' in obj && typeof(obj.id) === 'number' && !isNaN(obj.id)) {
return true;
} else if (Object.prototype.toString.call(obj) === '[object Array]') { //obj type is Object, not Array
obj.filter(filterByID);
} else {
return false;
}
}
var arrByID = arr.filter(filterByID);
console.log('expected length = 3, actual length = ' + arrByID.length);
console.log(arrByID);
How can I filter 'arr' array ? Is there any other techniques to get proper result?
EDIT: the expected result is filtered array of objects that have id property with numeric value
So expected Id values are 15, 777, 1111
I suggest to use Array#reduce() instead of Array#filter(), because you need a flat array for the result, to count.
I use isFinite as check for the id.
function rr(r, a) {
Object.keys(a).forEach(function (k) {
if (Array.isArray(a[k])) {
r = r.concat(a[k].reduce(rr, []));
} else {
isFinite(a.id) && r.push(a);
}
});
return r;
}
var arr = [{ id: 15 }, { x: [{ id: 777 }, { id: 'xx' }, { notidproperty: 987 }] }, { id: 1111 }],
result = arr.reduce(rr, []);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
You have to modify the following branch:
if (Object.prototype.toString.call(obj) === '[object Array]') { //obj type is Object, not Array
obj.filter(filterByID);
}
This only filters but returns no result. And your object with x property of Array type will not be processed here. So:
if(obj.x && Array.isArray(obj.x)){
return obj.x.filter(filterByID).length>0
}
Or:
if(obj.x && Array.isArray(obj.x)){
obj.x = obj.x.filter(filterByID)
return true
}
(Depending on what you want to do)
Almost there. In the else if you don't return anything. Instead of
obj.filter(filterByID);
try return (obj.filter(filterByID).length > 0);
because if the result of the recursive filterByID is an array with length more than 0, you want to add that to the outer result.

How can I use lodash/underscore to sort by multiple nested fields?

I want to do something like this:
var data = [
{
sortData: {a: 'a', b: 2}
},
{
sortData: {a: 'a', b: 1}
},
{
sortData: {a: 'b', b: 5}
},
{
sortData: {a: 'a', b: 3}
}
];
data = _.sortBy(data, ["sortData.a", "sortData.b"]);
_.map(data, function(element) {console.log(element.sortData.a + " " + element.sortData.b);});
And have it output this:
"a 1"
"a 2"
"a 3"
"b 5"
Unfortunately, this doesn't work and the array remains sorted in its original form. This would work if the fields weren't nested inside the sortData. How can I use lodash/underscore to sort an array of objects by more than one nested field?
I've turned this into a lodash feature request: https://github.com/lodash/lodash/issues/581
Update: See the comments below, this is not a good solution in most cases.
Someone kindly answered in the issue I created. Here's his answer, inlined:
_.sortBy(data, function(item) {
return [item.sortData.a, item.sortData.b];
});
I didn't realize that you're allowed to return an array from that function. The documentation doesn't mention that.
If you need to specify the sort direction, you can use _.orderBy with the array of functions syntax from Lodash 4.x:
_.orderBy(data, [
function (item) { return item.sortData.a; },
function (item) { return item.sortData.b; }
], ["asc", "desc"]);
This will sort first ascending by property a, and for objects that have the same value for property a, will sort them descending by property b.
It works as expected when the a and b properties have different types.
Here is a jsbin example using this syntax.
There is a _.sortByAll method in lodash version 3:
https://github.com/lodash/lodash/blob/3.10.1/doc/README.md#_sortbyallcollection-iteratees
Lodash version 4, it has been unified:
https://lodash.com/docs#sortBy
Other option would be to sort values yourself:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
function compareValues(v1, v2) {
return (v1 > v2)
? 1
: (v1 < v2 ? -1 : 0);
};
var data = [
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 3 }
];
data.sort(function (x, y) {
var result = compareValues(x.a, y.a);
return result === 0
? compareValues(x.b, y.b)
: result;
});
// data after sort:
// [
// { a: 1, b: 3 },
// { a: 2, b: 1 },
// { a: 2, b: 2 }
// ];
The awesome, simple way is:
_.sortBy(data, [function(item) {
return item.sortData.a;
}, function(item) {
return item.sortData.b;
}]);
I found it from check the source code of lodash, it always check the function one by one.
Hope that help.
With ES6 easy syntax and lodash
sortBy(item.sortData, (item) => (-item.a), (item) => (-item.b))
I think this could work in most cases with underscore:
var properties = ["sortData.a", "sortData.b"];
data = _.sortBy(data, function (d) {
var predicate = '';
for (var i = 0; i < properties.length; i++)
{
predicate += (i == properties.length - 1
? 'd.' + properties[i]
: 'd.' + properties[i] + ' + ')
}
return eval(predicate)
});
It works and you can see it in Plunker
If the problem is an integer is converted to a string, add zeroes before the integer to make it have the same length as the longest in the collection:
var maxLength = _.reduce(data, function(result, item) {
var bString = _.toString(item.sortData.b);
return result > bString.length ? result : bString.length;
}, 0);
_.sortBy(data, function(item) {
var bString = _.toString(item.sortData.b);
if(maxLength > bString.length) {
bString = [new Array(maxLength - bString.length + 1).join('0'), bString].join('');
}
return [item.sortData.a, bString];
});
I've found a good way to sort array by multiple nested fields.
const array = [
{id: '1', name: 'test', properties: { prop1: 'prop', prop2: 'prop'}},
{id: '2', name: 'test2', properties: { prop1: 'prop second', prop2: 'prop second'}}
]
I suggest to use 'sorters' object which will describe a key and sort order. It's comfortable to use it with some data table.
const sorters = {
'id': 'asc',
'properties_prop1': 'desc',//I'm describing nested fields with '_' symbol
}
dataSorted = orderBy(array, Object.keys(sorters).map(sorter => {
return (row) => {
if (sorter.includes('_')) { //checking for nested field
const value = row["properties"][sorter.split('_')[1]];
return value || null;
};
return row[sorter] || null;// checking for empty values
};
}), Object.values(sorters));
This function will sort an array with multiple nested fields, for the first arguments it takes an array to modify, seconds one it's actually an array of functions, each function have argument that actually an object from 'array' and return a value or null for sorting. Last argument of this function is 'sorting orders', each 'order' links with functions array by index. How the function looks like simple example after mapping:
orderBy(array, [(row) => row[key] || null, (row) => row[key] || null , (row) => row[key] || null] , ['asc', 'desc', 'asc'])
P.S. This code can be improved, but I would like to keep it like this for better understanding.

Categories