How I implement a TypeScript type-guard on an object-like type.
It happens often I need to implement a TypeScript type-guard that consumes an object-like type, but did not found clear examples in official documentation or articles. This is the solution I found.
Suppose you have a MyType
type with fields foo
, bar
, quz
.
We want an isMyType
type guard, it can be a standalone function or also a static class method if it makes sense.
Check that the argument is an object with attributes.
if (typeof value !== 'object' || value === null) return false;
Then it is possible to destructure the value as it were a Partial
of our type.
const { foo, bar, quz } = value as Partial<MyType>;
Now you can check every single field. Some field could be a nested object, for instance here quz: AnotherType
.
Here it is the sample code.
import type { AnotherType, isAnotherType } from "./AnotherType";
export type MyType = {
foo: string;
bar: boolean;
quz: AnotherType;
}
export function isMyType(value: unknown): value is MyType {
if (typeof value !== "object" || value === null) return false;
const { foo, bar, quz } = value as Partial<MyType>;
return (
typeof foo === "string" &&
typeof bar === "boolean" &&
isAnotherType(bar)
);
}
You can also use a more accurate alternative to built-in `Partial`. The cons is that you need to define a generic `isMaybeObject` type guard as well as a `objectTypeGuard` helper and import it in `MyType` implementation. The pros is that it correctly type our type attributes as `unknown` and it reduces boilerplate.
This is the implementation.
/**
* Use `isMaybeObject` in a *type guard*.
*
* @example
* ```ts
* type Foo = { bar: boolean };
*
* const isFoo = (arg: unknown): arg is Foo => {
* if (isMaybeObject<Foo>(arg)) return false;
* const { bar } = arg;
* return typeof bar === "boolean";
* }
* ```
*/
export const isMaybeObject = <T extends object>(
arg: unknown
): arg is {
[K in keyof T]: unknown;
} => typeof arg === "object" && arg !== null && !Array.isArray(arg);
/**
* Use `objectTypeGuard` as a *type guard* helper to reduce boilerplate.
*
* @example
* ```ts
* type Foo = { bar: boolean };
*
* const isFoo = objectTypeGuard<Foo>(({ bar }) => typeof bar === "boolean");
* ```
*/
export const objectTypeGuard =
<T extends object>(check: (obj: { [K in keyof T]: unknown }) => boolean) =>
(arg: unknown): arg is T =>
isMaybeObject<T>(arg) && check(arg);