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 and User 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.