6

My question is similar to Types from both keys and values of object in Typescript, but with an additional layer of nesting:

interface Outer {
  a: {
    b: number
  },
  c: {
    d: string
  }
}

And I'm only looking to retrieve the union type of all values, here number | string.

Here's my stab:

type Values<T extends Record<string, any>> = T[keyof T]; // From previous SO answer

type InnerValues<
  T extends Record<string, Record<string, any>>,
  K extends keyof T
> = T[K][keyof T[K]];

type All = Values<Outer>;
type In = InnerValues<Outer, keyof Outer>; // expected number|string

but I have an error saying Outer doesn't have an index type.

Playground link.

jeanpaul62
  • 9,451
  • 13
  • 54
  • 94

1 Answers1

9

T[K][keyof T[K]] will not work because T[K] will be a union of all possible values in T (well the values specified by K anyway) and keyof T[K] will probably end up being never if the values have no common keys.

The solution is to take each constituent in the union T[K] and get its possible values. We can do this using a distributive conditional type

interface Outer {
  a: {
    b: number;
  };
  c: {
    d: string;
  };
}

type DistributiveValues<T extends Record<string, any>> = T extends T ? T[keyof T] : never;

type InnerValues<
  T extends Record<keyof T, object>,
  K extends keyof T
> = DistributiveValues<T[K]>;

type In = InnerValues<Outer, keyof Outer>; // is number|string

play

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 1
    Would it be possible to make this recursive so it could give you a union of all the non-`Record` leaf value types in an arbitrarily deep nesting of objects? For example, if the property `d` in `Outer` was an object rather than a string. – Jimmy Feb 13 '22 at 09:57