To make your code more versatile, you can start by generalizing the T
type.
function toMap<T extends { type: string }>(...args: T[]): { [type: string]: T } {
return args.reduce((res, t) => ({
...res,
[t.type]: t
}), {});
}
If you want to further refine the types, you need to specify generic types for variable arguments, like toMap<A>(arg1: A)
,
toMap<A, B>(arg1: A, arg2: B)
.
However, there are a couple of drawbacks:
1) You have to create these overloads for different numbers of arguments, which is common in Typescript (similar to the way it's done with Object.assign
).
2) By default, Typescript types { type: "test" }
as { type: string }
, making it impossible to directly infer the keys as "test"
. To work around this, you must cast the string literal to a specific narrowed down string type like { type: "test" as "test" }
.
// Generic overload for one argument
function toMap<A>(arg: A): { [K1 in O<A>]: A };
// Generic overload for two arguments:
function toMap<A, B>(arg: A, arg2: B): { [K in O<A>]: A } & { [K in O<B>]: B };
// Add more overloads for additional arguments
// Implementation for various arguments
function toMap<T extends { type: string }>(...args: T[]): { [type: string]: T } {
return args.reduce((res, t) => ({
...res,
[t.type]: t
}), {});
}
// Infer the type of "type" from an object
type O<V> = V extends { type: infer K } ? K extends string ? K : never : never;
// Narrow down a.type to be "test"
const a = { type: "test" as "test" }
const b = { type: "test2" as "test2", v: 1 };
const test = toMap(a);
const test2 = toMap(a, b);
console.log(
test2.test2.v, // Works!
test2.whatever, // Doesn't work!
test2.test2.k // Doesn't work!
);
Give it a try!