Mike
Mike

Reputation: 573

how can i properly type the index signature on a nested for...in loop in typescript?

I have a RootType like this

Type1 = {
 nestedProp1: string
 nestedProp2: string
}

Type2 = {
 nestedProp3: string
 nestedProp4: boolean
}

type RootType = {
  prop1?: Type1
  prop2?: Type2
}

And i'm trying to iterate through it to get all of the values like this:

const root: RootType = {
  prop1: {
    nestedProp1: 'foo',
    nestedProp2: 'bar'
  },
  prop2: {
    nestedProp3: 'baz',
    nestedProp4: false
  }
}
for(const key in root) {
  for(const nestedKey in root[key as keyof RootType]) {
    console.log(root[key as keyof RootType][nestedKey]) <---- how do you get the index signature type for the nested type key?
  }
{

For the top level, keyof RootType works fine for the top level keys, but i can't seem to get the nested key type. I've tried root[key as keyof RootType][nestedKey as keyof Type1 | keyof Type2] which dosen't work, i've tried creating a new interface that extends the RootType interface with a nested index signature as a string, but typescript complains that string isn't compatible with "prop1" | "prop2"

I'd rather not modify the RootType as it is generated off of a graphql schema, and if i change it, it'll require a custom type definition in a config file that we'll have to maintain, so a local overload would be preferable, and the best possible solution would be a clean way of getting typescript to infer the key type in a for...in loop so it dosen't have to be casted at all.

Upvotes: 1

Views: 1040

Answers (1)

Terry
Terry

Reputation: 66103

You can use const to store the value of the current key-value pair at each level of the for loop, so that you can access the value in the keyof typeof <...> declaration:

for (const key in root) {
  const nestedObj = root[key as keyof typeof root];

  for (const nestedKey in nestedObj) {
    const nestedValue = nestedObj[nestedKey as keyof typeof nestedObj];

    console.log(nestedValue);
  }
}

See proof-of-concept on TypeScript Playround.

Alternatively, if you prefer to stick to your older method of referencing the type of the object directly instead of the object itself, it is possible but that also means you need to use a union type for all the nested object keys:

for (const key in root) {
  const nestedObj = root[key as keyof RootType];

  for (const nestedKey in nestedObj) {
    // Can be very verbose, if your parent object has many nested object types
    const nestedValue = nestedObj[nestedKey as keyof (Type1 | Type2)];

    console.log(nestedValue);
  }
}

See proof-of-concept on TypeScript Playround.

Upvotes: 1

Related Questions