How To Check for Optional Properties in TypeScript
In TypeScript, a property is considered optional if it can be omitted from an object, meaning it can be either undefined
or not provided at all. Optional properties are denoted using the ?
suffix on the property key. Determining whether a property is optional or explicitly defined with undefined
as its type can be quite tricky.
Let's consider the following example with five possible combinations:
type Example = { required: number; optional?: number; requiredAsUndefined: undefined; requiredWithUndefined: number | undefined; optionalWithUndefined?: number | undefined; }
The last four properties are allowed to be undefined
, but only the second and fifth are actually optional. Interestingly, the properties optional
, requiredWithUndefined
, and optionalWithUndefined
all resolve to the same union type number | undefined
.
So, what we want is a type that returns true
for optional
and optionalWithUndefined
, and false
for the rest. Here's how such a utility type can look:
type IsOptional<T, K extends keyof T> = undefined extends T[K] ? ({} extends Pick<T, K> ? true : false) : false; type Required = IsOptional<Example, 'required'>; // false type Optional = IsOptional<Example, 'optional'>; // true type RequiredAsUndefined = IsOptional<Example, 'requiredAsUndefined'>; // false type RequiredWithUndefined = IsOptional<Example, 'requiredWithUndefined'>; // false type OptionalWithUndefined = IsOptional<Example, 'optionalWithUndefined'>; // true
There are two constraints in this utility type. The first constraint, undefined extends T[K]
, checks if undefined
can be a part of the type accessed by T[K]
. It essentially asks whether the type T[K]
can include undefined
. The second constraint, {} extends Pick<T, K> ? true : false
, ensures that the type {}
(an empty object) is assignable to a type where the property K
is picked, implying the property is optional.
From this utility type, we can build a new mapped type which only picks optional properties. The non-optional properties will be set to never
:
type OptionalProperties<T> = { [K in keyof T]: IsOptional<T, K> extends true ? T[K] : never } type OnlyOptionals = OptionalProperties<Example>; // type OnlyOptionals = { // required: never; // optional?: number; // requiredAsUndefined: never; // requiredWithUndefined: never; // optionalWithUndefined?: number | undefined; // }
Having the properties with type never
is usually enough for type safety, but in case we truly want to omit these properties for stylistic purposes, we can move the IsOptional<T, K> extends true
constraint into the square brackets. This is a nice little trick because it will set the key of non-optional properties to never
, which will then get omitted by TypeScript automatically.
type OnlyOptionals<T> = { [K in keyof T as IsOptional<T, K> extends true ? K : never]: T[K] } type OnlyOptionals = OnlyOptionals<Example>; // type OnlyOptionals = { // optional?: number; // optionalWithUndefined?: number | undefined; // }
Here's the Playground to try it out directly in the browser: TypeScript Playground