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
UsrandUserare mutually assignable. This means that not changes will have to be made to the rest of the application. - The resulting variable
uis correctly typed.