How To Check for Optional Properties in TypeScript

// Comment on DEV

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