Reputation: 4519
Simplest example
assuming this type
type Foo = { a: number } | { b: string } | { c: boolean };
is it possible to get
type KeysOfFoo = 'a' | 'b' | 'c';
I tried this but it doesn't work
type Failed = keyof Foo; // never
Upvotes: 4
Views: 1134
Reputation: 330466
Something like keyof (A | B | C)
will result in only the keys that are definitely on an object of type A | B | C
, meaning it would have to be a key known to be in all of A
, B
, and C
, which is: keyof A & keyof B & keyof C
. That is, keyof T
is "contravariant in T
".
This isn't what you want though (in your case there are no keys in common so the intersection is never
).
If you're looking for the set of keys which are in at least one of the members of your union, you need to distribute the keyof
operator over the union members. Luckily there is a way to do this via distributive conditional types. It looks like this:
type AllKeys<T> = T extends any ? keyof T : never;
The T extends any
doesn't do much in terms of type checking, but it does signal to the compiler that operations on T
should happen for each union member of T
separately and then the results would be united back together into a union. That means AllKeys<A | B | C>
will be treated like AllKeys<A> | AllKeys<B> | AllKeys<C>
. Let's try it:
type KeysOfFoo = AllKeys<Foo>;
// type KeysOfFoo = "a" | "b" | "c"
Looks good! Please note that you should be careful about actually using KeysOfFoo
in concert with objects of type Foo
. keyof
is contravariant for a reason:
function hmm(foo: Foo, k: AllKeys<Foo>) {
foo[k]; // error!
// "a" | "b" | "c"' can't be used to index type 'Foo'.
// Property 'a' does not exist on type 'Foo'
}
It's not safe to index into foo
with k
for the same reason you can't safely index into a value of type {a: number}
with "b"
... the key might not exist on the object. Obviously you know your use cases better than I do though, so you may well have some legitimate use of AllKeys<Foo>
and Foo
together. I'm just saying to be careful.
Upvotes: 5
Reputation: 11047
Failed
is of type never
because your Foo
type can't have any keys. It's currently set up as an intersection type between 3 completely exclusive types, so there are no valid keys.
If you change from using |
to a &
, then it'll work as is.
type Foo = { a: number } & { b: string } & { c: boolean }
type a = keyof Foo // 'a' | 'b' | 'c'
Upvotes: 0