A complete guide to check data types in JavaScript
#javascriptJavaScript is such an imperfect language that you can't even rely on it for simple things like checking data types.
In JavaScript, there are 8 data types, and there are mainly 3 ways to check which type a given value is. In this post, I am going to cover what they are, when to use them, and, in my opinion, which one is the best.
Tl;dr#
- There are no built-in ways to check data types in JavaScript that are straightforward and all-encompassing. All of the current native ways are imperfect (or just straight up buggy), so we are stuck rolling our own.
- Forget about
typeof
andinstanceof
, the best way to check data types is to useObject.prototype.toString
the typeof operator#
The typeof
operator is probably the first that comes to mind. It works fine for number
, string
, undefined
, boolean
, symbol
, function
but there are some pitfalls to watch out for when using typeof
:
- it is a known bug that
typeof null === 'object'
. The history of typeof null covers this bug in details. - it doesn't differentiate plain objects from other built-in objects, except for
function
.typeof []; // 'object' typeof {}; // 'object' typeof new Date(); // 'object' typeof /foo/; // 'object'
Let's move on to the next option - the instanceof
operator.
the instanceof operator#
The instanceof
operator checks for the constructor of an object. In other words, it tests which class created a given value.
let Car = function () {};
let benz = new Car();
benz instanceof Car; // true
Because of this, instaceof
can correctly determine types for objects, but not for primitive types.
[] instanceof Array // ✅ true
(() => {}) instanceof Function; // ✅ true
new Map() instanceof Map; // ✅ true
1 instanceof Number; // ❌ false
'foo' instanceof String; // ❌ false
Also, since instanceof
checks the constructor of an object, if you modify the prototype of an object during the runtime, the result of the instanceof
check can change:
const array = [];
array instanceof Array; // ✅ true
Object.setPrototypeOf(array, null);
array instanceof Array; // ❌ false
As you can see, neither typeof
or instanceof
is perfect and most of the time people have to leverage and combine both approaches to do type checking.
the Object.prototype.toString method#
Turns out there is a third, arguably better way to check data types in JavaScript – Object.prototype.toString
. It is a method that exists on Object.prototype
, which returns a string value used for the description of an object based on its Symbol.toStringTag
.
If you take a look at its spec, you will realize that it is actually the all-encompassing solution we have been looking for when it comes to check data types.
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call('1'); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(new String('string')); // "[object String]"
Object.prototype.toString.call(function () {}); // "[object Function]"
Object.prototype.toString.call(null); //"[object Null]"
Object.prototype.toString.call(undefined); //"[object Undefined]"
Object.prototype.toString.call(/123/g); //"[object RegExp]"
Object.prototype.toString.call(new Date()); //"[object Date]"
Object.prototype.toString.call([]); //"[object Array]"
Object.prototype.toString.call(document); //"[object HTMLDocument]"
Object.prototype.toString.call(window); //"[object Window]
With a little bit of string processing using a regexp
, we can come up with the following solution that can account for all cases:
function getType(obj) {
const lowerCaseTheFirstLetter = (str) => str[0].toLowerCase() + str.slice(1);
const type = typeof obj;
if (type !== 'object') {
return type;
}
return lowerCaseTheFirstLetter(
Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1')
);
}
getType([]); // "array"
getType('123'); // "string"
getType(null); // "null"
getType(undefined); // "undefined"
getType(); // "undefined"
getType(function () {}); // "function"
getType(/123/g); // "regExp"
getType(new Date()); // "date"
getType(new Map()); // "map"
getType(new Set()); // "set"
In fact, many runtime type validation libraries use this technique under the hood, such as this one.
Note: Object.prototype.toString
can still be fooled...
Since Object.prototype.toString
checks for Symbol.toStringTag
, if you change Symbol.toStringTag
, then you can manipulate the result:
const foo = {}
foo[Symbol.toStringTag] = 'Foo'
Object.prototype.toString.call(foo) // '[object Foo]'