Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

get `keyof` non-optional property names in typescript

Tags:

typescript

This is my interface

interface X {
    key: string
    value: number | undefined
    default?: number
}

But I want the non-optional keys only, aka. "key" | "value", or just "key" (both will do fine for me)

type KeyOfX = keyof X gives me "key" | "value" | "default".

type NonOptionalX = {
    [P in keyof X]-?: X[P]
}

type NonOptionalKeyOfX = keyof NonOptionalX gives "key" | "value" | "default" as -? only removes the optional modifier and make all of them non-optional.

ps. I use Typescript 2.9.

like image 276
foresightyj Avatar asked Jun 13 '18 05:06

foresightyj


2 Answers

You can use conditional type operator of the form undefined extends k ? never : k to substitute never for keys of values that undefined could be assigned to, and then use the fact that union T | never is just T for any type:

interface X {
    key: string
    value: number | undefined
    default?: number
}


type NonOptionalKeys<T> = { [k in keyof T]-?: undefined extends T[k] ? never : k }[keyof T];

type Z = NonOptionalKeys<X>; // just 'key'

Also this comment might be relevant: https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458

like image 69
artem Avatar answered Oct 13 '22 01:10

artem


But I want the non-optional keys only, aka. "key" | "value", or just "key" (both will do fine for me)

While easier option of "key" is covered, I'd like to cover a less obvious option of "key" | "value". It might be needed because value is not an optional field. It can be assigned undefined, but it should pass a "value" in x check.

type NonOptionalKeys<T> = { [K in keyof T]-?: T extends { [K1 in K]: any } ? K : never}[keyof T]

How it works:

  • keys in mapped object types (K) are actually not only types, but types with "requiredness" and "readonlyness" attached;
  • iteration over keys of T with -? makes optional keys required before putting them into K;
  • { [K1 in K]: any } object is an object with single required key
  • if original type extends it, that key was required, we keep its name as a type of values for that key;
  • optional keys don't pass the check and get collected as never;
  • the resulting object contains names of the keys of original types as fields and either same key or never as value;
  • with [keyof T] a union of all the value types gets computed, never types get removed ((A | never) = A), and only a union of all required keys is left.

Update: readonly is way trickier. Read here or here. You can completely disassemble an object into a tuple like this.

like image 33
polkovnikov.ph Avatar answered Oct 12 '22 23:10

polkovnikov.ph