Transforming Typescript types to io-ts runtime validators with ts-to-io
During the last few years I’ve witnessed a major increase in the adoption of TypeScript in both frontend and server-side projects. I’m a big fan of static types for the web (and elsewhere) and also prefer TS for all my greenfield projects and also believe adding types to existing projects whenever possible.
One of the pain points in full-stack or server-side TypeScript is the validation of incoming data. While the rest of the project is neatly typed and runtime type errors are history, it is easy to mess up the external data boundary and bring in some unexpected type errors while simultaneously having a false sense of security thanks to the types used elsewhere.
The best solution I’ve found for this is io-ts, a library for decoding untyped values at runtime. It has great TS support which means that once a value has been validated it is easy to treat it as a typed value of the correct type.
After using io-ts for some time, I’ve found creating new validators to be a repetitive manual task. I then assumed that someone out there would have already written a library for automatically generating validators from TS type or interface declarations. When I found out that this library did not exist I decided to build one!
A few evenings of messing with the TypeScript compiler API later, I’ve released ts-to-io. It provides a module and script API for the task. The easiest way to use it is to generate the validator codecs from a file as a one-off script.
npx ts-to-io file.ts
For example running the following file through ts-to-io
export interface Data {
name: string;
value: number;
}
export interface MoreData extends Data {
config?: {
id: number;
comment: string | number;
flag: boolean;
};
}
export type T = "foo" | "bar";
results in the following source file
import * as t from "io-ts";
const Data = t.type({ name: t.string, value: t.number });
const MoreData = t.intersection([
t.type({ name: t.string, value: t.number }),
t.partial({
config: t.type({
id: t.number,
comment: t.union([t.string, t.number]),
flag: t.boolean
})
})
]);
const T = t.keyof({ foo: null, bar: null });
I hope other people find the library useful too. It was fun and surprisingly easy to implement though there are still a few rough edges. Being a very fresh project, I’m sure that there are some bugs hiding out there so check the generated validators and let me know if there’s something I’ve missed. You can find ts-to-io in GitHub and NPM.