6

I have an Object with keys that I've typed and I want to loop over it and maintain their type, avoiding "Element implicitly has an 'any' type because 'ContactList' has no index signature".

I know this has been discussed at length on Typescript's GitHub pages but I haven't found any solutions for what I'm trying to do here. Trying it both with enums and with unions, but I still get the error.

Example with enums (playground)

enum Names {
  Joe,
  Bill,
  Bob
}

type ContactList = {
  [key in Names]: {
    isFriend: boolean;
  }
}

const contactList: ContactList = {
  [Names.Joe]: {
    isFriend: true,
  },
  [Names.Bill]: {
    isFriend: false,
  },
  [Names.Bob]: {
    isFriend: true,
    },
};

Object.keys(contactList).forEach(name => {
    // turn on 'noImplicitAny' and get error
    console.log(contactList[name])
})

Example with unions (playground)

type Names = 'joe' | 'bill' | 'bob'

type ContactList = {
  [K in Names]: {
    isFriend: boolean;
  }
}

const contactList: ContactList = {
  joe: {
    isFriend: true,
  },
  bill: {
    isFriend: false,
  },
  bob: {
    isFriend: true,
    },
};

Object.keys(contactList).forEach(name => {
    // turn on 'noImplicitAny' and get error
    console.log(contactList[name])
})

Either way, I'm telling typescript "the only keys allowed in this object are from Names" and as far as I understand index signatures, [key in Names] should be one, so I'm not sure why I'm seeing an error. Sorry if this is a noob question, I'm a little green with TS still. Thanks.

hello_luke
  • 837
  • 12
  • 15
  • It sounds weird... with the union you can write a workaround: `(Object.keys(contactList) as Names[])`. It doesn't work with the enum. – Joel Mar 05 '19 at 21:36

1 Answers1

6

As Object.keys might return more properties than the object actually contains by type, the return type of it is intentionally typed as string[]. Otherwise, it would be pretty common for well-typed code to throw unexpected exceptions at runtime, as explained in this answer.

Therefore you have to make your index type work on strings:

type ContactList = {
  [key: string]: {
    isFriend: boolean;
  },
};

Or you typecast and hope for the best:

contactList[name as Names]

Also you could just:

console.log(...Object.values(contactList));
artem
  • 46,476
  • 8
  • 74
  • 78
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Thanks, but I'm trying to avoid opening it up to any ol' string. I want it to throw an error if something other than my approved "whitelist" of names is in there. (Also I know I could console.log the values in an easier way, this is just a contrived example. Ultimately going into a react project with .map) – hello_luke Mar 05 '19 at 21:31
  • @dramus sometimes it is difficult for typescript to wrap around Javascript's dynmic model. – Jonas Wilms Mar 05 '19 at 21:34
  • If I declare up front that the only keys allowed on ContactList are Names, how can Object.keys() return anything other than Names? – hello_luke Mar 05 '19 at 21:35
  • 1
    @dramus `Object.prototype.whyThough = "idk";` – Jonas Wilms Mar 05 '19 at 21:35
  • 2
    You might have some *subtype* of `ContactList` which has additional keys. – Ryan Cavanaugh Mar 05 '19 at 21:36
  • Hmm. What about `console.log(contactList[name as keyof ContactList])`? That seems to play nice with the enums, as long as I make them strings, like `Bill = 'bill'`. No errors then. Does that violate some other best practice though? – hello_luke Mar 05 '19 at 21:54