38

If I have the following type

interface Foo {
    bar: string;
    baz: number;
    qux: string;
}

can I use typeof to type a parameter such that it only takes keys of Foo that return string ('bar' or 'qux')?

Levi Botelho
  • 24,626
  • 5
  • 61
  • 96

1 Answers1

73

Typescript 4.1 and above

With the addition of the as clause in mapped types we can now simplify the original type to:

type KeyOfType<T, V> = keyof {
    [P in keyof T as T[P] extends V? P: never]: any
}

Playground Link

Original answer

You can use conditional types in Tyepscript 2.8 :

type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp? P : never }[keyof T];

let onlyStrings: KeysOfType<Foo, string>;
onlyStrings = 'baz' // error
onlyStrings = 'bar' // ok

let onlyNumbers: KeysOfType<Foo, number>;
onlyNumbers = 'baz' // ok
onlyNumbers = 'bar' // error 

Playground Link

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 2
    Thanks for this answer. in my using it, I found I needed to extend `KeysOfType` to include `keyof T & { [P ....` or else I couldn't use a `KeysOfType` to then subscript `T`. – Rob Napier Mar 05 '20 at 21:03
  • How to make it to respect nullable column like `number | null` filtering properties which have `number` type? – Vadim Ovchinnikov May 04 '22 at 11:44
  • [Link](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types) to docs of 'as' clause. – user764754 Sep 07 '22 at 14:06
  • Thank you! This was exactly what I was looking for - now I was able to write: `type TypeSafeUnion = { [P in keyof (T1 | T2) as T1[P] extends T2[P] ? T2[P] extends T1[P] ? P : never : never]: (T1[P] | T2[P]); };` – AnorZaken Oct 25 '22 at 04:04
  • With it I can get *only* the compatible keys of two type as simply `keyof TypeSafeUnion`. Meaning I can write a Pick type that lets you pick only compatible properties that exists in _both_ of two types, meaning I can write type safe patch methods between dto/model objects that have no type relation between them. :) – AnorZaken Oct 25 '22 at 04:07
  • (Technically you could do this before too, but the compile error if you picked a mutual but non-type-matched key would be on one of the objects instead of on the key, which has subpar clarity.) – AnorZaken Oct 25 '22 at 04:20
  • 1
    @VadimOvchinnikov, I was wondering the same... it seems we can do something like ``` type KeyOfTypeOptionalIncluded = KeyOfType``` for optional keys or even ``` type KeyOfTypeNullIncluded = KeyOfType``` for the example you mentioned.... or even ``` type KeyOfTypeNullIablencluded = KeyOfType``` for optional nullable types – awdk Nov 01 '22 at 05:25
  • @Titian Cernicova-Dragomir, I think you should update your answer enhancing the `KeyOfType` so that it works with functions as @Robby Cornelissen answered my question at https://stackoverflow.com/questions/74272393/how-to-distinguish-different-functions-signature-with-conditional-type-checks – awdk Nov 01 '22 at 23:26
  • Doesn't work for generic types - indexing the object with keys like these does not result in a type compatible with `number`. – riv Aug 13 '23 at 19:42