Skip to content

JS type check (TypeScript supported) functions like `isPlainObject() isArray()` etc. A simple & small integration.

License

Notifications You must be signed in to change notification settings

mesqueeb/is-what

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

is What? πŸ™‰

npm version npm downloads/week

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'
  1. Below are the most common functions (with a special note on isNumber and isDate).
  2. Then we cover Objects (plain objects vs class instances).
  3. 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:

  • isPlainObject will only return true on plain JavaScript objects and not on classes or others
  • isAnyObject will be more loose and return true on 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 isPlainObject returns true only 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)

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.