24

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

admir86
  • 368
  • 1
  • 3
  • 13
  • have you tried using the metadata reflection api? – toskv Oct 25 '18 at 08:37
  • @toskv - Link? I didn't think TypeScript offered much in the way of reflection features, and JavaScript's own reflection features wouldn't answer the above. – T.J. Crowder Oct 25 '18 at 08:55
  • it does offer some stuff on decorator reflection. This explains it a bit. http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4 – toskv Oct 25 '18 at 08:56
  • I can't add the needed decorators for the metadata reflection api, the interfaces I'm using are generated by a swagger generator – admir86 Oct 25 '18 at 09:01
  • 1
    then the answer is pretty simple. It's not possible. – toskv Oct 25 '18 at 09:40

4 Answers4

34

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 

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Really helpfull and intersting concept, I use this in combination with to construct a new object with only the optional keys `type PickOptional = Pick>` –  Feb 11 '20 at 09:54
  • 2
    Why is `-?` needed? It seems to me removing the optional tag from each key does not affect the end result? What am I missing? – Xander Sep 09 '20 at 10:32
  • ...because `SomeType` in your example has no index signature. That's the intended behavior. – jcalz Feb 17 '22 at 15:32
  • Nowhere in this code do i see you returning what OP is asking - an array of all required keys. You're just declaring new types ... how is that useful? – AlxVallejo May 16 '22 at 20:19
  • @AlxVallejo The question gives two examples; one is `Object.getOwnPropertyDescriptors(myObject)`, corresponding to the runtime array you mention. This is not possible, as I say in the top paragraph. The second is `keyof T`, which is a type that exists in the TypeScript type system at design time. This is possible, and I explain how to get it in the second part of the answer. How, specifically, would you have me modify this answer? – jcalz May 16 '22 at 20:23
  • @jcalz Where in your answer are you returning an array of required keys? All i see is that you're defining a new type of required keys. So what am i suppose to do with that? – AlxVallejo May 16 '22 at 20:25
  • I'm not returning an array of required keys, because that is not possible. JavaScript doesn't have a concept of "required" keys, so there's no way to do it. This is the first half of the question and the first part of my answer. I don't know how else to explain it. The answer to the question you're asking is: it is not possible. – jcalz May 16 '22 at 20:30
  • The part of my answer that introduces the type of the required keys is the answer to the part of the question that says "like `keyof T`". That is possible and I explain how to do it. There are two parts of the question and two parts of the answer. I am just repeating myself at this point, so I'll stop. If you can articulate a concrete way that I should modify my answer, please suggest it. Otherwise I think we are at an impasse. – jcalz May 16 '22 at 20:32
10

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

  • 1
    I am indeed looking for an answer in 2022 but did the accepted answer not work for you? It works for me with TS 4.8.2 – Ran Lottem Aug 29 '22 at 19:49
  • 1
    yes, it works but in a different way, lets take required keys for example, the accepted solution returns a union of required keys, while my solution returns an object type where optional keys are removed and required keys are kept. also, the accepted answer might break the @typescript-eslint/ban-types rule. – Gabriel Pureliani Sep 01 '22 at 07:37
0

Here is my solution:

type _OptionalKeys<A extends object, B extends object> = {
  [K in KnownKeys<A> & KnownKeys<B>]: Pick<A, K> extends Pick<B, K> ? never : K
};

/**
 * OptionalKeys grabs the keys which are optional from a type `T`.
 * For example, `{ a: string; b: string | undefined; c?: string }` => `'c'`.
 */
export type OptionalKeys<T extends object> = _OptionalKeys<
  T,
  Required<T>
>[KnownKeys<T>];

Basically, we're checking for OptionalKeys so that we can then extract the RequiredKeys.

/**
 * RequiredKeys grabs the keys which are required from a type `T`.
 * For example, `{ a: string; b: string | undefined; c?: string }` => `'b' | 'c'`.
 */
export type RequiredKeys<T extends object> = Exclude<
  KnownKeys<T>,
  OptionalKeys<T>
>;

And, what is KnownKeys?

/**
 * Extracts the keys of a union type
 */
// tslint:disable-next-line:no-any
export type KeysOfUnion<T> = T extends any ? keyof T : never;

/**
 * Extracts the known keys from an object – regardless of whether it has an
 * index signature.
 */
export type KnownKeys<T extends object> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U }
  ? {} extends U
    ? never
    : U
  : never;

Of course, you could invert this approach, so you'd find RequiredKeys first, and then find OptionalKeys by changing around the Pick<A, K> extends Pick<B, K> to Pick<B, K> extends Pick<A, K> in _OptionalKeys making it _RequiredKeys.

So this solution (I created it for my rbx package) works on union types as well as singleton types.

Devin
  • 2,113
  • 2
  • 22
  • 28
0

This worked for me

type RequiredProps<T> = {
    [K in keyof T as string extends K ? never : number extends K ? never : {} extends Pick<T, K> ? never : K]: T[K]
  }

interface User {
    id: string;
    name: string;
    age?: string;
    height?: number;
    interest: string[];
  }

type A = RequiredProps<User>
ASM
  • 169
  • 3
  • 12