Type safe programming is often broken on the edge to the real world. Typescript
does not come with any batteries to mitigate this. A cleverly designed library
called io-ts
fixes this problem. This is
how I use it:
A generic parser function:
import { pipe } from "fp-ts/lib/function"import { fold } from 'fp-ts/lib/Either'import { Decoder, Errors } from "io-ts"export const parse = <A>(codec: Decoder<any, A>, value: any): A => { // failure handler const onLeft = (errors: Errors) => { throw new Error(`Could not parse ${JSON.stringify(errors)}`) } return pipe(codec.decode(value), fold(onLeft, v => v))}
A quick example of rewriting typescript types into their equivalent io-ts
definitions goes as follows:
Before:
type User = { name: string uid: string description?: string | undefined metadata: { [m: string]: string } gender: "male" | "female" | "other"}
After:
const User = intersection([ type({ name: string, uid: string, metadata: record(string, string), gender: union([literal("male"), literal("female"), literal("other")]) }), partial({ description: string })])type User = TypeOf<typeof User>
And lastly we do type safe decoding as follows:
try { const u = parse(User, ...) ...} catch (e) { console.error("Could not parse")}
Couple of notes:
- We overload the name such that it can both be used as a type and as a decoder
- The types of
Usr
andUser
are mutually assignable. This means that not changes will have to be made to the rest of the application. - The resulting variable
u
is correctly typed.