Check if an array contains a specified object - javascript

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

Related

Trying to sort object keys based on child property

I have this object:
var myObject = {
cat: {
order: 1
},
mouse: {
order: 4
},
dog: {
order: 2
},
shark: {
order: 3
}
}
I'm trying to get back: ["cat", "dog", "shark", "mouse"]
I tried:
_.sortBy(x, function(e) { return e.order} )
You can simply use Object.keys() and Array.sort() for it.
get all the keys from the Object using Object.keys().
Simply sort all the keys by passing a custom Comparator to the sort function which compares the order property of the keys in the object.
var myObject = { cat: { order: 1 }, mouse: { order: 4 }, dog: { order: 2 }, shark: { order: 3 } };
let result = Object.keys(myObject).sort((a,b)=> myObject[a].order - myObject[b].order);
console.log(result);
use Object.entries first, then sort by the order property of its second element (because Object.entries returns an array of a given object's own enumerable property [key, value] pairs), finally use Array.map to get what you need.
var myObject = {
cat: {
order: 1
},
mouse: {
order: 4
},
dog: {
order: 2
},
shark: {
order: 3
}
}
console.log(
Object.entries(myObject).sort((a, b) => {
return a[1].order - b[1].order
}).map(item => item[0])
)
var myObject = {
cat: {
order: 1
},
mouse: {
order: 4
},
dog: {
order: 2
},
shark: {
order: 3
}
}
let oKeys = Object.keys(myObject)
let tempArray = []
oKeys.forEach(function(key) {
tempArray[myObject[key]['order']-1] = key
})
console.log(tempArray)
Here is a solution using lodash.
Use _.map and _.sort for it.
First, _.map to array of order and name.
Then, _.sort and _.map name
By using lodash chain, make the code easy to read.
_(myObject)
.map((v, k) => ({order: v.order, name: k}))
.sortBy('order')
.map('name')
.value()

Object's Deep Compare

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
}

Sort objects of objects by nested property

I have an object that looks like the one below. How can I sort something like this based on a common property within nested object. The output I expect is for player2 to come first based on the higher score.
My challenge is in accessing the property of each object to sort.
Here is what I had in mind and tried but it didn't do the sorting.
Object.keys(data).sort(function(p1, p2){
return p1.score - p2.score;
}).forEach(function(key) {
var value = data[key];
delete data[key];
data[key] = value;
});
My data
var data =
{
player1:
{ score: 4,
cards: 6 },
player2:
{ score: 6,
cards: 4}
}
You need to sort the data with the object, not with a key's property and then it has to be reverted, because you need a descending sort.
return data[b].score - data[a].score;
// ^^^^^^^ ^^^^^^^ object
// ^ ^ descending
I suggest to use an empty object and insert the properties by the ordered keys.
var data = { player1: { score: 4, cards: 6 }, player2: { score: 6, cards: 4 } },
sorted = {};
Object
.keys(data).sort(function(a, b){
return data[b].score - data[a].score;
})
.forEach(function(key) {
sorted[key] = data[key];
});
console.log(sorted);
Here is one line functional approach using Object.entries(), Array.prototype.sort() and Object.fromEntries method. Before sorting you need to make the object an array by using Object.entries() method. It returns array of key-value pair of the given object. Then sort the array in descending order by the score. At last, use Object.fromEntries() method to transform the key-value pair into an object.
const data = {
player1: { score: 4, cards: 6 },
player2: { score: 6, cards: 4 },
};
const ret = Object.fromEntries(
Object.entries(data).sort((x, y) => y[1].score - x[1].score)
);
console.log(ret);
You can't sort an object. You should convert your object to an array and then sort it.
var data =
{
player1:
{ score: 4,
cards: 6 },
player2:
{ score: 6,
cards: 4}
}
var array = $.map(data, function(value, index) {
value.key = index;
return value;
});
var sortedData = array.sort(function(p1, p2){
return p2.score - p1.score;
});
console.log(sortedData);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

How do I merge an array of objects in Javascript?

Example:
var array1 = [ {'key':1, 'property1': 'x'}, {'key':2, 'property1': 'y'} ]
var array2 = [ {'key':2, 'property2': 'a'}, {'key':1, 'property2': 'b'} ]
I want merge(array1, array2) to give me:
[
{'key':1, 'property1': 'x', 'property2' : 'b'},
{'key':2, 'property1': 'y', 'property2' : 'a'}
]
Is there an easy way to do this?
EDIT: several people have answered without looking too closely at my problem, please be note that I want to match similar objects in each array and combine their properties into my final array. Keys are unique and there will only ever be at most one object with a particular key in each array.
I wrote a quick not-so-quick solution. The one problem you might want to consider is whether a property from an object in the second array should override the same property, if present, in the second object it's being compared to.
Solution 1
This solution is of complexity O(n²). Solution 2 is much faster; this solution is only for those who don't want to be Sanic the Hedgehog fast.
JavaScript
var mergeByKey = function (arr1, arr2, key) {
// key is the key that the function merges based on
arr1.forEach(function (d, i) {
var prop = d[key];
// since keys are unique, compare based on this key's value
arr2.forEach(function (f) {
if (prop == f[key]) { // if true, the objects share keys
for (var x in f) { // loop through each key in the 2nd object
if (!(x in d)) // if the key is not in the 1st object
arr1[i][x] = f[x]; // add it to the first object
// this is the part you might want to change for matching properties
// which object overrides the other?
}
}
})
})
return arr1;
}
Test Case
var arr = [ {'key':1, 'property1': 'x'},
{'key':2, 'property1': 'y'} ],
arr2= [ {'key':2, 'property2': 'a'},
{'key':1, 'property2': 'b'} ];
console.log(mergeByKey(arr, arr2, "key"));
Results
/* returns:
Object
key: 1
property1: "x"
property2: "b"
__proto__: Object
and
Object
key: 2
property1: "y"
property2: "a"
__proto__: Object
*/
fiddle
Solution 2
As Vivin Paliath pointed out in the comments below, my first solution was of O(n²) complexity (read: bad). His answer is very good and provides a solution with a complexity of O(m + n), where m is the size of the first array and n of the second array. In other words, of complexity O(2n).
However, his solution does not address objects within objects. To solve this, I used recursion—read: the devil, just like O(n²).
JavaScript
var mergeByKey = function (arr1, arr2, key) {
var holder = [],
storedKeys = {},
i = 0; j = 0; l1 = arr1.length, l2 = arr2.length;
var merge = function (obj, ref) {
for (var x in obj) {
if (!(x in ref || x instanceof Object)) {
ref[x] = obj[x];
} else {
merge(obj[x], ref[x]);
}
}
storedKeys[obj.key] = ref;
}
for (; i < l1; i++) {
merge(arr1[i], storedKeys[arr1[i].key] || {});
}
for (; j < l2; j++) {
merge(arr2[j], storedKeys[arr2[j].key] || {});
}
delete storedKeys[undefined];
for (var obj in storedKeys)
holder.push(storedKeys[obj]);
return holder;
}
Test Case
var arr1 = [
{
"key" : 1,
"prop1" : "x",
"test" : {
"one": 1,
"test2": {
"maybe" : false,
"test3": { "nothing" : true }
}
}
},
{
"key" : 2,
"prop1": "y",
"test" : { "one": 1 }
}],
arr2 = [
{
"key" : 1,
"prop2" : "y",
"test" : { "two" : 2 }
},
{
"key" : 2,
"prop2" : "z",
"test" : { "two": 2 }
}];
console.log(mergeByKey(arr1, arr2, "key"));
Results
/*
Object
key: 1
prop1: "x"
prop2: "y"
test: Object
one: 1
test2: Object
maybe: false
test3: Object
nothing: true
__proto__: Object
__proto__: Object
two: 2
__proto__: Object
__proto__: Object
Object
key: 2
prop1: "y"
prop2: "z"
test: Object
one: 1
two: 2
__proto__: Object
__proto__: Object
*/
This correctly merges the objects, along with all child objects. This solutions assumes that objects with matching keys have the same hierarchies. It also does not handle the merging of two arrays.
fiddle
You could do something like this:
function merge(array1, array2) {
var keyedResult = {};
function _merge(element) {
if(!keyedResult[element.key]) {
keyedResult[element.key] = {};
}
var entry = keyedResult[element.key];
for(var property in element) if(element.hasOwnProperty(property)) {
if(property !== "key") {
entry[property] = element[property];
}
}
entry["key"] = element.key;
}
array1.forEach(_merge);
array2.forEach(_merge);
var result = [];
for(var key in keyedResult) if(keyedResult.hasOwnProperty(key)) {
result.push(keyedResult[key]);
}
return result.sort(function(a, b) {
return a.key - b.key;
});
}
You could eliminate the sort if you don't care about the order. Another option is to use an array instead of the map I have used (keyedResult) if you have numeric keys and don't care about the array being sparse (i.e., if the keys are non-consecutive numbers). Here the key would also be the index of the array.
This solution also runs in O(n).
fiddle
It would be preferable to use existing infrastructure such as Underscore's _.groupBy and _.extend to handle cases like this, rather than re-inventing the wheel.
function merge(array1, array2) {
// merge the arrays
// [ {'key':1, 'property1': 'x'}, {'key':2, 'property1': 'y'}, {'key':2, 'property2': 'a'}, {'key':1, 'property2': 'b'} ]
var merged_array = array1.concat(array2);
// Use _.groupBy to create an object indexed by key of relevant array entries
// {1: [{ }, { }], 2: [{ }, { }]}
var keyed_objects = _.groupBy(merged_array, 'key');
// for each entry in keyed_objects, merge objects
return Object.keys(keyed_objects).map(function(key) {
return _.extend.apply({}, keyed_objects[key]);
});
}
The idea here is using _.extend.apply to pass the array of objects grouped under a particular key as arguments to _.extend, which will merge them all into a single object.

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