58

I want to get all available keys of an union type.

interface Foo {
  foo: string;
}

interface Bar {
   bar: string;
}

type Batz = Foo | Bar;

type AvailableKeys = keyof Batz;

I want to have 'foo' | 'bar' as result of AvailableKeys but it is never (as alternative I could do keyof (Foo & Bar), what produces exact the required type but I want to avoid to repeat the Types).

I found already the issue keyof union type should produce union of keys at github. I understand the answer, that keyof UnionType should not produce all possible keys.

So my question is: Is there an other way to get the list of all possible keys (it is ok if the verison 2.8 of tsc is required)?

Xenya
  • 1,255
  • 2
  • 11
  • 13

2 Answers2

122

This can be done in typescript 2.8 and later using conditional types. Conditional types iterate over types in a union, union-ing the result:

type Batz = Foo | Bar;

type KeysOfUnion<T> = T extends T ? keyof T: never;
// AvailableKeys will basically be keyof Foo | keyof Bar 
// so it will be  "foo" | "bar"
type AvailableKeys = KeysOfUnion<Batz>; 

The reason a simple keyof Union does not work is because keyof always returns the accessible keys of a type, in the case of a union that will only be the common keys. The conditional type in KeysOfUnion will actually take each member of the union and get its keys so the result will be a union of keyof applied to each member in the union.

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • this is awesome - can you explain the difference between KeysOfUnion type and a simple `keyof T` type? the former returns the correct union of the keys, the latter an intersection (which is not useful) – dmwong2268 Aug 18 '18 at 16:37
  • 2
    @dmwong2268 added a bit more of an explanation let me know if it's clear – Titian Cernicova-Dragomir Aug 18 '18 at 16:47
  • 2
    @TitianCernicova-Dragomir Your solution works, but your explanation doesn't seem to explain anything, just re-states the behavior. I found a discussion on gitter in which you're pointing to the right place where it's explained, so let me add it here: https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types – sarimarton May 04 '20 at 21:55
  • Great answer! I also found that you have to use generics, otherwise it won't work. type allBatzKeys = KeysOfUnion // "foo"|"bar" type alternativeAllBatzKeys = Batz extends any ? keyof Batz: never; // never – Bing Ren Sep 26 '20 at 04:00
  • 3
    To elaborate on the explanation a little more - this works due to the _distributive conditionals_ linked by @MártonSári above, in which conditional clauses on a union type are automatically turned into unions of conditionals. Crucially, the statement `T extends T` is ALWAYS TRUE, so this type will turn T into a union of conditionals all of which will follow the true branch. It's basically a bit of a hack, because we're not really doing anything conditional at all. – devrobf Aug 01 '22 at 11:37
0

Instead of a union type, you need an intersection type:

type Batz = Foo & Bar;

I agree that their naming can sometimes be confusing.

Behrooz
  • 2,181
  • 1
  • 16
  • 24
  • 1
    thanks for the answer but I really meant the union type. I want to get all possible keys of an union type (yes of Foo or Bar) so that I can do a exhaustive check. – Xenya Mar 21 '18 at 12:58
  • @Xenya Would you please provide a case in which `type Batz = Foo & Bar;` doesn't work for getting the keys? In the above case, `Batz` is `'foo' | 'bar'` as expected. – Behrooz Mar 21 '18 at 18:26
  • 5
    you're right, for getting the key it works. But I have already the type `type Batz = Foo | Bar;`. I want to avoid to rewrite the type with `&`. – Xenya Mar 26 '18 at 10:32