Tiny, zero-dependency JavaScript type guards with first-class TypeScript support. Tree-shakable ESM by default.
npm i is-what
Or for deno available at: "deno.land/x/is_what"
Also check out is-where 🙈
Motivation
I built is-what because the existing solutions were all too complex or too poorly built.
I was looking for:
- A simple way to check any kind of type (including non-primitives)
- Be able to check if an object is a plain object
{}or a special object (like a class instance) ‼️ - Let TypeScript automatically know what type a value is when checking
And that's exactly what is-what is! (what a great wordplay 😃)
Usage
is-what is really easy to use, and most functions work just like you'd expect.
// import functions you want to use like so: import { isString, isDate, isPlainObject } from 'is-what'
- Below are the most common functions (with a special note on
isNumberandisDate). - Then we cover Objects (plain objects vs class instances).
- Finally we show TypeScript narrowing and
isObjectLike<T>.
Simple type check functions
// basics isBoolean(true) // true isBoolean(false) // true isUndefined(undefined) // true isNull(null) // true // strings isString('') // true isEmptyString('') // true isFullString('') // false isHexDecimal('60adf084f0fbdcab42de841e') // true isHexDecimal('60adf084f0fbdcab42de841e', 24) // check specific length of 24 (eg. MongoDB ObjectId) // numbers isNumber(0) // true isNumber('0') // false isNumber(NaN) // false * isPositiveNumber(1) // true isNegativeNumber(-1) // true // * see below for special NaN use cases! // arrays isArray([]) // true isEmptyArray([]) // true isFullArray([1]) // true // objects isPlainObject({}) // true * isEmptyObject({}) // true isFullObject({ a: 1 }) // true // * see below for special object (& class instance) use cases! // functions isFunction(function () {}) // true isFunction(() => {}) // true // dates isDate(new Date()) // true isDate(new Date('invalid date')) // false // maps & sets isMap(new Map()) // true isSet(new Set()) // true isWeakMap(new WeakMap()) // true isWeakSet(new WeakSet()) // true // others isRegExp(/\s/gi) // true isSymbol(Symbol()) // true isBlob(new Blob()) // true isFile(new File([''], '', { type: 'text/html' })) // true isError(new Error('')) // true isPromise(new Promise((resolve) => {})) // true // primitives isPrimitive('') // true // true for any of: boolean, null, undefined, number, string, symbol // iterables isIterable([1, 2, 3]) // true isIterable('hello') // true isIterable(new Map()) // true isIterable(new Set()) // true isIterable(function* generator() { yield 1 }) // true
Let's talk about NaN
isNaN is a built-in JS Function but it really makes no sense:
// 1) typeof NaN === 'number' // true // 🤔 ("not a number" is a "number"...) // 2) isNaN('1') // false // 🤔 the string '1' is not-"not a number"... so it's a number?? // 3) isNaN('one') // true // 🤔 'one' is NaN but `NaN === 'one'` is false...
With is-what the way we treat NaN makes a little bit more sense:
import { isNumber, isNaNValue } from 'is-what' // 1) isNumber(NaN) // false! // let's not treat NaN as a number // 2) isNaNValue('1') // false // if it's not NaN, it's not NaN!! // 3) isNaNValue('one') // false // if it's not NaN, it's not NaN!! isNaNValue(NaN) // true
isPlainObject vs isAnyObject
Checking for a JavaScript object can be really difficult. In JavaScript you can create classes that will behave just like JavaScript objects but might have completely different prototypes. With is-what I went for this classification:
isPlainObjectwill only returntrueon plain JavaScript objects and not on classes or othersisAnyObjectwill be more loose and returntrueon regular objects, classes, etc.
// define a plain object const plainObject = { hello: 'I am a good old object.' } // define a special object class SpecialObject { constructor(somethingSpecial) { this.speciality = somethingSpecial } } const specialObject = new SpecialObject('I am a special object! I am a class instance!!!') // check the plain object isPlainObject(plainObject) // returns true isAnyObject(plainObject) // returns true getType(plainObject) // returns 'Object' // check the special object isPlainObject(specialObject) // returns false !!!!!!!!! isAnyObject(specialObject) // returns true getType(specialObject) // returns 'Object'
Please note that
isPlainObjectreturnstrueonly for plain JavaScript objects.
Getting and checking for specific types
You can check for specific types with getType and isType:
import { getType, isType } from 'is-what' getType('') // returns 'String' // pass a Type as second param: isType('', String) // returns true
If you want to make sure your object inherits from a particular class or matches a toStringTag value, use isInstanceOf():
import { isInstanceOf } from 'is-what' isInstanceOf(new XMLHttpRequest(), 'EventTarget') // returns true isInstanceOf(globalThis, ReadableStream) // returns false
TypeScript
is-what makes TypeScript know the type during if statements. This means that a check returns the type of the payload for TypeScript users.
function isNumber(payload: unknown): payload is number { // return boolean } // All functions return a boolean for JavaScript, but act as type guards for TypeScript. // usage example: function fn(payload: string | number): number { if (isNumber(payload)) { // ↑ TypeScript already knows payload is a number here! return payload } return 0 }
isPlainObject and isAnyObject with TypeScript will declare the payload to be an object type with any props:
function isPlainObject(payload: unknown): payload is { [key: string]: unknown } function isAnyObject(payload: unknown): payload is { [key: string]: unknown } // The reason to return `{[key: string]: unknown}` is to be able to do if (isPlainObject(payload) && payload.id) return payload.id // if isPlainObject() would return `payload is object` then it would give an error at `payload.id`
isObjectLike
If you want more control over what kind of interface/type is cast when checking for objects:
To cast to a specific type while checking for isAnyObject, can use isObjectLike<T>:
import { isObjectLike } from 'is-what' const payload = { name: 'Mesqueeb' } // current type: `{ name: string }` // Without casting: if (isAnyObject(payload)) { // in here `payload` is casted to: `Record<string | number | symbol, unknown>` // WE LOOSE THE TYPE! } // With casting: // you can pass a specific type for TS that will be casted when the function returns if (isObjectLike<{ name: string }>(payload)) { // in here `payload` is casted to: `{ name: string }` }
Please note: this library will not actually check the shape of the object, you need to do that yourself.
isObjectLike<T> works like this under the hood:
function isObjectLike<T extends object>(payload: unknown): payload is T { return isAnyObject(payload) }
Meet the family (more tiny utils with TS support)
- is-what 🙉
- is-where 🙈
- merge-anything 🥡
- check-anything 👁
- remove-anything ✂️
- getorset-anything 🐊
- map-anything 🗺
- filter-anything ⚔️
- copy-anything 🎭
- case-anything 🐫
- flatten-anything 🏏
- nestify-anything 🧅
Source code
It's litterally just these functions:
function getType(payload) { return Object.prototype.toString.call(payload).slice(8, -1) } function isUndefined(payload) { return getType(payload) === 'Undefined' } function isString(payload) { return getType(payload) === 'String' } function isAnyObject(payload) { return getType(payload) === 'Object' } // etc...
See the full source code here.