Is there a way to infer the name value from an array of objects and have the new object use those values as keys for the output type?
interface TypeA {
name: string;
value: number;
}
interface TypeB {
[key: string]: { value: any };
}
// can this be created without hard-coding a new type containing the values?
interface OutputType {
test: {value: any},
test2: {value: any},
}
const arrayOfObjectsToObject = (array: TypeA[]):OutputType =>
array.reduce((acc: TypeB, { name, value }: TypeA) => {
acc[name] = { value };
return acc;
}, {});
const result = arrayOfObjectsToObject([ // {test:{value:1}, test2:{value:2}} etc...
{ name: 'test', value: 1 },
{ name: 'test2', value: 2 }
]);
CodePudding user response:
We could define a generic type ToOutputType
which takes a tuple and transforms the type to the desired object type using a mapped type.
type ToOutputType<T extends { name: string, value: any }[]> = {
[K in T[number] as K["name"]]: { value: K["value"] }
}
We also modify the arrayOfObjectsToObject
to make it generic.
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
const arrayOfObjectsToObject =
<
T extends { name: K, value: any }[],
K extends string
>(array: readonly [...T]): Expand<ToOutputType<T>> => {
return array.reduce((acc, { name, value }) => {
acc[name] = { value };
return acc;
}, {} as any) as Expand<ToOutputType<T>>;
}
T
will hold the tuple passed to the function. K
will be used to narrow the strings in the tuple to literal types. We use the type Expand<ToOutputType<T>>
as the return type. The Expand
type is used just to make the type prettier.
When you call the function, you get the following result.
const result = arrayOfObjectsToObject([
{ name: 'test', value: 1 },
{ name: 'test2', value: 2 }
]);
const a = result.test
// ^? { value: number; }
const b = result.test2
// ^? { value: number; }
Notice that the type of value
is number
in both cases. TypeScript automatically widens the numbers to number
. To prevent this, we can use as const
.
const result = arrayOfObjectsToObject([
{ name: 'test', value: 1 },
{ name: 'test2', value: 2 }
] as const);
const a = result.test
// ^? { value: 1; }
const b = result.test2
// ^? { value: 2; }
If you don't want to use as const
, we can also use an extra magic generic type for inference.
type Narrowable = string | number | boolean | symbol | object | undefined | void | null | {};
const arrayOfObjectsToObject =
<
T extends { name: K, value: N }[],
N extends { [k: string]: N | T | [] } | Narrowable,
K extends string
>(array: readonly [...T]): Expand<ToOutputType<T>> => {
return array.reduce((acc, { name, value }) => {
acc[name] = { value };
return acc;
}, {} as any) as Expand<ToOutputType<T>>;
}