Guides/ TypeScript / At the boundaries
Series · TypeScript at the boundaries
Part 1 of 4
●○○○

Parsing, not casting: trusting data at the boundary

A type assertion is a promise you make to the compiler and break at runtime. Parsing is how you keep it.

8 min read TypeScript

Every bug that reaches production through a type system does so the same way: someone told the compiler a value had a shape it didn’t actually have. as User. <string>x. json!. The annotation is a claim about runtime reality — and the compiler believes you completely, because believing you is its entire job.

The fix isn’t more annotations. It’s drawing a line around the edge of your program where untrusted data becomes trusted, and never asserting across it — parsing instead.

Casting is a lie the compiler can’t check

A cast changes what the compiler thinks without changing what exists:

cast.ts
const user = JSON.parse(body) as { id: number; name: string };
// user.id is "typed" number — but it's whatever the network sent.
// If the client sent { id: "42" }, every downstream `user.id * 2` is wrong.

The annotation propagates a falsehood through the rest of the file. Nothing fails here; it fails three functions away, with a stack trace that points at the symptom, never the lie.

Parsing returns proof

A parser is just a function unknown -> T that throws (or returns an error) when the value doesn’t match. Its return type is earned, not asserted. Run it:

parse.js Node

Past the parseUser call, user genuinely is the type — because anything that wasn’t threw before it got here.

Rule

Assertions move type errors to runtime and hide them. Parsers move them to the boundary and surface them. Same information, opposite ergonomics.

Where to draw the line

Put a parser at every place untrusted data enters: HTTP bodies, query strings, env vars, message-queue payloads, files on disk. Inside the line you can trust your types completely. Outside it, you trust nothing. Next in the series: applying exactly this to environment variables.