Search code examples
typescriptgenericsmapped-types

narrow down mapped type value types


The use case is as follows:

First, I have an interface called Test1 which maps between a certain key and a type. Next, I have a second interface, Test2, which maps between those same keys and different types. Third, I have functions (as values in mapper object) that know how to map between the first to the second.

Essentially, the use case is the need to map between different objects that are connected by some key, but to make it as generic as possible while constraint by the interfaces that holds the mappings.

The following code

interface Test1 {
  a: number;
  b: string;
}

interface Test2 {
  a: object;
  b: Array<number>;
}

type Mapper = {
  [P in keyof Test1]: (param: Test2[P]) => Test1[P];
};

const mapper: Mapper = {
  a: (param: object) => 123,
  b: (param: Array<number>) => 'asd'
};

function test<T extends keyof Test1>(type: T, msg: Test2[T]): Test1[T] {
  const func = mapper[type];
  return func(msg);
}

throws the following exception when trying to pass msg to func:

Argument of type `Test2[T]` is not assignable to parameter of type `number[]`.

In this example, lets assume type will be equal to 'a', then I want to receive the function that the mapper holds as value of key 'a' and use it on the msg I have.

I did see this` issue when searching for a solution, but I thought maybe my case is differnet, reason being that I'm using a mapped type.

Any way to work this out? Thanks!


Solution

  • The issue stems from the fact that the type of func is inferred to be (param: object & number[]) => 'number | string' which narrows to (param: number[]) => 'number | string' The typescript compiler is simply not smart enough to realize that the parameters signature of func always matches Test2[T].

    Unfortunately, your best solution is probably to just cast and tell the compiler that you know better.

    function test<T extends keyof Test1>(type: T, msg: Test2[T]): Test1[T] {
      const func = mapper[type] as any;
      return func(msg);
    }