Home > database >  How do I express "K extends keyof T such that T[K] is of type V"?
How do I express "K extends keyof T such that T[K] is of type V"?

Time:03-19

I want to create a function signature like this:

countBy<R, V>(objs: R[], iteratee: ((x: R) => V) | keyof R): Map<V, number> {}

In the style of lodash, iteratee should either be a function (x: R) => V or the shorthand keyof R:

countBy(pizzas, pizza => pizza.sauce)
countBy(pizzas, 'sauce')

How can I tell Typescript that if I use the shorthand ('sauce'), R[keyof R] must produce a value of type V?

CodePudding user response:

Assuming you won't need to type the function body (which is trickier), you can create two overloads to achieve this:

function countBy<R, V>(objs: R[], iteratee: ((x: R) => V)): Map<V, number>
function countBy<R, K extends keyof R, V extends R[K]>(objs: R[], iteratee: K): Map<V, number>
function countBy<R, V>(objs: R[], iteratee: ((x: R) => V) | keyof R): Map<V, number> {
  return {} as any
}

TypeScript playground

CodePudding user response:

To expand upon Oblosys' answer, here is another set of overloads that would achieve your goal:

function countBy<R, K extends keyof R>(
  objs: R[],
  iteratee: K
): Map<R[K], number>;

function countBy<R, V>(
  objs: R[],
  iteratee: (x: R) => V
): Map<V, number>;

function countBy<R>(
  objs: R[],
  iteratee: ((x: R) => unknown) | keyof R
): Map<unknown, number> {
  /* ... your code ... */
}

Alternatively, if you don't want to bother with overloads or you want to have a typed body, you can try the following signature, which takes advantage of the fact that you can put conditional types in the return type:

function countBy<R, I extends ((x: R) => any) | keyof R>(
  objs: R[],
  iteratee: I
): Map<
  I extends keyof R ? R[I] : I extends (x: R) => any ? ReturnType<I> : never,
  number
> {
  /* ... your code ... */
}
  • Related