TypeScript
Narrow types at the edge of your program
Validate untrusted input once, where it enters, and let the rest of the function accept a type it can trust.
- TypeScript
- Validation
unknown is the honest type for data you did not create: a request body, a query string, a file on disk. The mistake is to cast it with as in the middle of a function and hope the shape holds.
Write a guard that returns a type
A type predicate narrows unknown to the shape you checked. Call it at the edge. The code after it can stop asking whether the field exists.
type GuideInput = {
title: string;
minutes: number;
};
function isGuideInput(value: unknown): value is GuideInput {
if (typeof value !== "object" || value === null) return false;
const record = value as Record<string, unknown>;
return (
typeof record.title === "string" &&
typeof record.minutes === "number"
);
}
export function parseGuide(value: unknown): GuideInput {
if (!isGuideInput(value)) {
throw new Error("Guide payload is missing a title or minutes.");
}
return value;
}The cast inside the guard is local, and it sits behind an object check. That is a different habit from sprinkling as GuideInput through the UI.
Trust the value after the parse
parseGuide either throws or returns GuideInput. Callers should not re-check title. If a field is optional, model that in the type and handle it inside the parser, not in every consumer.
Forms and search params
searchParams values arrive as string | string[] | undefined. Normalize them in one function that returns a small object. Components then receive page: number, not an array they each parse differently.
- 01
Accept the raw value at the boundary
The parameter type is
unknown, or the framework type you have not checked yet. - 02
Return a named type or fail
Throw an error a caller can catch, or return a result object. Do not return a half-parsed record.
- 03
Keep casts inside the parser
A cast in a view is a sign the parser stopped too early.