Typescript for in loop, iterate through union type of object or array

Viewed 2489

I'm trying to use the for in loop over union type of either object or array, since arrays are objects as well and their key property is the index.

const loopOver = (question: any[] | {[key: string]: any} ) => {
  for (let key in question) { // error ts 7053
    console.log(question[key])
  }
};

results in error

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | { [key: string]: any; }'.
  No index signature with a parameter of type 'string' was found on type 'any[] | { [key: string]: any; }'.ts(7053)

but once I remove the union type and leave it to either object or array, then I don't get any errors.

// okay
const loopOver = (question: {[key: string]: any} ) => {
  for (let key in question) {
    console.log(question[key])
  }
};
// okay
const loopOver = (question: any[] ) => {
  for (let key in question) { 
    console.log(question[key])
  }
};

tsconfig

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "exclude": ["node_modules"],
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

ts version in package.json ^3.8.3

1 Answers

Arrays should be indexed with numbers, not strings. But for..in iterates strings - your key is a string. When looking up properties on the array, cast the string key to a number for it to work. (But when looking up the property on the object, since the object uses [key: string]: any, you have to keep looking up using the string)

const loopOver = (question: [] | {[key: string]: any} ) => {
  for (let key in question) {
    if (Array.isArray(question)) {
      console.log(question[Number(key)])
    } else {
      console.log(question[key])
    }
  }
};

But, from this code, it looks like you don't actually care about the keys at all (and for..in should not be used with arrays anyway). You only care about the values, so how about using Object.values instead?

const loopOver = (question: [] | {[key: string]: any} ) => {
  for (const val of Object.values(question)) {
    console.log(val)
  }
};
Related