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
Related
I have object that has multiple values for example
let obj = {
a: "day1",
b: "",
c: "day3",
aa: 10,
bb: 11,
cc: 12,
}
let data = {};
let item = [];
for (let i in obj) {
if (i === 'a') {
data["title"] = obj.a;
data['value'] = obj.aa;
}
if (i === 'b') {
data["title"] = obj.b;
data['value'] = obj.bb;
}
if (i === 'c') {
data["title"] = obj.c;
data['value'] = obj.cc;
}
item.push(data);
}
console.log(item)
but I'm getting only last day3 value in multiple times .
item [
{title:"day3",value:12},
{title:"day3",value:12},
{title:"day3",value:11}
]
I want in the following format
item [
{title:"day1",value:10},
{title:"day3",value:11}
]
please help, thanks in advance.
From the above comments ...
"Of cause the OP gets the last state since the OP always reassigns the same properties (title and value) at one and the same data object. The OP wants to use an array and push at/each time a newly created data object into it."
Peter Seliger can you give me any example of solution – omkar p
const obj = {
a: "day1",
b: "",
c: "day3",
aa: 10,
bb: 11,
cc: 12,
}
const items = [];
// slightly fixed OP approach.
for (const key in obj) {
if (key === 'a') {
items.push({
title: obj.a,
value: obj.aa,
});
}
if (key === 'b') {
items.push({
title: obj.b,
value: obj.bb,
});
}
if (key === 'c') {
items.push({
title: obj.c,
value: obj.cc,
});
}
}
console.log({ items });
// or entirely generic/configurable and maybe even more expressive ...
console.log(
Object
.entries(obj)
.reduce(({ target, valueMap, result }, [key, value]) => {
if (key in valueMap) {
result.push({
title: value,
value: target[valueMap[key]],
});
}
return { target, valueMap, result };
}, {
target: obj,
valueMap: { a: 'aa', b: 'bb', c: 'cc' },
result: [],
}).result
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
You should declare the data variable inside the loop because otherwise you are always changing its value at each iteration and that object is what you are adding to the item variable and its value it's always written over the previous one. If you create a new object at each iteration it will be on its own and no further iteration is going to overwrite its value.
*As it was pointed out in the comments, I added a check in your loop to skip iterations not belonging to the cases: 'a','b','c'; so that your final array won't contain empty objects.
let obj = {
a: "day1",
b: "",
c: "day3",
aa: 10,
bb: 11,
cc: 12,
}
let item = [];
for (let i in obj) {
//this check decides if you want to skip this iteration
//so that you won't have empty object appended to your result array
//if(i!='a' && i!='b' && i!='c')
// continue;
//this check was replaced by the final check later for better mantainance
//this was (before) the only thing I changed
let data = {};
if (i === 'a') {
data["title"] = obj.a;
data['value'] = obj.aa;
}
if (i === 'b') {
data["title"] = obj.b;
data['value'] = obj.bb;
}
if (i === 'c') {
data["title"] = obj.c;
data['value'] = obj.cc;
}
let isEmpty = Object.keys(data).length === 0;
//this will check if data is empty before pushing it to the array
if(!isEmpty)
item.push(data);
}
console.log(item)
Array loading object needs deep copy,
// 多个对象载入 数组中,需要深拷贝,不然键指相同
let objXXX = {
a:123,
b:456,
C:789
};
let arrYYY = [];
arrYYY[0] = JSON.parse(JSON.stringify(objXXX));
arrYYY[1] = JSON.parse(JSON.stringify(objXXX));
arrYYY[0].a = "123";
arrYYY[1].a = "9923";
// 0: Object { a: "123", b: 456, C: 789 }
// 1: Object { a: "9923", b: 456, C: 789 }
console.log(arrYYY);
I wonder how I could use lodash to filter some objects using an array of filters options.
So far, I have a function that can use the array of filters option but I can't manage to take the LOGICAL operator (AND, OR) into consideration.
Edit:
we find those objects which match every LOGICAL : AND condition or any LOGICAL : OR condition
See this demo:
var toFilter = [
{"foo" : 1, "bar": 0},
{"foo" : 1, "bar": 0},
{"foo" : 2, "bar": null}
];
var filters = [
{"KEY": "foo", "VALUE": 1, "EQUALITY": "EQUAL", "LOGICAL": "OR"},
{"KEY": "bar", "VALUE": 0, "EQUALITY": "NOT_EQUAL", "LOGICAL": "AND"}
];
//the function should be equivalent to this one:
_.filter(toFilter, function(obj) { return obj.foo == 1 || obj.bar != 0 });
//this function does not take the LOGICAL operator ("AND" and "OR") in consideration
_.uniq(_.flatMap(filters, function(filter) {
return _.filter(toFilter, function(obj) {
if (filter.EQUALITY == "EQUAL") {
return obj[filter.KEY] == filter.VALUE;
} else {
return obj[filter.KEY] != filter.VALUE;
}
});
}));
This is a very simple example, but the list of filters could be a lot larger.
I think what you're looking for is Array.prototype.every and Array.prototype.some. This might do what you need. If there are actual duplicates you need to remove, then lodash's uniq could be added easily enough.
const conditions = {
'EQUAL': (a, b) => a === b,
'NOT_EQUAL': (a, b) => a !== b,
}
const check = (objs, filters) => objs.filter(obj => {
const ands = filters.filter(filter => filter.LOGICAL === 'AND')
const ors = filters.filter(filter => filter.LOGICAL === 'OR')
return ors.some(
filter => conditions[filter.EQUALITY](obj[filter.KEY], filter.VALUE)
) || (ands.length && ands.every(
filter => conditions[filter.EQUALITY](obj[filter.KEY], filter.VALUE)
))
})
const toFilter = [
{foo : 1, bar: 0, id: 'a'},
{foo : 1, bar: 0, id: 'b'},
{foo : 2, bar: null, id: 'c'},
{foo : 2, bar: 0, id: 'd'},
];
const filters = [
{KEY: "foo", VALUE: 1, EQUALITY: "EQUAL", LOGICAL: "OR"},
{KEY: "bar", VALUE: 0, EQUALITY: "NOT_EQUAL", LOGICAL: "AND"}
];
console.log(check(toFilter, filters)) //=> ids a, b and c
Note that this design makes it easy to add new conditions. For instance:
'GREATER_THAN': (a, b) => a > b
If this is for the web, and you don't have a build process, I would recommend storing the conditions in a local closure for the function, with something like this (untested):
const check = (() => {
const conditions = {
'EQUAL': (a, b) => a === b,
'NOT_EQUAL': (a, b) => a !== b,
}
return (objs, filters) => (objs, filters) => objs.filter(obj => {
const ands = filters.filter(filter => filter.LOGICAL === 'AND')
const ors = filters.filter(filter => filter.LOGICAL === 'OR')
return ors.some(
filter => conditions[filter.EQUALITY](obj[filter.KEY], filter.VALUE)
) || (ands.length && ands.every(
filter => conditions[filter.EQUALITY](obj[filter.KEY], filter.VALUE)
))
})
})()
This will be my approach to solve the problem:
var toFilter = [{ foo: 1, bar: 0 }, { foo: 1, bar: 0 }, { foo: 2, bar: null }];
var filters = [
{ KEY: 'foo', VALUE: 1, EQUALITY: 'EQUAL', LOGICAL: 'OR' },
{ KEY: 'bar', VALUE: 0, EQUALITY: 'NOT_EQUAL', LOGICAL: 'AND' }
];
var orFilters = _.filter(filters, ['LOGICAL', 'OR']);
var andFilters = _.filter(filters, ['LOGICAL', 'AND']);
var result = _.filter(toFilter, evalFilters);
function evalFilters(obj) {
var curriedEquality = equality(obj);
return (
_.some(orFilters, curriedEquality) ||
(andFilters.length && _.every(andFilters, curriedEquality))
);
}
function equality(obj) {
return function(filter) {
return filter.EQUALITY === 'EQUAL'
? obj[filter.KEY] === filter.VALUE
: obj[filter.KEY] !== filter.VALUE;
};
}
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The first step is to separate the OR filters and the AND filters in two different arrays.
Then I would iterate over the elements to be filtered, I would make a curried function equality which will receive the element to be evaluated and returns a function which contains the equality comparison.
Then I would use _.some to check if the element evals to true in at least one OR filter if not, I would use _.every to verify if the element evals to true in all the AND filters.
Obs: Is necessary to verify if the andFilters array is not empty, because _.every and also Array.prototype.every will return true if it's the case.
Hope this explanation helps!
Is there a good way to check if all items in an array are of the same type?
Something that does this:
[1, 2, 3, 4] // true
[2, 3, 4, "foo"] // false
You could create a Set from the types of each element in the array and make sure that it has at most one element:
console.log( allSameType( [1,2,3,4] ) );
console.log( allSameType( [2,3,4,"foo"] ) );
function allSameType( arr ) {
return new Set( arr.map( x => typeof x ) ).size <= 1;
}
You can use Array.every to check if all elements are strings.
arr.every( (val, i, arr) => typeof val === typeof arr[0]);
arr = ["foo", "bar", "baz"] // true
arr = [1, "foo", true] // false
Note:
arr = [] // true
Maybe less complicated solution would be
function sameTypes(arr, type) {
arr.forEach((item, index) => {
if (typeof item == type) {
console.log('TRUE');
} else {
console.log('FALSE');
}
});
}
Came up with a functional approach using recursion.
var array = [1, 2, "foo"]
function check([head, ...tail])
{
if(!head)
{
return true
}
else
{
var flag = true
tail.forEach(element => {
flag &= typeof element === typeof head
})
return flag? check(tail) : false
}
}
console.log(check(array))
Not exactly what OP asked but if you want to check if it's a certain type:
function isArrayOfType(arr, type) {
return arr.filter(i => typeof i === type).length === arr.length;
}
const numericArray = [1, 2, 3, 4, 5];
const mixedArray = [1, 2, 3, "foo"];
console.log(isArrayOfType(numericArray, 'number')); // true
console.log(isArrayOfType(numericArray, 'string')); // false
console.log(isArrayOfType(mixedArray, 'number')); // false
i am doing one of my front end project and i have a situation where i have to merge/add objects present in the array based on some conditions. Conditions would be
Only those Objects having same label should be merged.
For objects which has same label, if object 'a' has properties which are present in b object as well, the value has to be added, else simply copy the property.
So my input would be
[
{
label: 'label-1',
published: 1,
draft: 2,
id: 'some1'
},
{
label: 'label-1',
published: 2,
status: 0,
draft: 1,
id: 'some4'
},
{
label: 'label-2',
published: 1,
draft: 14,
id: 'some2'
},
{
label: 'label-2',
published: 12,
status: 0,
draft: 14,
id: 'some3'
}
]
and the expect
[
{
label: 'label-1',
published: 3,
draft: 4,
status: 0
},
{
label: 'label-2',
published: 13,
draft: 28,
status: 0
}
]
Currently i am using the following code for achieving the same , but find it not tidy . Is there any way this could be achieved easily.
function mapData(data) {
let groupData = _.groupBy(data, 'label');
let stackBarData = [];
Object.keys(groupData).forEach((key) => {
if (groupData[key] && groupData[key].length > 0) {
let temp = Array.from(groupData[key]).reduce((a, b) => {
for (let property in b) {
if (b.hasOwnProperty(property)) {
if (property !== 'label' && property !== 'id' && property !== 'Others') {
a[property] = (a[property] || 0) + b[property];
} else {
a[property] = b[property];
}
}
}
return a;
}, {});
stackBarData.push(temp);
}
});
return stackBarData;
}
Please help.
Here is a pure ES6 function that collects the object values that are numeric, adding them up (which is what you seem to do), per unique label:
function mapData(data) {
const grouped = new Map(data.map( ({label}) => [label, { label }] ));
for (let obj of data) {
let target = grouped.get(obj.label);
for (let [key, val] of Object.entries(obj)) {
if (typeof val === 'number') {
target[key] = (target[key] || 0) + val;
}
}
}
return [...grouped.values()];
}
// Sample data
const data = [{label: 'label-1',published: 1,draft: 2,id: 'some1'},{label: 'label-1',published: 2,status: 0,draft: 1,id: 'some4'},{label: 'label-2',published: 1,draft: 14,id: 'some2'},{label: 'label-2',published: 12,status: 0,draft: 14,id: 'some3'}];
console.log(mapData(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you have numeric properties that you wanted to exclude, then it might be better to have an explicit set of properties you are interested in:
const props = new Set(['status', 'published', 'draft']);
// ... etc
//
if (props.has(key)) {
target[key] = (target[key] || 0) + val;
}
// ...
Lodash
_.groupBy() by the label, _.map() the groups, and merge each group using _.mergeWith(), and _.omit() the id. When merging the groups, if the current value is a number, sum the current and new values, if not return undefined - If customizer returns undefined, merging is handled by the method instead.
const arr = [{"label":"label-1","published":1,"draft":2,"id":"some1"},{"label":"label-1","published":2,"status":0,"draft":1,"id":"some4"},{"label":"label-2","published":1,"draft":14,"id":"some2"},{"label":"label-2","published":12,"status":0,"draft":14,"id":"some3"}]
const result = _(arr)
.groupBy('label')
.map((g) => _.omit(_.mergeWith({}, ...g, (objValue, srcValue) => _.isNumber(objValue) ? objValue + srcValue : undefined), 'id'))
.value()
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
ES6
Iterate the array with Array.reduce(). On each iteration check if the accumulator (the Map) has the label, and if not add an empty object with the label as the key. Iterate the current object keys with Array.forEach(), ignore id, and sum the numeric values. To get an array spread the Map.values():
const arr = [{"label":"label-1","published":1,"draft":2,"id":"some1"},{"label":"label-1","published":2,"status":0,"draft":1,"id":"some4"},{"label":"label-2","published":1,"draft":14,"id":"some2"},{"label":"label-2","published":12,"status":0,"draft":14,"id":"some3"}]
const result = [...arr.reduce((m, o) => {
m.has(o.label) || m.set(o.label, {})
const obj = m.get(o.label)
Object.keys(o).forEach((k) => {
if(k === 'id') return
obj[k] = typeof o[k] === 'number' ? (obj[k] || 0) + o[k] : o[k]
})
return m
}, new Map()).values()]
console.log(result)
I want to check if array contains object or not. I am not trying to compare values just want to check in my array if object is present or not?
Ex.
$arr = ['a','b','c'] // normal
$arr = [{ id: 1}, {id: 2}] // array of objects
$arr = [{id: 1}, {id:2}, 'a', 'b'] // mix values
So how can i check if array contains object
You can use some method which tests whether at least one element in the array passes the test implemented by the provided function.
let arr = [{id: 1}, {id:2}, 'a', 'b'];
let exists = arr.some(a => typeof a == 'object');
console.log(exists);
I want to check if array contains object or not
Use some to simply check if any item of the array has value of type "object"
var hasObject = $arr.some( function(val){
return typeof val == "object";
});
var hasObject = function(arr) {
for (var i=0; i<arr.length; i++) {
if (typeof arr[i] == 'object') {
return true;
}
}
return false;
};
console.log(hasObject(['a','b','c']));
console.log(hasObject([{ id: 1}, {id: 2}]));
console.log(hasObject([{id: 1}, {id:2}, 'a', 'b']));
You could count the objects and use it for one of the three types to return.
function getType(array) {
var count = array.reduce(function (r, a) {
return r + (typeof a === 'object');
}, 0);
return count === array.length
? 'array of objects'
: count
? 'mix values'
: 'normal';
}
console.log([
['a', 'b', 'c'],
[{ id: 1 }, { id: 2 }],
[{ id: 1 }, { id: 2 }, 'a', 'b']
].map(getType));
With a type check on array
const hasObject = a => Array.isArray(a) && a.some(val => typeof val === 'object')