Olical
Olical

Reputation: 41402

Check if a variable is a string in JavaScript

How can I determine whether a variable is a string or something else in JavaScript?

Upvotes: 2853

Views: 3002696

Answers (30)

PHP Guru
PHP Guru

Reputation: 1566

If you need to be 100% certain that a value or object is a string, this is the only answer that won't result in false positives or negatives in edge cases.

The following works because String.prototype.toString() only works on strings (both primitives and objects) and throws on all other values.

Finally, the strict equality check confirms that it can be cast as a string primitive without throwing.

function isString(value) {
    try {
        return "".toString.call(value)===String(value)
    }
    catch(e) {// not a string
        return false
    }
}

Be advised that try/catch is somewhat slow and that string objects that pass this test might have overwritten methods that prevent normal usage.

Consider using typeof to test only for string primitives or simply cast your value to a string.

Upvotes: 0

PHP Guru
PHP Guru

Reputation: 1566

If you don't care about string objects you can use

typeof s == "string"// true if s is a string primitive

If you need to throw if s can't be represented as a string, just cast it:

s+='';

If you need to include string objects in your test, here is a way that works without try/catch but will throw in edge cases where it should probably throw anyway.

if (s!=null && s.toString==="".toString && s.toString()==s) {
    // s is a string primitive or object
}

Upvotes: 2

Georgi Naumov
Georgi Naumov

Reputation: 4211

I needed to check if the variable is a string and wanted to do it "quick and dirty". Here is my approach. Since only string have replaceAll method this is enough eficient.

const isString =  (input) => typeof input?.replaceAll === 'function'
// Examples of usage
console.log(isString(true))
// For both of them it will be true
console.log(isString(new String('test')))
console.log(isString('test'))

Upvotes: 1

s3c
s3c

Reputation: 1851

Below are two simple functions that should be fairly fast and work correctly for all the different types of string instances/variables. One for a scalar value check and the other for a vector of values to check.

const isString = x => ![null, undefined].includes(x) && !!x.toUpperCase
['', 'a', new String, String('a'), String(1)].map(v => isString(v)) // [true, true, true, true, true]
[null, undefined, 0, 1, true, [], ['a'], {}, {a: 'a'}].map(v => isString(v)) // [false, false, false, false, false, false, false, false, false]
const isStrings = x => x.map(v => ![null, undefined].includes(v) && !!v.toUpperCase).reduce((a, b) => a && b, true)
isStrings([]) // true
isStrings(['', 'a', new String, String('a'), String(1)]) // true
isStrings(['', 'a', new String, String('a'), String(1), 1]) // false
isStrings([0, 1, true, [], ['a'], {}, {a: 'a'}]) // false

Upvotes: -2

Orwellophile
Orwellophile

Reputation: 13963

function isString(x) {
  return Object.prototype.toString.call(x) === "[object String]"
}

Or, inline (I have an UltiSnip setup for this):

Object.prototype.toString.call(myVar) === "[object String]"

FYI, Pablo Santa Cruz's answer is wrong, because typeof new String("string") is object

DRAX's answer is accurate and functional and should be the correct answer (since Pablo Santa Cruz is most definitely incorrect, and I won't argue against the popular vote.)

However, this answer is also definitely correct, and actually the best answer (except, perhaps, for the suggestion of using lodash/underscore). disclaimer: I contributed to the lodash 4 codebase.

My original answer (which obviously flew right over a lot of heads) follows:

I transcoded this from underscore.js:

['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'].forEach( 
    function(name) { 
        window['is' + name] = function(obj) {
              return toString.call(obj) == '[object ' + name + ']';
    }; 
});

That will define isString, isNumber, etc.


In Node.js, this can be implemented as a module:

module.exports = [
  'Arguments',
  'Function', 
  'String', 
  'Number', 
  'Date', 
  'RegExp'
].reduce( (obj, name) => {
  obj[ 'is' + name ] = x => toString.call(x) == '[object ' + name + ']';
  return obj;
}, {});

[edit]: Object.prototype.toString.call(x) works to delineate between functions and async functions as well:

const fn1 = () => new Promise((resolve, reject) => setTimeout(() => resolve({}), 1000))
const fn2 = async () => ({})

console.log('fn1', Object.prototype.toString.call(fn1))
console.log('fn2', Object.prototype.toString.call(fn2))

// Test this approach:

function isString(x) {
  return Object.prototype.toString.call(x) === "[object String]"
}

let falseCases = [
  [ 'null', null ],
  [ 'undefined', undefined ],
  [ 'object', { a: 1, b: 2 } ],
  [ 'array', [ 1, 2, 3 ] ],
  [ 'number', 123 ],
  [ 'zero', 0 ],
  [ 'RegExp', new RegExp('hello') ],
  [ 'number with valueOf returning string', Object.assign(10, { valueOf: () => 'abc' }) ],
  [ 'object pretending to be string', { constructor: String } ]
];
let trueCases = [
  [ 'empty literal string', '' ],
  [ 'unicode string literal', String.fromCharCode(10000) ],
  [ 'empty boxed string', new String('') ],
  [ 'unicode boxed string', new String(String.fromCharCode(10000)) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: Array }) ],
  [ 'string with overwritten "toString"', Object.assign('hi', { toString: 123 }) ],
  [ 'string with overwritten "valueOf"', Object.assign('hi', { valueOf: 123 }) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: RegExp }) ],
  [ 'proxied string', new Proxy(new String('hello'), {}) ],
];

console.log('NEGATIVE TESTS:');
for (let [ name, val ] of falseCases) {
  console.log(`Test ${name}:\n  Expect: false\n  Got:    ${isString(val)}`); 
}

console.log('\nPOSITIVE TESTS:');
for (let [ name, val ] of trueCases) {
  console.log(`Test ${name}:\n  Expect: true\n  Got:    ${isString(val)}`); 
}

Upvotes: 264

zlatanned
zlatanned

Reputation: 179

isString() checks whether the passed argument is a string or not, using optional chaining and the latest standards:

const isString = (value) => { 
    return value?.constructor === String;
}

Upvotes: 6

Sarl sbeer
Sarl sbeer

Reputation: 55

We also can use isFinite() rather than typeof or isNAN(). Check this:

var name="somename",trickyName="123", invalidName="123abc";
typeof name == typeof trickyName == typeof invalidName == "string" 🤷‍♀️

isNAN(name)==true
isNAN(trickyName)==false
isNAN(invalidName)==true 👀

where:

isFinite(name) == false
isFinite(trickyName)== true
isFinite(invalidName)== true

So we can do:

if(!isFinite(/*any string*/))
  console.log("it is string type for sure")

Notice that:

    isFinite("asd123")==false
    isNAN("asd123")==true

Upvotes: 0

Pablo Santa Cruz
Pablo Santa Cruz

Reputation: 181430

You can use typeof operator:

var booleanValue = true;
var numericalValue = 354;
var stringValue = "This is a String";
var stringObject = new String("This is a String Object");
console.log(typeof booleanValue) // displays "boolean"
console.log(typeof numericalValue) // displays "number"
console.log(typeof stringValue) // displays "string"
console.log(typeof stringObject) // displays "object"

Example from this webpage. (Example was slightly modified though).

This won't work as expected in the case of strings created with new String(), but this is seldom used and recommended against[1][2]. See the other answers for how to handle these, if you so desire.

// Test this approach:

let isString = value => typeof value === 'string';

let falseCases = [
  [ 'null', null ],
  [ 'undefined', undefined ],
  [ 'object', { a: 1, b: 2 } ],
  [ 'array', [ 1, 2, 3 ] ],
  [ 'number', 123 ],
  [ 'zero', 0 ],
  [ 'RegExp', new RegExp('hello') ],
  [ 'number with valueOf returning string', Object.assign(10, { valueOf: () => 'abc' }) ],
  [ 'object pretending to be string', { constructor: String } ]
];
let trueCases = [
  [ 'empty literal string', '' ],
  [ 'unicode string literal', String.fromCharCode(10000) ],
  [ 'empty boxed string', new String('') ],
  [ 'unicode boxed string', new String(String.fromCharCode(10000)) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: Array }) ],
  [ 'string with overwritten "toString"', Object.assign('hi', { toString: 123 }) ],
  [ 'string with overwritten "valueOf"', Object.assign('hi', { valueOf: 123 }) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: RegExp }) ],
  [ 'proxied string', new Proxy(new String('hello'), {}) ],
];

console.log('NEGATIVE TESTS:');
for (let [ name, val ] of falseCases) {
  console.log(`Test ${name}:\n  Expect: false\n  Got:    ${isString(val)}`); 
}

console.log('\nPOSITIVE TESTS:');
for (let [ name, val ] of trueCases) {
  console.log(`Test ${name}:\n  Expect: true\n  Got:    ${isString(val)}`); 
}


  1. The Google JavaScript Style Guide says to never use primitive object wrappers.
  2. Douglas Crockford recommended that primitive object wrappers be deprecated.

Upvotes: 2439

Gershom Maes
Gershom Maes

Reputation: 8170

This function is a safe way to check for any type:

let isType = (value, type) => {
    if (type == null || value == null) return value === type;
    return Object.getPrototypeOf(value ?? {}).constructor === type;
}

// All the following work as expected:
isType('abc', String);
isType(123, Number);
isType(/abc/, RegExp);
isType(null, null);
isType(undefined, undefined);

From this we can derive:

let isString = value => isType(value, String);

// Test this approach:

let isType = (value, type) => {
    if (type == null || value == null) return value === type;
    return Object.getPrototypeOf(value ?? {}).constructor === type;
}
let isString = value => isType(value, String);

let falseCases = [
  [ 'null', null ],
  [ 'undefined', undefined ],
  [ 'object', { a: 1, b: 2 } ],
  [ 'array', [ 1, 2, 3 ] ],
  [ 'number', 123 ],
  [ 'zero', 0 ],
  [ 'RegExp', new RegExp('hello') ],
  [ 'number with valueOf returning string', Object.assign(10, { valueOf: () => 'abc' }) ],
  [ 'object pretending to be string', { constructor: String } ]
];
let trueCases = [
  [ 'empty literal string', '' ],
  [ 'unicode string literal', String.fromCharCode(10000) ],
  [ 'empty boxed string', new String('') ],
  [ 'unicode boxed string', new String(String.fromCharCode(10000)) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: Array }) ],
  [ 'string with overwritten "toString"', Object.assign('hi', { toString: 123 }) ],
  [ 'string with overwritten "valueOf"', Object.assign('hi', { valueOf: 123 }) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: RegExp }) ],
  [ 'proxied string', new Proxy(new String('hello'), {}) ],
];

console.log('NEGATIVE TESTS:');
for (let [ name, val ] of falseCases) {
  console.log(`Test ${name}:\n  Expect: false\n  Got:    ${isString(val)}`); 
}

console.log('\nPOSITIVE TESTS:');
for (let [ name, val ] of trueCases) {
  console.log(`Test ${name}:\n  Expect: true\n  Got:    ${isString(val)}`); 
}

Upvotes: 0

DRAX
DRAX

Reputation: 32411

This is what works for me:

if (typeof myVar === 'string' || myVar instanceof String)
// it's a string
else
// it's something else

// Test this approach:

let isString = value => typeof value === 'string' || value instanceof String;

let falseCases = [
  [ 'null', null ],
  [ 'undefined', undefined ],
  [ 'object', { a: 1, b: 2 } ],
  [ 'array', [ 1, 2, 3 ] ],
  [ 'number', 123 ],
  [ 'zero', 0 ],
  [ 'RegExp', new RegExp('hello') ],
  [ 'number with valueOf returning string', Object.assign(10, { valueOf: () => 'abc' }) ],
  [ 'object pretending to be string', { constructor: String } ]
];
let trueCases = [
  [ 'empty literal string', '' ],
  [ 'unicode string literal', String.fromCharCode(10000) ],
  [ 'empty boxed string', new String('') ],
  [ 'unicode boxed string', new String(String.fromCharCode(10000)) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: Array }) ],
  [ 'string with overwritten "toString"', Object.assign('hi', { toString: 123 }) ],
  [ 'string with overwritten "valueOf"', Object.assign('hi', { valueOf: 123 }) ],
  [ 'string with overwritten "constructor"', Object.assign('hi', { constructor: RegExp }) ],
  [ 'proxied string', new Proxy(new String('hello'), {}) ],
];

console.log('NEGATIVE TESTS:');
for (let [ name, val ] of falseCases) {
  console.log(`Test ${name}:\n  Expect: false\n  Got:    ${isString(val)}`); 
}

console.log('\nPOSITIVE TESTS:');
for (let [ name, val ] of trueCases) {
  console.log(`Test ${name}:\n  Expect: true\n  Got:    ${isString(val)}`); 
}

Upvotes: 2981

Mulan
Mulan

Reputation: 135396

I find this simple technique useful to type-check for String -

String(x) === x // true, if x is a string
                // false in every other case

const test = x =>
  console.assert
    ( String(x) === x
    , `not a string: ${x}`
    )

test("some string")
test(123)           // assertion failed
test(0)             // assertion failed
test(/some regex/)  // assertion failed
test([ 5, 6 ])      // assertion failed
test({ a: 1 })      // assertion failed
test(x => x + 1)    // assertion failed

The same technique works for Number too -

Number(x) === x // true, if x is a number
                // false in every other case

const test = x =>
  console.assert
    ( Number(x) === x
    , `not a number: ${x}`
    )

test("some string") // assertion failed
test(123)           
test(0)             
test(/some regex/)  // assertion failed
test([ 5, 6 ])      // assertion failed
test({ a: 1 })      // assertion failed
test(x => x + 1)    // assertion failed

And for RegExp -

RegExp(x) === x // true, if x is a regexp
                // false in every other case

const test = x =>
  console.assert
    ( RegExp(x) === x
    , `not a regexp: ${x}`
    )

test("some string") // assertion failed
test(123)           // assertion failed
test(0)             // assertion failed
test(/some regex/)  
test([ 5, 6 ])      // assertion failed
test({ a: 1 })      // assertion failed
test(x => x + 1)    // assertion failed

Same for Object -

Object(x) === x // true, if x is an object
                // false in every other case

NB, regexps, arrays, and functions are considered objects too.

const test = x =>
  console.assert
    ( Object(x) === x
    , `not an object: ${x}`
    )

test("some string") // assertion failed
test(123)           // assertion failed
test(0)             // assertion failed
test(/some regex/)  
test([ 5, 6 ])      
test({ a: 1 })      
test(x => x + 1)    

But, checking for Array is a bit different -

Array.isArray(x) === x // true, if x is an array
                       // false in every other case

const test = x =>
  console.assert
    ( Array.isArray(x)
    , `not an array: ${x}`
    )

test("some string") // assertion failed
test(123)           // assertion failed
test(0)             // assertion failed
test(/some regex/)  // assertion failed
test([ 5, 6 ])      
test({ a: 1 })      // assertion failed
test(x => x + 1)    // assertion failed

This technique does not work for Functions however -

Function(x) === x // always false

For @Faither -

const fmt = JSON.stringify

function test1() {
  const a = "1"
  const b = 1
  console.log(`Number(${fmt(a)}) === ${fmt(b)}`, Number(a) === b) // true
}

function test2() {
  const a = "1"
  const b = 1
  console.log(`Number.isInteger(${fmt(a)})`, Number.isInteger(a)) // false
  console.log(`Number.isInteger(${fmt(b)})`, Number.isInteger(b)) // true
}

function test3() {
  name = 1 // global name will always be a string
  console.log(fmt(name)) // "1"
  console.log(`String(${fmt(name)}) === ${fmt(name)}`, String(name) === name) // true
}

function test4() {
  const name = 1 // local name 
  console.log(fmt(name)) // 1
  console.log(`String(${fmt(name)}) === ${fmt(name)}`, String(name) === name) // false
}

test1(); test2(); test3(); test4()

Upvotes: 16

ZJR
ZJR

Reputation: 9592

I have a technique that's stupid. But straightforward.

if(maybeAString.toUpperCase)
  weHaveAString(maybeAString)

Yeah, it's far from perfect. But it is straightforward.

Upvotes: 1

Filip Seman
Filip Seman

Reputation: 1744

Implementation from lodash library v4.0.0

// getTag.js

const toString = Object.prototype.toString;

/**
 * Gets the `toStringTag` of `value`.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the `toStringTag`.
 */
function getTag(value) {
    if (value == null) {
        return value === undefined 
            ? "[object Undefined]" 
            : "[object Null]";
    }
    return toString.call(value);
}
// isString.js

import getTag from "./getTag.js";

/**
 * Checks if `value` is classified as a `String` primitive or object.
 *
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a string, else `false`.
 * @example
 *
 * isString('abc')
 * // => true
 *
 * isString(1)
 * // => false
 */
function isString(value) {
    const type = typeof value;
    return (
        type === "string" || (type === "object" &&
                              value != null &&
                              !Array.isArray(value) &&
                              getTag(value) == "[object String]")
    );
}

export default isString;

Upvotes: 2

Mohamad
Mohamad

Reputation: 608

A code to have only string without any numbers

isNaN("A") = true;
parseInt("A") = NaN;
isNaN(NaN) = true;

Than we can use isNaN(parseInt()) to have only the string

let ignoreNumbers = "ad123a4m";

let ign = ignoreNumbers.split("").map((ele) => isNaN(parseInt(ele)) ? ele : "").join("");

console.log(ign);

Upvotes: 0

David
David

Reputation: 2746

Edit: The current way to do it is typeof value === 'string'. For example:

const str = 'hello';
if (typeof str === 'string') { ... }

Below has been deprecated since node v4.

If you work on the node.js environment, you can simply use the built-in function isString in utils.

const util = require('util');
if (util.isString(myVar)) {}

Upvotes: 66

Marcel Kohls
Marcel Kohls

Reputation: 1897

A simple and fast way to test can be using the constructor name attribute.

let x = "abc";
console.log(x.constructor.name === "String"); // true

let y = new String('abc');
console.log(y.constructor.name === "String"); // true

Performance

enter image description here

Upvotes: 10

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92657

Performance

Today 2020.09.17 I perform tests on MacOs HighSierra 10.13.6 on Chrome v85, Safari v13.1.2 and Firefox v80 for chosen solutions.

Results

For all browsers (and both test cases)

  • solutions typeof||instanceof (A, I) and x===x+'' (H) are fast/fastest
  • solution _.isString (lodash lib) is medium/fast
  • solutions B and K are slowest

enter image description here

Update: 2020.11.28 I update results for x=123 Chrome column - for solution I there was probably an error value before (=69M too low) - I use Chrome 86.0 to repeat tests.

Details

I perform 2 tests cases for solutions A B C D E F G H I J K L

  • when variable is string - you can run it HERE
  • when variable is NOT string - you can run it HERE

Below snippet presents differences between solutions

// https://stackoverflow.com/a/9436948/860099
function A(x) {
  return (typeof x == 'string') || (x instanceof String)
}

// https://stackoverflow.com/a/17772086/860099
function B(x) {
  return Object.prototype.toString.call(x) === "[object String]"
}

// https://stackoverflow.com/a/20958909/860099
function C(x) {
  return _.isString(x);
}

// https://stackoverflow.com/a/20958909/860099
function D(x) {
  return $.type(x) === "string";
}

// https://stackoverflow.com/a/16215800/860099
function E(x) {
  return x?.constructor === String;
}

// https://stackoverflow.com/a/42493631/860099
function F(x){
  return x?.charAt != null
}


// https://stackoverflow.com/a/57443488/860099
function G(x){
  return String(x) === x
}

// https://stackoverflow.com/a/19057360/860099
function H(x){
  return x === x + ''
}

// https://stackoverflow.com/a/4059166/860099
function I(x) {
  return typeof x == 'string'
}

// https://stackoverflow.com/a/28722301/860099
function J(x){
  return x === x?.toString()
}

// https://stackoverflow.com/a/58892465/860099
function K(x){
  return x && typeof x.valueOf() === "string"
}

// https://stackoverflow.com/a/9436948/860099
function L(x) {
  return x instanceof String
}

// ------------------
//     PRESENTATION
// ------------------

console.log('Solutions results for different inputs \n\n');
console.log("'abc' Str  ''  ' ' '1' '0'  1   0   {} [] true false null undef");

let tests = [ 'abc', new String("abc"),'',' ','1','0',1,0,{},[],true,false,null,undefined];

[A,B,C,D,E,F,G,H,I,J,K,L].map(f=> {  
console.log(
  `${f.name}   ` + tests.map(v=> (1*!!f(v)) ).join`   `
)})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>


This shippet only presents functions used in performance tests - it not perform tests itself!

And here are example results for chrome

enter image description here

Upvotes: 32

Pato
Pato

Reputation: 679

You can use this function to determine the type of anything:

var type = function(obj) {
    return Object.prototype.toString.apply(obj).replace(/\[object (.+)\]/i, '$1').toLowerCase();
};

To check if a variable is a string:

type('my string') === 'string' //true
type(new String('my string')) === 'string' //true
type(`my string`) === 'string' //true
type(12345) === 'string' //false
type({}) === 'string' // false

https://codepen.io/patodiblasi/pen/NQXPwY?editors=0012

To check for other types:

type(null) //null
type(undefined) //undefined
type([]) //array
type({}) //object
type(function() {}) //function
type(123) //number
type(new Number(123)) //number
type(/some_regex/) //regexp
type(Symbol("foo")) //symbol

Upvotes: 9

Hashbrown
Hashbrown

Reputation: 13023

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 <type>/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"

Upvotes: 2

whmkr
whmkr

Reputation: 3085

if (s && typeof s.valueOf() === "string") {
  // s is a string
}

Works for both string literals let s = 'blah' and for Object Strings let s = new String('blah')

Upvotes: 14

yaya
yaya

Reputation: 8253

A Typechecker helper:

function isFromType(variable, type){
  if (typeof type == 'string') res = (typeof variable == type.toLowerCase())
  else res = (variable.constructor == type)
  return res
}

usage:

isFromType('cs', 'string') //true
isFromType('cs', String) //true
isFromType(['cs'], Array) //true
isFromType(['cs'], 'object') //false

Also if you want it to be recursive(like Array that is an Object), you can use instanceof.

(['cs'] instanceof Object //true)

Upvotes: 3

Noris
Noris

Reputation: 157

A simple solution would be:

var x = "hello"

if(x === x.toString()){
// it's a string 
}else{
// it isn't
}

Upvotes: 2

Grant Miller
Grant Miller

Reputation: 29047

The following method will check if any variable is a string (including variables that do not exist).

const is_string = value => {
  try {
    return typeof value() === 'string';
  } catch (error) {
    return false;
  }
};

let example = 'Hello, world!';

console.log(is_string(() => example)); // true
console.log(is_string(() => variable_doesnt_exist)); // false

Upvotes: 3

customcommander
customcommander

Reputation: 18961

I can't honestly see why one would not simply use typeof in this case:

if (typeof str === 'string') {
  return 42;
}

Yes it will fail against object-wrapped strings (e.g. new String('foo')) but these are widely regarded as a bad practice and most modern development tools are likely to discourage their use. (If you see one, just fix it!)

The Object.prototype.toString trick is something that all front-end developers have been found guilty of doing one day in their careers but don't let it fool you by its polish of clever: it will break as soon as something monkey-patch the Object prototype:

const isString = thing => Object.prototype.toString.call(thing) === '[object String]';

console.log(isString('foo'));

Object.prototype.toString = () => 42;

console.log(isString('foo'));

Upvotes: 39

Rob Brander
Rob Brander

Reputation: 3781

This is a great example of why performance matters:

Doing something as simple as a test for a string can be expensive if not done correctly.

For example, if I wanted to write a function to test if something is a string, I could do it in one of two ways:

1) const isString = str => (Object.prototype.toString.call(str) === '[object String]');

2) const isString = str => ((typeof str === 'string') || (str instanceof String));

Both of these are pretty straight forward, so what could possibly impact performance? Generally speaking, function calls can be expensive, especially if you don't know what's happening inside. In the first example, there is a function call to Object's toString method. In the second example, there are no function calls, as typeof and instanceof are operators. Operators are significantly faster than function calls.

When the performance is tested, example 1 is 79% slower than example 2!

See the tests: https://jsperf.com/isstringtype

Upvotes: 19

Tomozma
Tomozma

Reputation: 61

This is good enough for me.

WARNING: This is not a perfect solution. See the bottom of my post.

Object.prototype.isString = function() { return false; };
String.prototype.isString = function() { return true; };

var isString = function(a) {
  return (a !== null) && (a !== undefined) && a.isString();
};

And you can use this like below.

//return false
isString(null);
isString(void 0);
isString(-123);
isString(0);
isString(true);
isString(false);
isString([]);
isString({});
isString(function() {});
isString(0/0);

//return true
isString("");
isString(new String("ABC"));

WARNING: This works incorrectly in the case:

//this is not a string
var obj = {
    //but returns true lol
    isString: function(){ return true; }
}

isString(obj) //should be false, but true

Upvotes: 2

ScottyG
ScottyG

Reputation: 3410

I like to use this simple solution:

var myString = "test";
if(myString.constructor === String)
{
     //It's a string
}

Upvotes: 16

Aryeh Beitz
Aryeh Beitz

Reputation: 2078

I'm not sure if you mean knowing if it's a type string regardless of its contents, or whether it's contents is a number or string, regardless of its type.

So to know if its type is a string, that's already been answered.
But to know based on its contents if its a string or a number, I would use this:

function isNumber(item) {
    return (parseInt(item) + '') === item;
}

And for some examples:

isNumber(123);   //true
isNumber('123'); //true
isNumber('123a');//false
isNumber('');    //false

Upvotes: -3

Cody
Cody

Reputation: 10025

Best way:

var s = 'String';
var a = [1,2,3];
var o = {key: 'val'};

(s.constructor === String) && console.log('its a string');
(a.constructor === Array) && console.log('its an array');
(o.constructor === Object) && console.log('its an object');
(o.constructor === Number || s.constructor === Boolean) && console.log('this won\'t run');

Each of these has been constructed by its appropriate class function, like "new Object()" etc.

Also, Duck-Typing: "If it looks like a duck, walks like a duck, and smells like a duck - it must be an Array" Meaning, check its properties.

Hope this helps.

Edit; 12/05/2016

Remember, you can always use combinations of approaches too. Here's an example of using an inline map of actions with typeof:

var type = { 'number': Math.sqrt.bind(Math), ... }[ typeof datum ];

Here's a more 'real world' example of using inline-maps:

function is(datum) {
    var isnt = !{ null: true, undefined: true, '': true, false: false, 0: false }[ datum ];
    return !isnt;
}
console.log( is(0), is(false), is(undefined), ... );  // >> true true false

This function would use [ custom ] "type-casting" -- rather, "type-/-value-mapping" -- to figure out if a variable actually "exists". Now you can split that nasty hair between null & 0!

Many times you don't even care about its type. Another way to circumvent typing is combining Duck-Type sets:

this.id = "998";  // use a number or a string-equivalent
function get(id) {
    if (!id || !id.toString) return;
    if (id.toString() === this.id.toString()) http( id || +this.id );
    // if (+id === +this.id) ...;
}

Both Number.prototype and String.prototype have a .toString() method. You just made sure that the string-equivalent of the number was the same, and then you made sure that you passed it into the http function as a Number. In other words, we didn't even care what its type was.

Hope that gives you more to work with :)

Upvotes: 40

ling
ling

Reputation: 10037

function isString (obj) {
  return (Object.prototype.toString.call(obj) === '[object String]');
}

I saw that here:

http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

Upvotes: 49

Related Questions