Write a type predicate that narrows an unknown value to a User
A handler receives a parsed JSON payload typed as unknown. Implement isUser so that inside an if (isUser(value)) branch the compiler narrows value to User.
Constraints: no as assertion may leak out of the function, the check must actually inspect the value at runtime, and it must reject null (whose typeof is object).
interface User { id: string; email: string; }
function isUser(value: unknown): value is User {
// your code here
}
Write the implementation.
A user-defined type guard is a function whose return type is value is User. When it returns true the compiler narrows the argument to User in that branch. The body must genuinely validate the shape at runtime — the predicate is trusted, never verified.
- ✗Forgetting
typeof null === 'object', so anullpayload slips through the guard - ✗Believing the compiler checks the predicate body against the declared shape
- ✗Returning
booleaninstead ofvalue is User, which gives no narrowing at all
- →What happens to the narrowing if the predicate body checks the wrong field?
- →How would a schema validator replace this hand-written guard?
Solution
A type guard is an ordinary function; the work is done by the value is User return type. The body has to validate the shape at runtime, because the compiler will not do it for you.
interface User { id: string; email: string; }
function isUser(value: unknown): value is User {
if (typeof value !== 'object' || value === null) return false; // typeof null === 'object'
const candidate = value as Record<string, unknown>; // the as never leaves the function
return typeof candidate.id === 'string' && typeof candidate.email === 'string';
}
function greet(payload: unknown): string {
if (isUser(payload)) {
return payload.email.toLowerCase(); // payload is narrowed to User
}
return 'anonymous'; // payload is unknown again here
}
⚠️ The predicate is a runtime function that the compiler trusts. Write return true;, or check the wrong field, and TypeScript still narrows the value to User — the crash lands later, in somebody else's code. That is unsoundness by design: the narrowing is exactly as truthful as the body you wrote.