During runtime, it is impossible to determine the required or optional properties of an object in TypeScript as this information is erased by the time the code executes. Although you can include your own runtime details using decorators, modifying the actual code that generates classes and objects is necessary. Therefore, retrieving a list of required property names from an object or constructor at runtime is unattainable.
However, at design time, it is feasible to extract the mandatory and optional keys of a type as a subtype of keyof T
. This approach utilizes conditional types and leverages the fact that an empty object type {}
is assignable to a weak type (a type with no obligatory properties). For example:
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
Here is an illustration of how to use it:
interface SomeType {
required: string;
optional?: number;
requiredButPossiblyUndefined: boolean | undefined;
}
type SomeTypeRequiredKeys = RequiredKeys<SomeType>;
// type SomeTypeRequiredKeys = "required" | "requiredButPossiblyUndefined" 🙂
type SomeTypeOptionalKeys = OptionalKeys<SomeType>;
// type SomeTypeOptionalKeys = "optional" 🙂
Note that this method may not work effectively with types containing index signatures:
interface SomeType {
required: string;
optional?: number;
requiredButPossiblyUndefined: boolean | undefined;
[k: string]: unknown; // index signature
}
type SomeTypeRequiredKeys = RequiredKeys<SomeType>;
// type SomeTypeRequiredKeys = never 🙁
type SomeTypeOptionalKeys = OptionalKeys<SomeType>;
// type SomeTypeOptionalKeys = string 🙁
If you require handling indexable types, a more intricate solution involves extracting known literal keys first and then identifying the required and optional properties:
(EDIT: The following was updated to address changes in TS4.3, see ms/TS#44143)
type RequiredLiteralKeys<T> = keyof { [K in keyof T as string extends K ? never : number extends K ? never :
{} extends Pick<T, K> ? never : K]: 0 }
type OptionalLiteralKeys<T> = keyof { [K in keyof T as string extends K ? never : number extends K ? never :
{} extends Pick<T, K> ? K : never]: 0 }
type IndexKeys<T> = string extends keyof T ? string : number extends keyof T ? number : never;
This results in:
type SomeTypeRequiredKeys = RequiredLiteralKeys<SomeType>;
// type SomeTypeRequiredKeys = "required" | "requiredButPossiblyUndefined" 🙂
type SomeTypeOptionalKeys = OptionalLiteralKeys<SomeType>;
// type SomeTypeOptionalKeys = "optional" 🙂
type SomeTypeIndexKeys = IndexKeys<SomeType>;
// type SomeTypeIndexKeys = string 🙂