My scenario involves three variables with the same type:
const foo = { name: "foo", age: 12, color: "red" } as const;
const bar = { name: "bar", age: 46, color: "blue" } as const;
const baz = { name: "baz", age: 52, color: "green" } as const;
I place these variables into an array like this:
const arr = [foo, bar, baz];
The goal is to transform this array into a dictionary based on name
as a unique identifier, where the "name" becomes the key. It's important to maintain the keys as string literal types for further property usage like so:
const dict = arrayToDict(arr);
// `dict.foo` is a known key of `dict`
console.log(dict.foo.age);
Fortunately, there is a function that achieves this in my case. Here it is:
export function arrayToObjectName<T extends { name: S }, S extends PropertyKey>(
arr: readonly T[]
) {
return arr
.reduce((acc, v) => ({ ...acc, [v.name]: v }), {} as { [V in T as V["name"]]: V });
}
However, I aim to make this function more generic by allowing dynamic property selection within the function. Here is my attempt:
function arrayToObject<S extends PropertyKey>(prop: S) {
return <T extends { [prop]: S }>(
arr: readonly T[]
) => {
return arr
.reduce((acc, v) => ({ ...acc, [prop]: v }), {} as { [V in T as V[S]]: V });
};
Unfortunately, this approach encounters two issues. The first error pertains to the second function signature (see error):
A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
Additionally, the assignment V[S]
at the end triggers the following error:
Type 'V[S]' is not assignable to type 'string | number | symbol'
Interestingly, despite these type errors, the function still functions correctly!
If you have any insights on how to create a generic function that preserves types successfully, please share. The TypeScript Playground link below showcases both functions and relevant type tests.
An alternative generic implementation has been developed, which also works but has one persistent error:
export type Narrowable =
string | number | boolean | symbol | object | undefined | void | null | {};
function arrayToObject<
S extends PropertyKey>(prop: S) {
return <N extends Narrowable, T extends Record<keyof T, N> & Record<S, T[S]>>(
arr: readonly T[]
) => {
return arr.reduce(
(acc, v) => ({ ...acc, [prop]: v }), {} as { [V in T as V[S]]: V }
);
};
}
The recurrent error is linked to V[S]
, however, the new type ensures that S must be a keyof T
. This resolves the initial error encountered.