'Exhaustive list of keys of type
I have some type Foo and I would like to extract the keys of Foo. For now I can do it manually like so.
type Foo = {
a?: string;
b?: string;
};
const fooKeys = ["a", "b"] as Readonly<Array<keyof Foo>>;
But then somebody could come along and add c and fooKeys is still valid despite it not containing all the keys. How can I enforce that fooKeys must be exhaustive?
type Foo = {
a?: string;
b?: string;
c: string;
};
const fooKeys = ["a", "b"] as Readonly<Array<keyof Foo>>;
My current workaround is to define an example and infer everything from that, which is okay even though the types don't line up perfectly:
const exampleFoo = {
a: 'example' as string | undefined,
b: 'example' as string | undefined,
c: 'example' as string
}
type Foo = typeof exampleFoo
const fooKeys = Object.keys(exampleFoo) as Readonly<Array<keyof Foo>>;
Maybe there is a better approach?
Solution 1:[1]
I think using an example object is ok.
This would be another option:
type Foo = {
a?: string;
b?: string;
c: string;
};
const Fields: Record<keyof Foo, 1> = { a: 1, b: 1, c: 1}
const fooKeys = Object.keys(Fields) as Readonly<Array<keyof Foo>>;
There are solutions to convert keyof Foo to a tuple type, but they tend to be unreliable as order in a union is not guaranteed to always be the same or if you generate a union with all possible combinations that will kill compiler performance.
You could also use a function to validate all keys have been specified:
type HasAllUnique<Tuple, Union, Seen = never> =
Tuple extends [infer Head, ...infer Tail]
? Head extends Seen
? { "? ERROR ?": ["The following key appears more than once in the tuple:", Head]}
: HasAllUnique<Tail, Exclude<Union, Head>, Head | Seen>
: [Union] extends [never]? unknown : { "? ERROR ?": ["Some keys are missing from the tuple:", Union]}
function allKeys<T>() {
return function<K extends Array<keyof T>>(...k : K & HasAllUnique<K, keyof T>) {
return k
}
}
allKeys<Foo>()("a") // errro
allKeys<Foo>()("a", "b") // error
allKeys<Foo>()("a", "b", "c") // ok
allKeys<Foo>()("a", "b", "c", "a") // error
You could also have a functionless approach if you are ok with defining an extra type after the constant:
const bad2= ["a", "b"] as const
type E2 = Validate<HasAllUnique<typeof bad2, keyof Foo>>
const ok = ["a", "b", "c"] as const
type E3 = Validate<HasAllUnique<typeof ok, keyof Foo>>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
