Chet
Chet

Reputation: 19839

How to use Typescript type object indexing for typed generic functions?

I have an api with a typed map from api name to api input object type:

type ApiInputTypes = {
    doThis: { x: number }
    doThat: { y: string }
}

type ApiNames = keyof ApiInputTypes

My goal is to write a generic function that can handle any of these api requests, but I'm getting a type error where I would not expect it.

function handleApi<Name extends ApiNames>(name: Name, args: ApiInputTypes[Name]) {
    if (name === "doThis") {
        args.x // Error
    } else {
        args.y // Error
    }
 }

Ironically, this still works though...

// But this works...
handleApi("doThis", { x: 190 })

Any ideas? Here's a playground link.

Upvotes: 1

Views: 60

Answers (2)

jcalz
jcalz

Reputation: 328132

Control flow analysis currently does not narrow generic type parameters (see microsoft/TypeScript#24085). It looks like this issue has been reported multiple times with multiple suggestions for dealing with it. Nothing's been implemented yet. For now there are just workarounds.

One possible workaround is to package name and args into a single value nameAndArgs, whose type you specify as a union of the two possible cases.

type ValueOf<T> = T[keyof T];
type NameAndArgs = ValueOf<{ [N in ApiNames]: { name: N, args: ApiInputTypes[N] } }>;

function handleApi<Name extends ApiNames>(name: Name, args: ApiInputTypes[Name]) {
  const nameAndArgs = { name, args } as NameAndArgs;
  if (nameAndArgs.name === "doThis") {
    nameAndArgs.args.x // okay
  } else {
    nameAndArgs.args.y // okay
  }
}

Note that the NameAndArgs property is equivalent to

type NameAndArgs = {
    name: "doThis";
    args: {
        x: number;
    };
} | {
    name: "doThat";
    args: {
        y: string;
    };
}

The function works because the single nameAndArgs variable can be narrowed, and the name and args properties stay narrowed in the relevant clauses.

Upvotes: 5

andrew ferguson
andrew ferguson

Reputation: 189

Try storing the types in an enum

export enum ApiInputTypes {
  doThis = 'doThis',
  doThat = 'doThat'
}

function handleApi(name: ApiInputTypes) {
  if(name === ApiInputTypes.doThis){
   //doThis
  }
}

Upvotes: 0

Related Questions