How can I access a common property of nested object types that themselves are accessed with generic type parameters

Viewed 147

Below is a simple generic type, "GetA", that is just supposed to index a type object to retrieve a nested type; it uses generic parameters as two of the indexes. At the last, non-generic index, however, it throws a type error ("a" cannot be used to index...), even though all of the possible objects have the indexed key. I hope that someone can help me understand.

How can I make a working version of GetA that uses the same or similar parameters?

type GetA<
  T extends keyof TestObj,
  P extends keyof TestObj[T]
> = TestObj[T][P]["a"];
// Type '"a"' cannot be used to index type 'TestObj[T][P]'.ts(2536)

type TestObj = {
  nested1: {
    prop: {
      a: "foo1";
      b: "bar1";
    };
    anotherProp: {
      a: "foo2";
      b: "bar2"
    }
  };
  nested2: {
    prop: {
      a: "foo3";
      b: "bar3";
    };
  };
};
1 Answers

Please keep in mind that extends does not mean equal. See this example:

type GetA<
  T extends keyof TestObj,
  P extends keyof TestObj[T]
  > = TestObj[T][P]['a'] // expected error

type TestObj = {
  nested1: {
    prop: {
      a: "foo1";
      b: "bar1";
    };
    anotherProp: {
      a: "foo2";
      b: "bar2"
    }
  };
  nested2: {
    prop: {
      a: "foo3";
      b: "bar3";
    };
  };
};

type Result = GetA<'nested1', 'prop' & { tag: 2 }> // unknown

You can find more explanation in my article

'prop' & { tag: 2 } is assignable to keyof TestObj[T] and at the same time it is a subtype of prop.

Try to get rid of {tag: 2} and you will get expected result:

type Result = GetA<'nested1', 'prop'> // foo1

Hence, in order to make it safer, you need to add conditional type:

type GetA<
  T extends keyof TestObj,
  P extends keyof TestObj[T]
  > = 'a' extends keyof TestObj[T][P] ? TestObj[T][P]['a'] : never

type TestObj = {
  nested1: {
    prop: {
      a: "foo1";
      b: "bar1";
    };
    anotherProp: {
      a: "foo2";
      b: "bar2"
    }
  };
  nested2: {
    prop: {
      a: "foo3";
      b: "bar3";
    };
  };
};

type Result = GetA<'nested1', 'prop'> // foo1

Playground

Also, you can make it less verbose:

type GetA<
  T extends keyof TestObj,
  P extends keyof TestObj[T],
  Result = TestObj[T][P]
  > = Result['a' & keyof Result]

Related