|
21 | 21 |
|
22 | 22 | <!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
23 | 23 |
|
| 24 | +# Basic usage |
| 25 | + |
| 26 | +```ts |
| 27 | +import * as t from 'io-ts' |
| 28 | + |
| 29 | +const User = t.type({ |
| 30 | + userId: t.number, |
| 31 | + name: t.string |
| 32 | +}) |
| 33 | +``` |
| 34 | + |
| 35 | +This is equivalent to defining something like: |
| 36 | + |
| 37 | +```ts |
| 38 | +type User = { |
| 39 | + userId: number |
| 40 | + name: string |
| 41 | +} |
| 42 | +``` |
| 43 | +
|
| 44 | +The advantage of using `io-ts` to define the runtime type is that we can validate the type at runtime, and we can also extract the corresponding static type, so we don’t have to define it twice. |
| 45 | +
|
| 46 | +You can use this runtime type to validate or decode untrusted data: |
| 47 | +
|
| 48 | +```ts |
| 49 | +import * as t from "io-ts"; |
| 50 | +import { PathReporter } from "io-ts/PathReporter"; |
| 51 | +import { isLeft } from "fp-ts/Either"; |
| 52 | + |
| 53 | +const User = t.type({ |
| 54 | + userId: t.number, |
| 55 | + name: t.string, |
| 56 | +}); |
| 57 | + |
| 58 | +const data: unknown = { userId: 123, name: "foo" }; // data that looks like User but from an unknown source |
| 59 | + |
| 60 | +const decoded = User.decode(data); // Either<Errors, User> |
| 61 | +if (isLeft(decoded)) { |
| 62 | + throw Error( |
| 63 | + `Could not validate data: ${PathReporter.report(decoded).join("\n")}` |
| 64 | + ); |
| 65 | + // e.g.: Could not validate data: Invalid value "foo" supplied to : { userId: number, name: string }/userId: number |
| 66 | +} |
| 67 | + |
| 68 | +type UserT = t.TypeOf<typeof User>; // compile-time type |
| 69 | +const decodedUser: UserT = decoded.right; // now safely the correct type |
| 70 | + |
| 71 | +console.log("decoded user id:", decodedUser.userId); |
| 72 | +``` |
| 73 | + |
| 74 | +io-ts also supports more complex encoding/decoding (serialization/deserialization) scenarios where the output type of `decode()` is not necessarily the same as the input type. This allows you to, for example, encode a `Date` object as a string and decode it back into a `Date` object (see [Custom types](#custom-types)). |
| 75 | + |
| 76 | +# Implemented types / combinators |
| 77 | + |
| 78 | +| Type | TypeScript | codec / combinator | |
| 79 | +| --------------------------- | --------------------------- | -------------------------------------------------------------------- | |
| 80 | +| null | `null` | `t.null` or `t.nullType` | |
| 81 | +| undefined | `undefined` | `t.undefined` | |
| 82 | +| void | `void` | `t.void` or `t.voidType` | |
| 83 | +| string | `string` | `t.string` | |
| 84 | +| number | `number` | `t.number` | |
| 85 | +| boolean | `boolean` | `t.boolean` | |
| 86 | +| unknown | `unknown` | `t.unknown` | |
| 87 | +| array of unknown | `Array<unknown>` | `t.UnknownArray` | |
| 88 | +| array of type | `Array<A>` | `t.array(A)` | |
| 89 | +| record of unknown | `Record<string, unknown>` | `t.UnknownRecord` | |
| 90 | +| record of type | `Record<K, A>` | `t.record(K, A)` | |
| 91 | +| function | `Function` | `t.Function` | |
| 92 | +| literal | `'s'` | `t.literal('s')` | |
| 93 | +| partial | `Partial<{ name: string }>` | `t.partial({ name: t.string })` | |
| 94 | +| readonly | `Readonly<A>` | `t.readonly(A)` | |
| 95 | +| readonly array | `ReadonlyArray<A>` | `t.readonlyArray(A)` | |
| 96 | +| type alias | `type T = { name: A }` | `t.type({ name: A })` | |
| 97 | +| tuple | `[ A, B ]` | `t.tuple([ A, B ])` | |
| 98 | +| union | `A \| B` | `t.union([ A, B ])` | |
| 99 | +| intersection | `A & B` | `t.intersection([ A, B ])` | |
| 100 | +| keyof | `keyof M` | `t.keyof(M)` (**only supports string keys**) | |
| 101 | +| recursive types | | `t.recursion(name, definition)` | |
| 102 | +| branded types / refinements | ✘ | `t.brand(A, predicate, brand)` | |
| 103 | +| integer | ✘ | `t.Int` (built-in branded codec) | |
| 104 | +| exact types | ✘ | `t.exact(type)` (no unknown extra properties) | |
| 105 | +| strict | ✘ | `t.strict({ name: A })` (an alias of `t.exact(t.type({ name: A })))` | |
| 106 | + |
| 107 | + |
| 108 | + |
24 | 109 | # The idea
|
25 | 110 |
|
26 | 111 | A value of type `Type<A, O, I>` (called "codec") is the runtime representation of the static type `A`.
|
@@ -116,27 +201,7 @@ pipe(t.string.decode(null), fold(onLeft, onRight))
|
116 | 201 | // => "1 error(s) found"
|
117 | 202 | ```
|
118 | 203 |
|
119 |
| -We can combine these codecs through [combinators](#implemented-types--combinators) to build composite types which represent entities like domain models, request payloads etc. in our applications: |
120 |
| - |
121 |
| -```ts |
122 |
| -import * as t from 'io-ts' |
123 |
| - |
124 |
| -const User = t.type({ |
125 |
| - userId: t.number, |
126 |
| - name: t.string |
127 |
| -}) |
128 |
| -``` |
129 |
| - |
130 |
| -So this is equivalent to defining something like: |
131 |
| - |
132 |
| -```ts |
133 |
| -type User = { |
134 |
| - userId: number |
135 |
| - name: string |
136 |
| -} |
137 |
| -``` |
138 |
| -
|
139 |
| -The advantage of using `io-ts` to define the runtime type is that we can validate the type at runtime, and we can also extract the corresponding static type, so we don’t have to define it twice. |
| 204 | +We can combine these codecs through [combinators](#implemented-types--combinators) to build composite types which represent entities like domain models, request payloads etc. in our applications. |
140 | 205 |
|
141 | 206 | # TypeScript integration
|
142 | 207 |
|
@@ -266,37 +331,6 @@ console.log(PathReporter.report(NumberFromString.decode('a')))
|
266 | 331 |
|
267 | 332 | You can also use the [`withMessage`](https://gcanti.github.io/io-ts-types/modules/withMessage.ts.html) helper from [io-ts-types](https://github.com/gcanti/io-ts-types)
|
268 | 333 |
|
269 |
| -# Implemented types / combinators |
270 |
| -
|
271 |
| -| Type | TypeScript | codec / combinator | |
272 |
| -| --------------------------- | --------------------------- | -------------------------------------------------------------------- | |
273 |
| -| null | `null` | `t.null` or `t.nullType` | |
274 |
| -| undefined | `undefined` | `t.undefined` | |
275 |
| -| void | `void` | `t.void` or `t.voidType` | |
276 |
| -| string | `string` | `t.string` | |
277 |
| -| number | `number` | `t.number` | |
278 |
| -| boolean | `boolean` | `t.boolean` | |
279 |
| -| unknown | `unknown` | `t.unknown` | |
280 |
| -| array of unknown | `Array<unknown>` | `t.UnknownArray` | |
281 |
| -| array of type | `Array<A>` | `t.array(A)` | |
282 |
| -| record of unknown | `Record<string, unknown>` | `t.UnknownRecord` | |
283 |
| -| record of type | `Record<K, A>` | `t.record(K, A)` | |
284 |
| -| function | `Function` | `t.Function` | |
285 |
| -| literal | `'s'` | `t.literal('s')` | |
286 |
| -| partial | `Partial<{ name: string }>` | `t.partial({ name: t.string })` | |
287 |
| -| readonly | `Readonly<A>` | `t.readonly(A)` | |
288 |
| -| readonly array | `ReadonlyArray<A>` | `t.readonlyArray(A)` | |
289 |
| -| type alias | `type T = { name: A }` | `t.type({ name: A })` | |
290 |
| -| tuple | `[ A, B ]` | `t.tuple([ A, B ])` | |
291 |
| -| union | `A \| B` | `t.union([ A, B ])` | |
292 |
| -| intersection | `A & B` | `t.intersection([ A, B ])` | |
293 |
| -| keyof | `keyof M` | `t.keyof(M)` (**only supports string keys**) | |
294 |
| -| recursive types | | `t.recursion(name, definition)` | |
295 |
| -| branded types / refinements | ✘ | `t.brand(A, predicate, brand)` | |
296 |
| -| integer | ✘ | `t.Int` (built-in branded codec) | |
297 |
| -| exact types | ✘ | `t.exact(type)` | |
298 |
| -| strict | ✘ | `t.strict({ name: A })` (an alias of `t.exact(t.type({ name: A })))` | |
299 |
| -
|
300 | 334 | # Recursive types
|
301 | 335 |
|
302 | 336 | Recursive types can't be inferred by TypeScript so you must provide the static type as a hint
|
|
0 commit comments