In the line
const assetTypes = ['VMs', 'Applications', 'Databases'];
you haven't annotated the type like const assertTypes: XXX =. So TypeScript has to infer the type of assetTypes from the array literal with which you're initializing it. And the type that it infers will be consulted in all subsequent uses of the assetTypes variable.
There are lots of possible types that are applicable for the particular array literal. It could be something super general like the unknown type, or it could be very specific like a tuple type consisting of the string literal types ["VMs", "Applications", "Databases"]. Or anything in between, like Array<string | number> | boolean. It chooses based on heuristic rules that work well in a wide variety of real-world code, but these rules don't always do what everyone wants.
In the case of an array literal with string literals inside it, TypeScript infers the type to be an array of string: that is, Array<string> or the equivalent string[]. That allows you to push() new string values onto it, or sort() it, etc. But it means the compiler does not even try to track which string values are in the array or where they are. Again, this works well for many use cases.
But unfortunately that means TypeScript has no idea that the code
for (const assetType of assetTypes) {
const connectStrings = data.SystemElementGroups[assetType];
is safe. You are indexing into data.SystemElementGroups with assetType of type string, which could be any string at all, most of which are not known to be keys. And that's what the error message says.
The problem is that assetTypes has too general of a type to be useful for your needs.
If you want to convince the compiler that assetTypes only contains the keys of the SystemElementGroups property of an EnvDefinition, you could annotate assertTypes as such:
const assetTypes: Array<keyof EnvDefinition['SystemElementGroups']> =
['VMs', 'Applications', 'Databases'];
for (const assetType of assetTypes) {
const connectStrings = data.SystemElementGroups[assetType]; // okay
}
That works, but it's a little verbose.
Another approach is use a const assertion to tell the compiler that the array initialized by the array literal is not intended to change at all. It will forever be an array of three elements, whose types are the string literal types of the initializing elements, and they will be in the exact order as initialized:
const assetTypes = ['VMs', 'Applications', 'Databases'] as const;
// const assetTypes: readonly ["VMs", "Applications", "Databases"]
Now the type of assetTypes is very specific. You can't push() some random string onto it, or sort() it, or write assetTypes[0]="random". This might be even more specific than you care about, but it's easy to do with as const... and now the compiler has enough information to know that each element of assetTypes is a valid key for data.SystemElementGroups:
for (const assetType of assetTypes) {
const connectStrings = data.SystemElementGroups[assetType]; // okay
}
Playground link to code
assetTypesis juststring[]because TS doesn't normally care about exactly which strings are in an array. If you want to see something else, you can use aconstassertion as shown in this playground link to make TS keep track of the exact contents, and then the error goes away. Does that fully address your question? If so I'll write up an answer explaining; if not, what am I missing?...] as const;-- thank you! However, I don't really understand why that fixes it, so if you post as an answer I'd appreciate it.