Adventures in JS type checking

In the last post I made a config file for this blog project that conformed to the Blog interface described in a previous post. The goal was to have the app load this file and validate it. I got stuck almost immediately.

TypeScript interfaces aren't actually that useful for data validation. That's because TS types are only used when you're developing the application. They're not used when you run the application. In fact, they're removed from the final codebase during compilation.

This feels counterintuitive. If you write a function that takes an argument of type Foo, and then elsewhere write some code that calls that function with a value of type Bar, the compiler (and maybe your IDE) will yell at you. But if your application loads some data from a file or Ajax request and passes it to that function, it's too late to check if that data conforms to the Foo type definition.

That's basically what this blog application is going to do: load a lot of JSON, YAML and Markdown text and parse it into HTML files at runtime. This TypeScript caveat means that the app can't tell if a blog post is missing a required field, like a title, unless I write code that checks for it and every other required field. So what's the use of having types if we can't use them for data validation when it counts?

OSS to the rescue. The ts-interface-checker library generates data validators from TypeScript type definitions. These validators (called "checkers") can be used at runtime, so we can take an data object, like a blog post, and check that it conforms to the Post type, even though the type itself was compiled out of the codebase.

I added a build step that regenerates the checkers automatically so they stay in sync with the type definitions, then wrote a wrapper function that can check any object type:

// src/checkType.ts

import exportedTypeSuite from '../types/index-ti';
import { createCheckers } from "ts-interface-checker";

const TypeChecker = createCheckers(exportedTypeSuite);

export function checkType(object: any, type: string) {
    if (!TypeChecker[type]) {
        throw new Error(`checkType: type ${type} is undefined.`);
    }
    try {
        TypeChecker[type].check(object);
    } catch (err) {
        console.error(`checkType: object does not validate as type ${type}.`);
        console.error(err);
    }
}

export default checkType;

Now I can load the blog config JSON file and validate it at runtime!

// index.js (the main app for now)

import { checkType } from './src/checkType';

const CONFIG_PATH = '../blog_config.json';
const config = require(CONFIG_PATH);

checkType(config, 'Blog');

console.log('Blog config loaded and validated.');

Before this, my app was happily loading the blog config as an object of type Blog, even if when I removed all of the fields. Now it can tell me exactly what fields are missing or have incorrect types. Perfect.


Posted in: blogging, code, javascript, typescript, node

Previously: Baby step: okay let's write some config stuff

Next: Some admin notes