1 | # is What? 🙉
|
2 |
|
3 | Very simple & small JS type check functions. It's fully TypeScript supported!
|
4 |
|
5 | ```
|
6 | npm i is-what
|
7 | ```
|
8 |
|
9 | Or for deno available at: `"deno.land/x/is_what"`
|
10 |
|
11 | ## Motivation
|
12 |
|
13 | I built is-what because the existing solutions were all too complex or too poorly built.
|
14 |
|
15 | I was looking for:
|
16 |
|
17 | - A simple way to check any kind of type (including non-primitives)
|
18 | - Be able to check if an object is a plain object `{}` or a special object (like a class instance) ‼️
|
19 | - Let TypeScript automatically know what type a value is when checking
|
20 |
|
21 | And that's exactly what `is-what` is! (what a great wordplay 😃)
|
22 |
|
23 | ## Usage
|
24 |
|
25 | is-what is really easy to use, and most functions work just like you'd expect.
|
26 |
|
27 | ```js
|
28 | // import functions you want to use like so:
|
29 | import { isString, isDate, isPlainObject } from 'is-what'
|
30 | ```
|
31 |
|
32 | 1. First I'll go over the simple functions available. Only `isNumber` and `isDate` have special treatment.
|
33 | 2. After that I'll talk about working with Objects (plain objects vs class instances etc.).
|
34 | 3. Lastly I'll talk about TypeScript implementation
|
35 |
|
36 | ### Simple type check functions
|
37 |
|
38 | ```js
|
39 | // basics
|
40 | isBoolean(true) // true
|
41 | isBoolean(false) // true
|
42 | isUndefined(undefined) // true
|
43 | isNull(null) // true
|
44 |
|
45 | // strings
|
46 | isString('') // true
|
47 | isEmptyString('') // true
|
48 | isFullString('') // false
|
49 |
|
50 | // numbers
|
51 | isNumber(0) // true
|
52 | isNumber('0') // false
|
53 | isNumber(NaN) // false *
|
54 | isPositiveNumber(1) // true
|
55 | isNegativeNumber(-1) // true
|
56 | // * see below for special NaN use cases!
|
57 |
|
58 | // arrays
|
59 | isArray([]) // true
|
60 | isEmptyArray([]) // true
|
61 | isFullArray([1]) // true
|
62 |
|
63 | // objects
|
64 | isPlainObject({}) // true *
|
65 | isEmptyObject({}) // true
|
66 | isFullObject({ a: 1 }) // true
|
67 | // * see below for special object (& class instance) use cases!
|
68 |
|
69 | // functions
|
70 | isFunction(function () {}) // true
|
71 | isFunction(() => {}) // true
|
72 |
|
73 | // dates
|
74 | isDate(new Date()) // true
|
75 | isDate(new Date('invalid date')) // false
|
76 |
|
77 | // maps & sets
|
78 | isMap(new Map()) // true
|
79 | isSet(new Set()) // true
|
80 | isWeakMap(new WeakMap()) // true
|
81 | isWeakSet(new WeakSet()) // true
|
82 |
|
83 | // others
|
84 | isRegExp(/\s/gi) // true
|
85 | isSymbol(Symbol()) // true
|
86 | isBlob(new Blob()) // true
|
87 | isFile(new File([''], '', { type: 'text/html' })) // true
|
88 | isError(new Error('')) // true
|
89 | isPromise(new Promise((resolve) => {})) // true
|
90 |
|
91 | // primitives
|
92 | isPrimitive('') // true
|
93 | // true for any of: boolean, null, undefined, number, string, symbol
|
94 | ```
|
95 |
|
96 | ### Let's talk about NaN
|
97 |
|
98 | `isNaN` is a built-in JS Function but it really makes no sense:
|
99 |
|
100 | ```js
|
101 | // 1)
|
102 | typeof NaN === 'number' // true
|
103 | // 🤔 ("not a number" is a "number"...)
|
104 |
|
105 | // 2)
|
106 | isNaN('1') // false
|
107 | // 🤔 the string '1' is not-"not a number"... so it's a number??
|
108 |
|
109 | // 3)
|
110 | isNaN('one') // true
|
111 | // 🤔 'one' is NaN but `NaN === 'one'` is false...
|
112 | ```
|
113 |
|
114 | With is-what the way we treat NaN makes a little bit more sense:
|
115 |
|
116 | ```js
|
117 | import { isNumber, isNaNValue } from 'is-what'
|
118 |
|
119 | // 1)
|
120 | isNumber(NaN) // false!
|
121 | // let's not treat NaN as a number
|
122 |
|
123 | // 2)
|
124 | isNaNValue('1') // false
|
125 | // if it's not NaN, it's not NaN!!
|
126 |
|
127 | // 3)
|
128 | isNaNValue('one') // false
|
129 | // if it's not NaN, it's not NaN!!
|
130 |
|
131 | isNaNValue(NaN) // true
|
132 | ```
|
133 |
|
134 | ### isPlainObject vs isAnyObject
|
135 |
|
136 | 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:
|
137 |
|
138 | - `isPlainObject` will only return `true` on plain JavaScript objects and not on classes or others
|
139 | - `isAnyObject` will be more loose and return `true` on regular objects, classes, etc.
|
140 |
|
141 | ```js
|
142 | // define a plain object
|
143 | const plainObject = { hello: 'I am a good old object.' }
|
144 |
|
145 | // define a special object
|
146 | class SpecialObject {
|
147 | constructor(somethingSpecial) {
|
148 | this.speciality = somethingSpecial
|
149 | }
|
150 | }
|
151 | const specialObject = new SpecialObject('I am a special object! I am a class instance!!!')
|
152 |
|
153 | // check the plain object
|
154 | isPlainObject(plainObject) // returns true
|
155 | isAnyObject(plainObject) // returns true
|
156 | getType(plainObject) // returns 'Object'
|
157 |
|
158 | // check the special object
|
159 | isPlainObject(specialObject) // returns false !!!!!!!!!
|
160 | isAnyObject(specialObject) // returns true
|
161 | getType(specialObject) // returns 'Object'
|
162 | ```
|
163 |
|
164 | > Please note that `isPlainObject` will only return `true` for normal plain JavaScript objects.
|
165 |
|
166 | ### Getting and checking for specific types
|
167 |
|
168 | You can check for specific types with `getType` and `isType`:
|
169 |
|
170 | ```js
|
171 | import { getType, isType } from 'is-what'
|
172 |
|
173 | getType('') // returns 'String'
|
174 | // pass a Type as second param:
|
175 | isType('', String) // returns true
|
176 | ```
|
177 |
|
178 | ## TypeScript
|
179 |
|
180 | is-what makes TypeScript know the type during if statements. This means that a check returns the type of the payload for TypeScript users.
|
181 |
|
182 | ```ts
|
183 | function isNumber(payload: any): payload is number {
|
184 | // return boolean
|
185 | }
|
186 | // As you can see above, all functions return a boolean for JavaScript, but pass the payload type to TypeScript.
|
187 |
|
188 | // usage example:
|
189 | function fn(payload: string | number): number {
|
190 | if (isNumber(payload)) {
|
191 | // ↑ TypeScript already knows payload is a number here!
|
192 | return payload
|
193 | }
|
194 | return 0
|
195 | }
|
196 | ```
|
197 |
|
198 | `isPlainObject` and `isAnyObject` with TypeScript will declare the payload to be an object type with any props:
|
199 |
|
200 | ```ts
|
201 | function isPlainObject(payload: any): payload is { [key: string]: any }
|
202 | function isAnyObject(payload: any): payload is { [key: string]: any }
|
203 | // The reason to return `{[key: string]: any}` is to be able to do
|
204 | if (isPlainObject(payload) && payload.id) return payload.id
|
205 | // if isPlainObject() would return `payload is object` then it would give an error at `payload.id`
|
206 | ```
|
207 |
|
208 | ### isObjectLike
|
209 |
|
210 | If you want more control over what kind of interface/type is casted when checking for objects.
|
211 |
|
212 | To cast to a specific type while checking for `isAnyObject`, can use `isObjectLike<T>`:
|
213 |
|
214 | ```ts
|
215 | import { isObjectLike } from 'is-what'
|
216 |
|
217 | const payload = { name: 'Mesqueeb' } // current type: `{ name: string }`
|
218 |
|
219 | // Without casting:
|
220 | if (isAnyObject(payload)) {
|
221 | // in here `payload` is casted to: `Record<string | number | symbol, any>`
|
222 | // WE LOOSE THE TYPE!
|
223 | }
|
224 |
|
225 | // With casting:
|
226 | // you can pass a specific type for TS that will be casted when the function returns
|
227 | if (isObjectLike<{ name: string }>(payload)) {
|
228 | // in here `payload` is casted to: `{ name: string }`
|
229 | }
|
230 | ```
|
231 |
|
232 | Please note: this library will not actually check the shape of the object, you need to do that yourself.
|
233 |
|
234 | `isObjectLike<T>` works like this under the hood:
|
235 |
|
236 | ```ts
|
237 | function isObjectLike<T extends object>(payload: any): payload is T {
|
238 | return isAnyObject(payload)
|
239 | }
|
240 | ```
|
241 |
|
242 | ## Meet the family
|
243 |
|
244 | - [is-what 🙉](https://github.com/mesqueeb/is-what)
|
245 | - [merge-anything 🥡](https://github.com/mesqueeb/merge-anything)
|
246 | - [filter-anything ⚔️](https://github.com/mesqueeb/filter-anything)
|
247 | - [find-and-replace-anything 🎣](https://github.com/mesqueeb/find-and-replace-anything)
|
248 | - [compare-anything 🛰](https://github.com/mesqueeb/compare-anything)
|
249 | - [copy-anything 🎭](https://github.com/mesqueeb/copy-anything)
|
250 | - [flatten-anything 🏏](https://github.com/mesqueeb/flatten-anything)
|
251 |
|
252 | ## Source code
|
253 |
|
254 | It's litterally just these functions:
|
255 |
|
256 | ```js
|
257 | function getType(payload) {
|
258 | return Object.prototype.toString.call(payload).slice(8, -1)
|
259 | }
|
260 | function isUndefined(payload) {
|
261 | return getType(payload) === 'Undefined'
|
262 | }
|
263 | function isString(payload) {
|
264 | return getType(payload) === 'String'
|
265 | }
|
266 | function isAnyObject(payload) {
|
267 | return getType(payload) === 'Object'
|
268 | }
|
269 | // etc...
|
270 | ```
|
271 |
|
272 | See the full source code [here](https://github.com/mesqueeb/is-what/blob/master/src/index.ts).
|