Convert string parameter to an array of one element

Viewed 6833

I wrote a function which has to support two types of a paramter names for a list of values. Internally it deals with the parameter as an array.

A single name is given as string and multiples names are given as an array of strings.

// simplified example
let doSome =  names => names.map(name => name.toUpperCase())

names(['Bart', 'Lisa'])
// [ 'BART', 'LISA' ]
names('Homer')
// TypeError: names.map is not a function

I found a solution using Array.of() in combination with flatten() which needs some babel configuration.

doSome = names => Array.of(names).flatten().map(name => name.toUpperCase());

Is there an idiomatic way in JavaScript to get an array without a type check?

5 Answers

You can use Array.concat(), since concat accepts both arrays and non arrays:

const names = (v) => [].concat(v).map(name => name.toUpperCase())

console.log(names(['Bart', 'Lisa'])) // [ 'BART', 'LISA' ]
console.log(names('Homer')) // ['HOMER']

Short solution:

[names].flat()

If names is an array, it will be left as-is. Anything else will be converted to an array with one element.

Demo

Demo

This works because .flat() only flattens one level by default.

If names is not an array, [names] makes it an array with one element, and .flat() does nothing more because the array has no child arrays. If names is an array, [names] makes it an array with one child array, and .flat() brings the child array back up to be a parent array.

Alternative

This is more self-explanitory:

names instanceof Array ? names : [names]

This uses a simple ternary statement to do nothing to it if it is an array already or make it an array if it is not already.

Maybe an maybe upcoming method of Array#flat would help in this case (works actually only in Chrome and FF).

const names = unknown => [unknown].flat().map(name => name.toUpperCase())

console.log(names(['Bart', 'Lisa']));
console.log(names('Homer'));

You might not be able to implement it this way if you already have code depending on this function. Still, it would probably be cleaner to allow your function to accept a variable number of arguments with rest parameters.

It means you can call the function as names('Homer') or names('Bart', 'Lisa'):

function names(...args){
  return args.map(name => name.toUpperCase());
}

console.log(names('Bart', 'Lisa')); // [ 'BART', 'LISA' ]
console.log(names('Homer')); // ['HOMER']

If you really want to call the function with an array as argument, you can use the spread syntax :

console.log(names(...['Bart', 'Lisa'])); // [ "BART", "LISA" ]

If you use it with a string, you'll get back an array of characters, though:

console.log(names(...'Homer')); // [ "H", "O", "M", "E", "R" ]

Why not just check if the input is an array or not using isArray()?

I made another solution using this approach, also I put a control inside the map() so this don't fail when the name argument is null or undefined.

const names = x => (Array.isArray(x) ? x : [x]).map(name => name && name.toUpperCase());

console.log(JSON.stringify( names(['Bart', 'Lisa']) ));
console.log(JSON.stringify( names('Homer') ));
console.log(JSON.stringify( names('') ));
console.log(JSON.stringify( names(null) ));
console.log(JSON.stringify( names([null]) ));
console.log(JSON.stringify( names([undefined, "Roger", "Bob", null]) ));

Related