Typescript: derive union type from array of objects
The usual approaches are:
- let TS infer the type of
pairs
by omitting the explicit typeReadonlyArray<Pair>
(see answer) - give
key
inPair
the type"foo"|"bar"
If you don't want to do this, then the only way to infer your keys and restrict the type of pairs
is to use a helper function. The Pair
type will also be made generic to save the given key
string literal types. You can use an IIFE to make the assignment compact:
type Pair<K = string> = {
key: K;
value: number;
};
const pairs = (<T>(p: readonly Pair<T>[]) => p)([
{ key: 'foo', value: 1 },
{ key: 'bar', value: 2 },
] as const) // readonly Pair<"foo" | "bar">[]
type Keys = typeof pairs[number]['key'] // "foo" | "bar"
Playground
For a variable you can either let the compiler infer the type from initialization, or write it out explicitly. If you write it explicitly, as you have, then the initialization value is checked against the annotation, but the actual type of the initializer does not affect the type of the variable (so you lose the type information you want). If you let the compiler infer it, it is no longer possible to constrain the type to conform to a specific interface (as you seem to want)
The solution for this is to use a generic function to both constrain the value and infer it's actual type:
type Pair = {
key: string;
value: number;
};
function createPairsArray<T extends readonly Pair[] & Array<{key: V}>, V extends string>(...args: T) {
return args
}
const pairs = createPairsArray(
{ key: 'foo', value: 1 },
{ key: 'bar', value: 2 },
)
type Keys1 = typeof pairs[number]['key']
type Data = {
name: string;
age: number;
};
function createDataObject<T extends Record<string, Data>>(arg: T) {
return arg;
}
const DataRecord = createDataObject({
foo: { name: 'Mark', age: 35 },
bar: { name: 'Jeff', age: 56 },
})
type Keys2 = keyof typeof DataRecord;
Playground Link
Note: For the array case we need to strong arm the compiler a bit into inferring string literal types for key
, hence the whole & Array<{key: V}>
, where V
is a type parameter extending string