0
votes

My function can work with array and objects. Objects have keys with string-type and array have keys with numeric-type. Also there can be object without keys.

I determine two interfaces and one type:

interface IObjectStringKey {
  [key: string]: any
}
interface IObjectNumberKey {
  [key: number]: any
}
// object with key-string, key-number or without key
type IObjectAnyKey = IObjectNumberKey | IObjectStringKey | {};

My function get argument - array of this type and I want to iterate through every key of every object.

function myFunction( ...sources: IObjectAnyKey[]) {
        const values = [];
for (let i = 0; i < sources.length; i++) {
    const source: IObjectAnyKey = sources[i];
        for (let key in source) {
            const val = source[key];
        }
    }
}

You can see error in playground: typescript.org/myTask (you need to enable 'noImplicitAny' in Options)

enter image description here

Error is "Element implicitly has an 'any' type because type 'IObjectAnyKey' has no index signature". How can I solve this problem? I need to know how to define type with different index type. Or, maybe, there is some other solve.

2
hmm. I didn't see any error. - Tan Duong
@TanDuong you need to click on 'Options' and check "noImplicitAny' - Alexander Knyazev

2 Answers

1
votes

IObjectAnyKey is essentially equivalent to the empty type {}. The type {} matches pretty much every value (except for null and undefined), so the union of {} with anything else (except a possibly null or undefined type) is also {}. TypeScript therefore doesn't know what keys are in source (keyof IObjectAnyKey is equivalent to never, the empty set) so it balks at indexing into it.

Your IObjectStringKey is only slightly narrower than {}. Since TypeScript introduced implicit index signatures, any type with known literal keys can be widened to a type with an index signature whose value type is the union of all value types. For example:

const literalKeys = {a: 'hey', b: 2, c: true};
const indexSignature: {[k: string]: string | number | boolean} = literalKeys;

The above assignment works because literalKeys is compatible with the index signature. Therefore, if you make myFunction() accept an array of IObjectStringKey, it shouldn't prevent you from calling it:

function myFunction(...sources: IObjectStringKey[]) {
  const values = [];
  for (let i = 0; i < sources.length; i++) {
    const source = sources[i];
    for (let key in source) {
      const val = source[key]; // no error
    }
  }
}
myFunction({},{a: 'hey'},{b: 2}); // still works

If you insist on using (the relatively useless) IObjectAnyKey, you can always just do a type assertion to make the error go away:

function myFunction(...sources: IObjectAnyKey[]) {
  const values = [];
  for (let i = 0; i < sources.length; i++) {
    const source = sources[i];
    for (let key in source) {
      const val = (source as any)[key]; // explicitly "any"
    }
  }
}

Hope that helps. Good luck.

0
votes

in javascript the property key of an object is always converted to a string, so obj[1] is equivalent to obj['1'] and you can only implement the first interface IObjectStringKey. in general, you should avoid using pseudo arrays and use real arrays if all your data is contiguous.