Testing for a Value in JavaScript Array

Ever have an IF statement that has a whole bunch of OR (||) statements that keep testing a variable against a bunch of values?

if(name == 'bobby' || name == 'sue' || name == 'smith') { ... }

In JavaScript, there's an in operator that tests whether a property is in an object. We can actually use this to mimic the PHP function in_array.

if(name in {'bobby':'', 'sue':'','smith':''}) { ... }

If the value of name matches any of the keys in the object, it returns true. Now, I personally find that looks a little ugly since we have to assign values to each of our keys in order to get this to work. So, I made a quick function that simply converts an array into an object.

function oc(a)
{
  var o = {};
  for(var i=0;i<a.length;i++)
  {
    o[a[i]]='';
  }
  return o;
}

I called it oc because it's short and stands for object converter. Using our function, we can now test for values in an array like so:

if( name in oc(['bobby', 'sue','smith']) ) { ... }

It's important to note that this is really only advantageous for doing string comparisons since values get converted to strings once in the array.

25 in oc(['25', '10','15']) // returns true 
null in oc(['null']) // returns true
['test'] in oc(['test']) // returns false
['test'] in oc([['test']]) // returns true
['foo','bar'] in oc(['foo','bar']) // returns false
['foo','bar'] in oc([['foo','bar']]) // returns true
'foo,bar' in oc([['foo','bar']]) // returns true 
{} in oc({}) // returns true
new Object() in oc({}) // returns true
new Object() in oc([{albert:''}]) // returns true
'[object Object]' in oc([{albert:'test'}]) // returns true

As I hope is evident from that stream of examples, objects get converted into strings and it is that string that is compared. In any case, for simpler comparisons, I like the readability of the in operator and having people understand your code is sometimes half the battle.

Published September 08, 2006 · Updated September 14, 2011
Categorized as JavaScript
Short URL: http://snook.ca/s/665

Conversation

27 Comments · RSS feed
Graham B said on September 08, 2006

Sweet. I didn't know about In. At the moment I use

Array.prototype.has=function(v){
for (i=0;i<this.length;i++){
if (this[i]==v) return i;
}
return false;
}

which has the added benefit of returning the index of the value, but using it is ugly since you must use strict comparison to test it.

['foo','bar'].has('foo') // returns 0

But, it works for numeric values as well as strings.>

Graham B said on September 08, 2006

Sorry, didn't escape the less than. Should read

Array.prototype.has=function(v){
for (i=0; i<this.length; i++){
if (this[i]==v) return i;
}
return false;
}

Andy Kant said on September 08, 2006

I use regular expressions for this (when it comes to the case that an array is created solely for testing purposes). Creating an object literal seems like too much overhead for a test. If I used arrays at all, I would use Graham's method.

if (/^bobby|sue|smith$/.test(name))
{ .. }

Jonathan Snook said on September 08, 2006

Andy: huh, now why did I think .test() wouldn't work? And graham's solution is nice, although, I'd have it return true or false instead as it'd be nicer not to have to test for false in an if statement.

Andy Kant said on September 08, 2006

I suppose thats true, I forget about that zero-index sometimes.

Graham B said on September 08, 2006

You're right, although i originally intended the function as an index lookup rather than a containment test. Allowing both would probably be better

Array.prototype.has=function(v,i){
for (var j=0;j<this.length;j++){
if (this[j]==v) return (!i ? true : j);
}
return false;
}

['foo','bar'].has('foo') // returns true
['foo','bar'].has('foo',1) // returns 0

Andy Kant said on September 08, 2006

If you're adding extra arguments it might be a better idea to just separate them.

Array.prototype.indexOf = function(obj) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == obj)
      return i;
  }
  return -1;
}

Array.prototype.has = function(obj) {
  return this.indexOf(obj) >= 0;
}

Andrew said on September 08, 2006

I have been using a 'contains' method on Array.prototype for a while now, but i do like the idea of using in instead - the orthogonality with hashes is something i miss from python.

I think you could improve oc by just using arguments as its source array instead of a to remove the syntactic kruft introduced by the array literal eg:


function oc(arguments)
{
  var o = {};
  for(var i=0;i<arguments.length;i++)
  {
    o[arguments[i]]=null;
  }
  return o;
}


allowing checks like

if( name in oc('bobby', 'sue','smith') ) { ... }
Phil Freo said on September 09, 2006

I woud use a combination of the method mentioned in the article and Andrews comment #8, so that the function could receive EITHER an array OR string arguments.

function oc(arrayOrArgs)
{
  var o = {};
  var a = (isArray(arrayOrArgs)) ? arrayOrArgs : arguments;
  for(var i=0;i<a.length;i++)
  {
    o[a[i]]='';
  }
  return o;
}

allows for check like

if(name in oc('bobby','sue','smith')) { ... }

OR

if(name in oc(['bobby','sue','smith'])) { ... }

This would be useful because you want to be able to pass the function a variable which contains an array.

Danilo said on September 12, 2006

I found out about this type of code from the following blog post: Sets in JavaScript last year some time. It's decent for small arrays, but for larger arrays, you're really much better off using a function that drops out the moment it finds the proper value rather iterate over every element in the array.

I generally use an indexInArray() function which returns the index of the element within the array, and a -1 if not found for those situations:
if(indexInArray(arr, val)>-1){
// found element
}

function indexInArray(arr,val){
for(var i=0;i<arr.length;i++) if(arr[i]==val) return i;
return -1;
}

Olivier Mengué said on September 12, 2006

All the proposed solutions involving arrays are not efficient at all.
The only efficient way to check for strings in a set is to use RegExp.
However Andy's proposed solution is wrong too as the RegExp is incorrectly bounded : it matches "bobbyblabla", "blasuebla" and "blablasmith" (and many more).
The solution is to group the strings to match between ^ and $.
Here is the fix:

if (/^(?:bobby|sue|smith)$.test(name))
{ .. }
Olivier Mengué said on September 12, 2006

I forgot a '/':

if (/^(?:bobby|sue|smith)$/.test(name)) { .. }
Danilo said on September 12, 2006

Iterating through arrays is an attempt to make the search process more generic, so that you're not tied to one construct for strings, and another for other value types. It all depends on what you're trying to accomplish.

The indexInArray function I displayed above could easily be modified to allow a comparison function to be passed in as well to allow for custom checks against a varitey of object types, not just strings. (off the top of my head):

function indexInArray(arr, val, compFN){
for(var i=0, len=arr.length;i<len;i++){
if(compFN(arr[i], val)){
// positive match
}
}

If you're going the RegExp route and you know tha the set of strings could be arbitrary, then you could generate the RegExp on the fly using the Array join() method.

Olivier, do you have any links that show how the RegExp processing is compared to Array iteration in JavaScript? It would be good to see the data behind that.

Andy Kant said on September 12, 2006

I ran some tests using all of these methods and the results are negligible even for extremely large lists. Its all up to personal preference. I think that RegExp is the cleanest of all the methods though, which is why I use it.

Code for RegExp of pre-assembled array:

if (new RegExp('^(' + names.join('|') + ')$').test('sue'))
{ .. }
Andy Kant said on September 12, 2006

Actually, I usually use an equivalent to the Array.has if I have a pre-assembled array. That RegExp version for a pre-assembled array is pretty ugly.

Olivier Mengué said on September 12, 2006

As the string to check are only ASCII, the hash version can be shortened like this (quotes removed, value '' replaced by 0):

if(name in {bobby:0, sue:0, smith:0}) {

But I maintain that I prefer the RegExp version in case of a constant set of strings (sorry, no numbers to show), because the RegExp object is built just once for the whole execution, at the script parsing time.
When the set is created dynamically at runtime (not known at parsing time), creating the RegExp may not be worth it for just one pass.For multiple passes the RegExp must be cached in a variable outside a loop to be used inside a loop.
Also, building a reliable RegExp is difficult as it requires to escape meta characters: relying on the Andy version is acceptable only if we are sure that the names are pure alpha-numeric.
This kind of problem has been often been studied in Perl mailing lists as Perl has the same data structures. Have a look to this entry in the Perl FAQ.

Andrea Giammarchi said on September 14, 2006

You should simply use indexOf or some ... that's are JS 1.6 Array methods *

Array.prototype.has = function(obj) {
return this.indexOf(obj) !== -1;
};

Array.prototype.has = function(obj) {
return this.some(function(e){return e === obj});
};

this implementation is for multiple search (more than one value that should be present)

Array.prototype.has = function() {
var i = arguments.length,
result = [];
while(i)
result.push(this.indexOf(arguments[--i]) !== -1);
return result.every(function(e){return e});
};

alert([1,2,3].has(3)); //true
alert([1,2,3].has(2,4)); // false
alert([1,2,3].has(1,2,3)); // true

* To have JS 1.6 with every browser (starting from IE4) just develop your code adding JSL at the top - http://www.devpro.it/JSL/

Ash Searle said on September 20, 2006

I'm pretty sure there's a good reason to avoid using the 'in' operator, and that's that IE5.01 doesn't completely understand it .

// this works (for iteration)
for (foo in bar) { ... }
// but this doesn't (property testing)
if (foo in bar) { ... }

I suspect it has something to do with the lack of hasOwnProperty support too.
If I remember right, it throws a syntax error at parse time... but I could be wrong.

Chris Iufer said on September 22, 2006

If you already have prototype.js loaded, try this one:

if ( ['a','b','c'].any(match) ) { ... }

function match(value) { return (value == 'b');  }

Set function match to test whatever it is you want. In this case its testing every element against the string 'b'.

This can be simplified down even more if you want.

if ( ['a','b','c'].any( function(value) { return (value == 'b'); }) ) { ... }
kirie said on November 11, 2006

if(name== ('bobby' ||'sue' ||'smith')) { ... }

Mark Lucy said on December 19, 2006

You can simply check if an array has a key by using the 'in' operator

var arrTest = new Array();

arrTest["key"] = "value";

if("key" in arrTest){
// this code will run
}

XeNTaR said on May 31, 2007

Cool thx! This saves some extra for loops.
And I didn't even know you could use the "name in {}" format :)

eric fickes said on August 28, 2008

I had never used "IN" for anything but enumeration. Thanks a lot for this post!

baknudz said on September 01, 2008

Hi all,

Am new to Javascript, I was just wondering if you have better solution for my comparison of string.

Comparison description:

* I would like to have a lesser comparison or the number of OR statements...

ex :

if ( name == 'fish' && fish.name == 'Goldfish' || fish.name == 'goldfish' || fish.name == 'GOLDFISH' ... )
{
//do something here!
}

I think of using the no case sensitive of the strings..but I just don't know how to implement this.

Hoping for good experts advice.

Jonathan Snook said on September 01, 2008

if you're just looking to make string comparison case insensitive, just convert to lowercase. For example, if fish.name.toLowerCase() == 'goldfish'. That'll save having to test every variation.

Scot said on March 25, 2011

Just wanted to say thanks. This helped me a ton on a current project. I'm going to study the object literal a little more to get a better grasp on it but for now you saved me :)

Bahadir Katipoglu said on April 26, 2011

I think indexOf method can be used for same trick with one line solution.

if (['bobby','sue','smith'].indexOf(name) >= 0) {
alert('I know that person');
} else {
alert("I can't remember, sorry!");
}

Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.