Home > Back-end >  TypeScript: How to cast generic type to another generic type when overloading a function?
TypeScript: How to cast generic type to another generic type when overloading a function?

Time:03-02

I want to define a generic function that creates a dictionary from an array. The function takes as parameters the array, a keySelector and an optional valueSelector. If no valueSelector is provided, the function fallbacks to the identity function. I wished Typescript would understand that type V is the same as T. Instead, the compiler gives me the error Type 'T' is not assignable to type 'V'.

export function arrayToDictionary<T, K extends string | number | symbol, V>(
  array: T[],
  keySelector: (item: T) => K,
  valueSelector: (item: T) => V = (item) => item // ERROR Type 'T' is not assignable to type 'V'
): Record<K, V> {
  return array.reduce(
    (acc, curr) => ({ ...acc, [keySelector(curr)]: valueSelector(curr) }),
    {} as Record<K, V>
  );
}

Here is the link to the TypeScript Playground.

The only solution I found is to use the any keyword.

  valueSelector: (item: T) => V = (item: any) => item

is there a better way to define the type of the function?

CodePudding user response:

You can relax the return type of the valueSelector param from simply V to V | T, which is exactly what you want to express, and at the same time provide a default type for your generic V (which would otherwise be inferred as unknown if you don't pass a third argument to the function).

function arrayToDictionary<T, K extends string | number | symbol, V = T>(
  array: T[],
  keySelector: (item: T) => K,
  valueSelector: (item: T) => V | T = item => item
): Record<K, V> {
  return array.reduce(
    (acc, curr) => ({ ...acc, [keySelector(curr)]: valueSelector(curr) }),
    {} as Record<K, V>
  );
}

const arr = [1,2,3,4]

const dict1 = arrayToDictionary(arr, n => n.toString()   '!', n => n > 2) //Record<string, boolean>
const dict2 = arrayToDictionary(arr, n => n.toString()   '!') //Record<string, number>

CodePudding user response:

I think you can consider using function overloading:

function arrayToDictionary<T, K extends string | number | symbol>(
    array: T[],
    keySelector: (item: T) => K 
) : Record<K, K>;
function arrayToDictionary<T, K extends string | number | symbol, V>(
  array: T[],
  keySelector: (item: T) => K,
  valueSelector: (item: T) => V
): Record<K, V>;
function arrayToDictionary<T, K extends string | number | symbol, V>(
  array: T[],
  keySelector: (item: T) => K,
  valueSelector?: (item: T) => V
): Record<K, V> {
  return array.reduce(
    (acc, curr) => ({ ...acc, [keySelector(curr)]: valueSelector?.(curr)??curr }),
    {} as Record<K, V>
  );
}

While this does not directly resolve the issue it does provide clear feedback on the function signatures. The implementation is only expected to cover what the function does regardless of which signature was used to call it.

Playground link

  • Related