Home > Enterprise >  Discriminatable key-value tuple type (of a given object)
Discriminatable key-value tuple type (of a given object)

Time:09-26

Given the following code:

interface Obj {
  foo: string;
  bar: number;
}
type ObjKey = keyof Obj;
type ObjVal<Key extends ObjKey> = Obj[Key];
type ObjTuple<Key extends ObjKey> = [Key, ObjVal<Key>];

I want to create a function that takes in a tuple (of unknown type), and discriminates the Key part (at index 0), to figure out what the value type is:

function doThings(tuple: ObjTuple): string {
  switch (tuple[0]) {
    case "bar":
      return tuple[1].toFixed();
    case "foo":
      return tuple[1];
  }
}

Of course, this doesn't work, as the type argument for ObjTuple is missing. Since I don't want to specify it (the function should figure out what type it is), I changed the declaration to have ObjKey as default:

type ObjTuple<Key extends ObjKey = ObjKey> = [Key, ObjVal<Key>];

Sadly, the discrimination is not working at all. TypeScript correctly detects the allowed variants of the key (e.g. tells me when a code path is missing, or when the switch contains an impossible key), but the value is always a union of all the variants.

Here is a link to a playground, with the final code.

CodePudding user response:

The problem here is the type of ObjVal. The editor is not doing us a favor when displaying it but here is how the expanded type looks like:

type ObjTuple = ["foo" | "bar", string | number]

That does not look right at all since there is not correlation between specific properties and types anymore. Both elements are just a union.

We rather want a type which looks like this:

type ObjTuple = ["foo", string] | ["bar", number]

And we can generate this one using ObjTuple with one modification. We need to distribute over Key.

type ObjTuple<Key extends ObjKey = ObjKey> = Key extends Key 
  ? [Key, ObjVal<Key>]
  : never

This should solve the issue.


Playground

  • Related