2

Now that I'm programming in javascript more and more often, there's a task I'm coming across quite often that I wonder could be dealt with more elegantly.

It's about checking whether a variable, values say, is an array of Xs, or rather just an X, immediatly followed by an iteration over it or its elements. (X being object, string, number, ... anything really -- except array).

Especially when dealing with xml or json files, a single X is not wrapped in [ ] to make it a 1-element array, and my code breaks if I don't watch out.

I deal with this now in the following way:

if (!(values instanceof Array)) values = [values]
values.forEach(function(value){/*do stuff with value*/});

For now, I've written a function to take care of this,

function arrayIfNot(arr) {return (arr instanceof Array) ? arr : [arr];}

Which I can use as

arrayIfNot(values).forEach(function(value){/*do stuff with value*/});

but as it is such a common task, I'd be surprised if there isn't a common shortcut or library function (jQuery?) to do this.

Thanks!


EDIT:

I suppose I could extend the prototypes like so (haven't tried):

Array.prototype.toArray = function () {return this;};
String.prototype.toArray = function () {return [this];};
...

so that I could do

values.toArray().forEach(function(value){/*do stuff with value*/});

but I'm always warned against extending the prototype. What do you think?

Thanks again!

ElRudi
  • 2,122
  • 2
  • 18
  • 33
  • Have you tried using `Array.prototype.every()`? – Mr. Polywhirl Aug 05 '14 at 21:15
  • possible duplicate of [How do you check if a variable is an array in JavaScript?](http://stackoverflow.com/questions/767486/how-do-you-check-if-a-variable-is-an-array-in-javascript) –  Aug 06 '14 at 14:28

3 Answers3

4

Personally, I've adopted this idiom:

[].concat(a)

This takes advantage of the behavior of concat, which is that if its argument is a scalar, it just adds it to the array; if it is an array, it adds each of its elements.

  • Nice. I'm going to starting using that myself. :-) – Andrew Miner Aug 06 '14 at 16:25
  • Nice, very creative! Thanks – ElRudi Aug 06 '14 at 16:28
  • Although, on second thought -- this creates a new object, so that when looping through with `.forEach`, the original elements cannot be changed... right? – ElRudi Aug 06 '14 at 17:19
  • No...if you have `var a = {p: 1}; [].concat(a).forEach(function(v) { v.p = 2; });` will work as you expect, if that's what you are asking. –  Aug 07 '14 at 01:49
  • If objects, yes, but for primitive values, `var a = 1; [].concat(a).forEach(function(val) {val++;});` gives `a = 1` afterwards. – ElRudi Aug 07 '14 at 07:46
  • Admittedly, so do the `.toArray()` extension of the prototypes, as well as my `arrayIfNot` function.                                                                       I suppose (but can't check right now) that even changing the `arrayIfNot` function to `function arrayIfNot(arr) {arr = (arr instanceof Array) ? arr : [arr]; return arr;}` wouldn't work, because primitive values are passed by value. Therefore, it's necessary to write an assignment, like `values = arrayIfNot(values);` followed by `values.forEach()`. And an 'inline' `arrayIfNot(values).forEach()` wouldn't work. – ElRudi Aug 07 '14 at 08:02
2

There is a common way to do this in modern browsers, it's Array.isArray, and it's supported from IE9 and up.
MDN has a polyfill for non-supporting browsers.

jQuery has it's own version, jQuery.isArray

For your specific example, a common way to do it is

values = Array.isArray(values) ? values : [values];
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • But this only determines if the value is an array. It doesn't actually *cast* the value to to an array. – Boaz Aug 05 '14 at 21:15
  • @Boaz - Nope, you can't really typecast a primitive value to an array in javascript. You could of course create an array, but I don't think that's what the OP is asking. – adeneo Aug 05 '14 at 21:17
  • 1
    The OP is asking about the whole task, IMHO. i.e. - *if a value is not an array, "cast" it as such*. – Boaz Aug 05 '14 at 21:18
  • @Boaz - I've added something that does that, but it still creates a new array with the value as the first and only index in the array if it's not an array, there's no way to typecast. – adeneo Aug 05 '14 at 21:20
  • 1
    I agree. It might be worthwhile to mention this fact in the answer, since the final solution is not that dissimilar from the OP's. – Boaz Aug 05 '14 at 21:21
0

If you're willing to use a library, Underscore.js has a lot of fantastic additions to basic JavaScript. Using Underscore, I commonly do something like this:

function myFunction(arg) {
    arrayArg = _.flatten([arg])
}

If your arg might itself contain arrays which you need to preserve, just add true as a second argument to flatten.

Andrew Miner
  • 5,587
  • 2
  • 22
  • 26