This question already has answers here:
Javascript: Using `.includes` to find if an array of objects contains a specific object
(7 answers)
Closed 28 days ago.
I am trying to understand the array "includes" function. My goal is to determine if an array includes a certain item. It works fine for an array of strings but when using objects it doesn't work.
var itemsString = ["name1", "name2"];
var itemsObject = [{ name: "name1" }, { name: "name2" }];
var itemToSearch = { name: "name1" };
console.log(itemsString.includes("name1" ));
console.log(itemsObject.includes(itemToSearch));
console.log(itemsObject.includes(x => x.name === "name1"));
Output:
true
false
false
Does "includes" work with objects or do I need to use another function?
You need to use Array.prototype.some() in this case. Array.prototype.includes() does not accept a function parameter*, and you're not testing for strict equality.
const itemsObject = [{ name: "name1" }, { name: "name2" }];
console.log(itemsObject.some(x => x.name === "name1"));
*Peer pressure from the comments section forces me to clarify that includes() does accept function parameters, but will not use the passed function as a predicate to determine whether a given item matches. Rather, it will try to find an item in the array that is strictly equal to the passed function.
In your last line you check wether a function is inside of your array. .includes also works for objects, but it compares them by reference. In your case you probably want to .find or check wether .some of the objects match your query.
Does "includes" work with objects or do I need to use another
function?
Includes works with objects, but it compares objects by reference.
In your case, despite the first element of itemsObject has the same keys and values of itemToSearch, they are different objects, hence includes will not work, since, for objects cases, it looks for the same object instance.
In order to make it work, you can use several alternatives, like .find, .some, .filter.
Another solution, which I personally don't recommend but I think that it's worth mentioning, is that you can use .includes if you first map items to strings instead. In that case, using JSON.stringify, you can check whether the objects are the same. BEWARE: this will work with single key items only. JSON.stringify doesn't preserve key and values order, so it works with single keys objects only (unless keys and values are in the same order in the original stringified object). Moreover, the JSON.stringify way is way heavier and less performant than the others, I just think it's worth mentioning that as an example.
Below some examples with each of them.
var itemsString = ["name1", "name2"];
var itemsObject = [{ name: "name1" }, { name: "name2" }];
var itemToSearch = { name: "name1" };
console.log(itemsObject.some(r => r.name === itemToSearch.name));
console.log(!!itemsObject.find(r => r.name === itemToSearch.name));
// ^--- !! is used to cast to boolean.
console.log(itemsObject.filter(r => r.name === itemToSearch.name).length > 0);
console.log(itemsObject.map(i => JSON.stringify(i)).includes(JSON.stringify(itemToSearch)));
// ^--------------^ ^---------------------------------------^
// |------ this will stringify each object, converting it to a json string. |
// |
// this will check whether the string[] includes any stringified value.--^
Related
I have an Array of Objects with a structure like this:
[
{
title: "",
imagePoster: "",
episodes: "",
score: "",
genres: ["Comedy", "Drama", ...],
description: ""
},
...
]
I need to return an Object which contains specific value like "Comedy" in the genres Array. I've been trying a lot of ways like for, filter, map, indexOf... But, I still can't figure it out, unless I search for ["Comedy", "Drama", ...] (i.e., the whole Array).
You could use Array#find to find the first element that satisfies the condition:
const search = "Comedy";
const found = array.find(obj => obj.genres.includes(search));
This also uses Array#includes which checks if the argument, the search keyword, exists in the genres array of each object. And if you needed IE support, use Array#indexOf and check if the return value is greater than -1 instead which does exactly the same thing:
obj => obj.genres.indexOf(search) > -1
And if you wanted to return all objects that have a certain genre, not just the first one, filter the array with Array#filter which filters the array based on a condition:
const search = "Comedy";
const found = array.filter(obj => obj.genres.includes(search));
This will filter the array and only leave the objects that include the search keyword in their genres array. Again, you can swap out includes for the indexOf check, and you can always just access the first element of the filtered array if find is not supported on your target browsers (namely IE).
Array.find & Array.includes are your friends. There are also polyfills for non-browsers :)
The most supported way would be to use Array.filter & Array.indexOf
function getMovieByGenre(genre) {
return movies.filter(function(movie) {
return movies.genres.indexOf(genre) >= 0
})[0];
}
var someComedy = getMovieByGenre('Comedy');
if (someComedy) {
// movie found
}
I am using the most supported code, without polyfills and ES6 features
I am pretty confused with relative searching in javascript For example
let array = [{name:'ram'},{name:'kumar ra'},{name:'nani'}]
If i search ra then array should be like [{name:'ram'},{name:'kumar ra'}]Similar to mysql Like statement .But i don't know how to do it in javascript ..
Thanks in advance..
Arrays have a filter (Array.prototype.filter) method which behaves very much like a where clause.
It takes a predicate and returns a new array containing the elements that satisfy that predicate.
const array = [{
name: 'ram'
}, {
name: 'kumar ra'
}, {
name: 'nani'
}];
const filtered = array.filter(e => e.name.match(/ra/));
console.log(filtered);
In the predicate itself, we use String's match (String.prototype.match) method to test each name against a regular expression. This is conceptually similar to a like expression in SQL.
The filter method walks the array and calls the predicate on each element of it. I have named this element e in inside the predicate. When the filter method calls the predicate, it passes the current element as the first argument thus binding it to e.
you can apply filter method on array elements to find items that their names contains 'ra'
let result=array.filter((item)=>{return item.name.contains('ra')})
Let's say I have dynamically loaded object each time with different properties and an array of objects of that type:
var obj = {name: someValue, key: someValue2};
var objArray = [{name: someValue, key: someValue2},{name: someValue, key: someValue3}];
I want to find index of objArray which contains the obj. Some elements of objArray can have the same name property but different key property so searching through obj.name is not an option. So far I came up with this solution:
var keys = [];
_.forEach(Object.keys(obj), function(key) {
keys.push(key, obj[key])
});
var index = _.findIndex(objArray, keys);
This works fine and all but I am looking for something with better performance because this objArray can be very large.
So the question is: Is there a better way to find index of exact same object in object Array?
Update:
I forgot to mention that the names of the keys are not specified and can vary each time.
Use Array.prototype.findIndex(), this works only if you know in advance the property you want to check and hard code the rules in the callback for .findIndex().
An example:
var obj = {
name: 'someValue',
key: 'someValue3'
};
var objArray = [{
name: 'someValue',
key: 'someValue2'
}, {
name: 'someValue',
key: 'someValue3'
}];
var index = objArray.findIndex(function(item, index) {
return (item.name === obj.name) && (item.key === obj.key) ? true : false;
});
console.log('index is: ' + index);
This below it is another approach, basically it takes a JavaScript value (your initial object) and convert to a JSON string, and it uses that string to search within your array. The script works without any recursions, with any number of nested properties for your objects. The order of your property is important in this script, as the conversion to string take it in consideration.
Regarding "best way" is difficult to answer, depending what are your parameters for best way. If you consider performance, you should consider benchmarking your scripts and do test with some real data.
var obj = {
name: 'someValue',
key: 'someValue2'
},
objArray = [{
name: 'someValue',
key: 'someValue2'
}, {
name: 'someValue',
key: 'someValue3'
}];
var str = JSON.stringify(obj),
index = objArray.findIndex(function(item, index) {
return str === JSON.stringify(item) ;
});
console.log('index is: ' + index);
I can think of 2 approaches for doing this:
1.) What would speed up the process (especially if you got large array and large objects) is to create some kind of unique key for every object and map it to let's say property called: hash. If you wanna keep it vanilla, the best way to do that might be using the String.hashCode() method.
Iterate trough object OWN (check hasOwnProperty) properties and concat into single string both property name and then ### and then value and then %%% in between.
Then enhance your object with property hash like:
myObj.hash = String.hashCode(StringOfNamesAndValues);
Now iterate trough your array using for, and compare obj.hash to objArray[index].hash
Once they match you fond your object, store the index :)
If your object and one in the array don't have to be EXACTLY the same object but only a subset needs to be same. Instead using all property names in hash generation, use only the names and values of properties you wanna to be same. Compare hashes made in that way - voila!
2.) More brutish way would be to make object equal function that takes 2 objects and compares all the properties and values of respective properties. Now iterate trough array using that function. Pass your object and array object as parameters. If it returns that comparison is true, store index.
The bigger the objects are the slower this works.The bigger array is the slower this works. The more time you search for same objects slower this works (you compare every time instead making hash once).
Also, if you are having a very large set of object, and search often but add or remove to it sparsely, consider ordering the array in order considering hash values. Then use binary tree search for that hash to find appropriate object instead iterating from start to end each time.
So I have an interesting issue I am not sure how to follow, I need to use lodash to search two arrays in an object, looking to see if x already exists, lets look at a console out put:
There are two keys I am interested in: questChains and singleQuests, I want to write two seperate functions using lodash to say: find me id x in the array of objects where questChains questChainID is equal to x.
The second function would say: Find me a quest in the array of objects where singleQuests questTitle equals y
So if we give an example, you can see that questChainId is a 1 so if I pass in a 1 to said function I would get true back, I don't actually care about the object its self, else I would get false.
The same goes for singleQuests, If I pass in hello (case insensitive) I would get back true because there is a quest with the questTitle of 'Hello'. Again I don't care about the object coming back.
The way I would write this is something like:
_.find(theArray, function(questObject){
_.find(questObject.questChains, function(questChain){
if (questChain.questChainId === 1) {
return true;
}
});
});
This is just for the quest chain id comparison. This seems super messy, why? Because I am nesting lodash find, I am also nesting if. It gets a bit difficult to read.
Is this the only way to do this? or is there a better way?
Yeah it can be expressed more simply.
Try something like this:
var exampleArray = [{
questChains: [{
questChainId: 1,
name: 'foo'
}, {
questChainId: 2,
name: 'bar'
}],
singleQuests: [{
questTitle: 'hello world'
}]
}, {
questChains: [{
questChainId: 77,
name: 'kappa'
}]
}];
var result = _.chain(exampleArray)
.pluck('questChains')
.flatten()
.findWhere({ questChainId: 2 })
.value();
console.log('result', result);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
Using chain and value is optional. They just let you chain together multiple lodash methods more succinctly.
pluck grabs a property from each object in an array and returns a new array of those properties.
flatten takes a nested array structure and flattens it into flat array structure.
findWhere will return the first element which matches the property name/value provided.
Combining all of these results in us fetching all questChain arrays from exampleArray, flattening them into a single array which can be more easily iterated upon, and then performing a search for the desired value.
Case-insensitive matching will be slightly more challenging. You'd either need to either replace findWhere with a method which accepts a matching function (i.e. find) or sanitize your input ahead of time. Either way you're going to need to call toLower, toUpper, or some variant on your names to standardize your search.
Javascript newbie here --
I have the following array:
var group = ({
one: value1,
two: value2,
three: value3
});
I want to check if array "group" is part of "groupsArray" and add it if doesn't or remove it if it does.
var groupLocate = $.inArray(group, groupsArray);
if(groupLocate ==-1){
groupsArray.push(group);
} else {
groupsArray.splice($.inArray(group, groupsArray),1);
}
This method works with single value arrays. Unfortunately, I can't get it to work in this case with three keys and values as groupLocate always returns -1.
What am I doing wrong?
Thanks.
First it helps to understand why $.inArray() didn't work. Let's try a simpler case. Paste this in to the JavaScript console in your browser on a page with jQuery loaded (such as this page we're on) and run it:
var object = { a: 1 };
var array = [ { a: 1 } ];
console.log( '$.inArray: ', $.inArray( object, array ) );
(Note the terminology: your group variable is an Object, not an Array.)
Now it looks like object is in the array, right? Why does it print -1 then? Try this:
console.log( object );
console.log( array[0] );
They look the same. How about:
console.log( '== or === works? ', object == array[0], object === array[0] );
Or even simpler:
console.log( 'Does {a:1} == {a:1}? ', {a:1} == {a:1} );
console.log( 'What about {} == {}? ', {} == {} );
Those all print false!
This is because two objects that happen to have the same content are still two separate objects, and when you use == or === to compare two objects, you are actually testing whether they are both references to one and the same object. Two different objects will never compare equal, even if they contain exactly the same content.
$.inArray() works like using an === operator to compare two objects - it won't find an object in an array unless it is the same object, not just an object with identical content.
Knowing this, does that suggest any possible ways to approach the problem? There are several ways you could write your own code to search the array for your object, or you may find it helpful to use a library such as Underscore.js which has many useful methods for arrays and objects.
For example, you could use _.findWhere( groupsArray, group ) to find the first match - with the caveat that it only compares the properties that are in the group object. For example, if group is {a:1}, it would match an object in the groupsArray array that was {a:1,b:2}.
If you need an exact match, you could combine Underscore's _.find() and _.isEqual() methods:
var index = _.find( groupsArray, function( element ) {
return _.isEqual( element, group );
});
Now one last thing to watch out for. Your code that pushes the group object onto the groupsArray array - you know that pushes the actual group object itself. It doesn't make a copy of it in the array, it's a reference to the very same object. (Ironically, this means that your original code to find group in the array would actually work in the case where you'd pushed that same group object onto the array yourself.)
If you want to make sure the elements in groupsArray are each their own independent object, and not a reference to another object floating around in your code, you can use another Underscore method to do a shallow copy:
groupsArray.push( _.clone(group) );
If group has any nested objects, though, this won't copy them. (I don't see a deep copy function in Underscore, although you could write one if you need it.)