Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is there a way to get all required properties of a typescript object

is there a way to get all required properties of a typescript interface or an object. something like Object.getOwnPropertyDescriptors(myObject) or keyof T but with the information property is required/optional

like image 373
admir86 Avatar asked Oct 25 '18 08:10

admir86


2 Answers

At runtime this is not possible, because requiredness/optionality of a property only exists in the TypeScript type system, which has been erased by the time the code actually runs. You can add your own runtime information via decorators or the like, but for that you need to modify the actual code that generates the classes and objects. So getting an array of required property names given an object or constructor is not possible.


At design time it is possible to extract the required/optional keys of a type, as a subtype of keyof T. The solution relies on conditional types and the fact that the empty object type {} is considered assignable to a weak type (a type with no required properties). Like this:

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];

And an example usage:

interface SomeType {
  required: string;
  optional?: number;
  requiredButPossiblyUndefined: boolean | undefined;
}

type SomeTypeRequiredKeys = RequiredKeys<SomeType>; 
// type SomeTypeRequiredKeys = "required" | "requiredButPossiblyUndefined" 🙂

type SomeTypeOptionalKeys = OptionalKeys<SomeType>; 
// type SomeTypeOptionalKeys = "optional" 🙂

That doesn't play nicely with types with 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 🙁

Not sure if your use case cares about indexable types or not. If so, there is a more complex solution which handles that by first extracting known literal keys and then checking for required/optional:

(EDIT: the following was updated to work around a breaking change 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;

which results in:

type SomeTypeRequiredKeys = RequiredLiteralKeys<SomeType>;
// type SomeTypeRequiredKeys = "required" | "requiredButPossiblyUndefined" 🙂

type SomeTypeOptionalKeys = OptionalLiteralKeys<SomeType>; 
// type SomeTypeOptionalKeys = "optional" 🙂

type SomeTypeIndexKeys = IndexKeys<SomeType>;
// type SomeTypeIndexKeys = string 🙂

like image 69
jcalz Avatar answered Nov 15 '22 06:11

jcalz


if anyone in 2022 is still looking for an answer, this is what worked for me:

export type GetOptionalKeys<T> = {[K in keyof T as (undefined extends T[K] ? K : never)]: T[K]}

export type GetRequiredKeys<T> = {[K in keyof T as (undefined extends T[K] ? never : K)]: T[K]}

Resource: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as

like image 6
Gabriel Avatar answered Nov 15 '22 08:11

Gabriel