I'm going to go a different route to the rest here, which try to tell if a variable is a specific, or a member of a specific set, of types.
JS is built on ducktyping; if something quacks like a string, we can and should use it like a string.
Is 7
a string? Then why does /\d/.test[7]
work?
Is {toString:[]=>['hello there']}
a string? Then why does [{toString:[]=>['hello there']}] + '\ngeneral kenobi!'
work?
These aren't questions about should the above work, the point is they do.
So I made a
duckyString[]
function
Below I test many cases not catered for by other answers. For each the code:
- sets a string-like variable
- runs an identical string operation on it and a real string to compare outputs [proving they can be treated like strings]
- converts the string-like to a real string to show you
duckyString[]
to normalise inputs for code that expects real strings
text = 'hello there';
out[text.replace[/e/g, 'E'] + ' ' + 'hello there'.replace[/e/g, 'E']];
out['Is string? ' + duckyString[text] + '\t"' + duckyString[text, true] + '"\n'];
text = new String['oh my'];
out[text.toUpperCase[] + ' ' + 'oh my'.toUpperCase[]];
out['Is string? ' + duckyString[text] + '\t"' + duckyString[text, true] + '"\n'];
text = 368;
out[[text + ' is a big number'] + ' ' + ['368' + ' is a big number']];
out['Is string? ' + duckyString[text] + '\t"' + duckyString[text, true] + '"\n'];
text = ['\uD83D', '\uDE07'];
out[text[1].charCodeAt[0] + ' ' + '😇'[1].charCodeAt[0]];
out['Is string? ' + duckyString[text] + '\t"' + duckyString[text, true] + '"\n'];
function Text[] { this.math = 7; }; Text.prototype = {toString:function[] { return this.math + 3 + ''; }}
text = new Text[];
out[String.prototype.match.call[text, '0'] + ' ' + text.toString[].match['0']];
out['Is string? ' + duckyString[text] + '\t"' + duckyString[text, true] + '"\n'];
This
is in the same vein as !!x
as opposed to x===true
and testing if something is array-like instead of necessitating an actual array.
jQuery objects; are they arrays? No. Are they good enough? Yeah, you can run them through Array.prototype
functions just fine.
It's this flexibility that gives JS its power, and testing for strings specifically makes your code less interoperable.
The output of the above is:
hEllo thErE hEllo thErE
Is string? true "hello there"
OH MY OH MY
Is string? true "oh my"
368 is a big number 368 is a big number
Is string? true "368"
56839 56839
Is string? true "😇"
0 0
Is string? true "10"
So, it's all about why you want to know if
something's a string.
If, like me, you arrived here from google and wanted to see if something was string-like, here's an answer.
It isn't even expensive unless you're working with really long or deeply nested char arrays.
This is because it is all if statements, no function calls like .toString[]
.
Except if you're trying to see if a char array with objects that only have toString[]
's or multi-byte characters, in which case there's no other way to check except to make
the string, and count characters the bytes make up, respectively
function duckyString[string, normalise, unacceptable] {
var type = null;
if [!unacceptable]
unacceptable = {};
if [string && !unacceptable.chars && unacceptable.to == null]
unacceptable.to = string.toString == Array.prototype.toString;
if [string == null]
;
//tests if `string` just is a string
else if [
!unacceptable.is &&
[typeof string == 'string' || string instanceof String]
]
type = 'is';
//tests if `string + ''` or `/./.test[string]` is valid
else if [
!unacceptable.to &&
string.toString && typeof string.toString == 'function' && string.toString != Object.prototype.toString
]
type = 'to';
//tests if `[...string]` is valid
else if [
!unacceptable.chars &&
[string.length > 0 || string.length == 0]
] {
type = 'chars';
//for each char
for [var index = 0; type && index < string.length; ++index] {
var char = string[index];
//efficiently get its length
var length = [[duckyString[char, false, {to:true}]] ?
char :
duckyString[char, true] || {}
].length;
if [length == 1]
continue;
//unicode surrogate-pair support
char = duckyString[char, true];
length = String.prototype[Symbol && Symbol.iterator];
if [![length = length && length.call[char]] || length.next[].done || !length.next[].done]
type = null;
}
}
//return true or false if they dont want to auto-convert to real string
if [![type && normalise]]
//return truthy or falsy with /null if they want why it's true
return [normalise == null] ? type != null : type;
//perform conversion
switch [type] {
case 'is':
return string;
case 'to':
return string.toString[];
case 'chars':
return Array.from[string].join[''];
}
}
Included are options to
- ask which method deemed it string-y
- exclude methods of string-detection [eg if you dont like
.toString[]
]
Here are more tests because I'm a completionist:
out['Edge-case testing']
function test[text, options] {
var result = duckyString[text, false, options];
text = duckyString[text, true, options];
out[result + ' ' + [[result] ? '"' + text + '"' : text]];
}
test[''];
test[null];
test[undefined];
test[0];
test[{length:0}];
test[{'0':'!', length:'1'}];
test[{}];
test[window];
test[false];
test[['hi']];
test[['\uD83D\uDE07']];
test[[['1'], 2, new String[3]]];
test[[['1'], 2, new String[3]], {chars:true}];
- All negative cases seem to be accounted for
- This should run on browsers >= IE8
- Char arrays with multiple bytes supported on browsers with string iterator support
Output:
Edge-case testing
is ""
null null
null null
to "0"
chars ""
chars "!"
null null
chars ""
to "false"
null null
chars "😇"
chars "123"
to "1,2,3"