I suggest you change your code to look like this:
function reduceByProp<T, K extends PropertyKey>(
array: T[],
mapper: (a: T) => K
) {
return array.reduce(
(previous, current) => ({ ...previous, [mapper(current)]: current }),
{} as { [P in K]?: T }
);
}
Explaining the differences:
For your question, you can't do {[key: K]: T}
or the like, since index signatures are constrained to be all strings or all numbers. Instead you can use mapped types of the form {[P in K]: T}
.
Unless you want reduceByProp([{foo: 1}], v => "bar")
to fail, you should make K extends PropertyKey
and not K extends keyof T
. keyof T
is specifically only the keys inside the objects in your array, while PropertyKey
is any key you want.
Don't annotate previous
and current
, or if you do annotate them, don't annotate them as T
. current
is definitely T
, but previous
is an accumulator and is not T
but the return type of reduceByProp()
which is something whose keys are returned by mapper()
and whose value types are T
.
Give the initial reduce object {}
an explicit type, or otherwise specify what reduce()
is expected to produce. The value {}
will be inferred as type {}
otherwise, which will fail to type check. So I've given it {} as ...
.
I've made the return type {[P in K]?: T}
in which the properties are optional (?
) instead of required as in {[P in K]: T}
. The reason is that you might want to make a call like this:
reduceByProp([{ foo: 1 }, { foo: 3 }, { foo: 5 }], v => v.foo % 2 === 0 ? "even" : "odd");
The return type of that in my version is {even?: {foo: number}, odd?: {foo: number}}
. It's good that those are optional because it turns out that the output has no even
key at all.
Okay, hope that helps; good luck!
Playground link to code