Intermediate · 14 min
Validating unknown data
Check untrusted fields at runtime before treating a value as a typed record.
Check the object before its fields
Treat parsed or external data as unknown. First check for a non-null object, then check each required field and its value. A type assertion only changes what the compiler believes; it does not establish that the input is valid.
function minutes(value: unknown): number {
if (typeof value !== "object" || value === null) return 0;
if (!("minutes" in value) || typeof value.minutes !== "number") return 0;
return isFinite(value.minutes) && value.minutes > 0 ? value.minutes : 0;
}
console.log(minutes({ minutes: 15 }));
console.log(minutes({ minutes: "15" }));Output
15 0
Write an honest type guard
A predicate such as "value is Booking" lets callers narrow a value after a true result. The compiler trusts this claim; the guard must actually check every required field. Here the product rules also require a nonempty topic and positive whole minutes. Extra fields are allowed. The guard does not sanitize or copy the input.
type Booking = { topic: string; minutes: number };
function isBooking(value: unknown): value is Booking {
return typeof value === "object" && value !== null
&& "topic" in value && typeof value.topic === "string" && value.topic.trim().length > 0
&& "minutes" in value && typeof value.minutes === "number"
&& isFinite(value.minutes) && value.minutes > 0 && Math.floor(value.minutes) === value.minutes;
}
console.log(isBooking({ topic: "Travel", minutes: 20 }));
console.log(isBooking({ topic: "", minutes: 20 }));Output
true false
Put it into practice
- Read the types and predict the output before running the examples.
- Fix the task without using "any" or a type assertion, then check both practice questions.
Try it yourself
Code runs on this device. When you are signed in, drafts sync to your account.
Narrow before reading a booking
Use "isBooking" to check "input" before reading its minutes. Return 0 for invalid input. Keep both calls and the existing guard.
Call the guard inside the function. Read the property only in the successful branch.
Show solution
type Booking = { topic: string; minutes: number };
function isBooking(value: unknown): value is Booking {
return typeof value === "object" && value !== null
&& "topic" in value && typeof value.topic === "string" && value.topic.trim().length > 0
&& "minutes" in value && typeof value.minutes === "number"
&& isFinite(value.minutes) && value.minutes > 0 && Math.floor(value.minutes) === value.minutes;
}
function readMinutes(input: unknown): number {
return isBooking(input) ? input.minutes : 0;
}
const total = readMinutes({ topic: "Food", minutes: 20 }) + readMinutes(null);
console.log(total);The guard checks the runtime shape and product rules before narrowing input. Invalid input takes the fallback branch instead of being asserted to be a Booking.