I'm guessing that you're running into the issue where Object.keys(obj)
returns string[]
instead of something like (keyof typeof obj)[]
. It's a common issue that gets reported a lot. The reason Object.keys()
has to return string[]
is because types in TypeScript are open in the sense that an object has to have at least the properties described by a type for it to match. So the only type-safe return value is string[]
. See this comment for more information.
This means that assuming data.details
is of type Idetails
(I don't see that in your code... data
is just of type any
; you should tighten that up), all you know is that it has at least the primary
and secondary
properties, but it might have more. For example, data.details
might be
const details = {
primary: { beginningBalance: "$0", endingBalance: "$200" },
secondary: { beginningBalance: "25¢", endingBalance: "10¢" },
tertiary: { beginningBalance: "₿100,000", endingBalance: "₿0.001" }
}
And therefore key
is not a valid index into response.details
, since key
might be "teritary"
.
The easiest way to deal with this is just to assert that Object.keys(data.details)
returns just the keys you know about. Sure, it's possible that at runtime there will be extra keys, and the code will just copy those extra properties into response.details
... which is probably harmless since it doesn't stop response.details
from being a valid Idetails
. Here's how you could do it:
(Object.keys(data.details) as (keyof Idetails)[]).forEach((key) => {
response.details[key] = data.details[key]; // okay
});
Notice that we are using the as
keyword to assert that Object.keys(data.details)
returns a (keyof Idetails)[]
. Now, key
is inferred to be "primary" | "secondary"
, and the compiler is happy with the assignment.
There are other ways to deal with the issue in the case where you want to prevent copying extra properties, such as manually specifying the array of keys to copy without inspecting data.details
at all:
// helper function to narrow array to string literals
const stringLiterals = <T extends string[]>(...args: T) => args;
// stringLiterals("primary", "secondary") is inferred as type ["primary", "secondary"]
stringLiterals("primary", "secondary").forEach((key) => {
response.details[key] = data.details[key]; // okay
});
This is now completely type safe and doesn't require any type assertions, but it might be more trouble than it's worth.
Hope that helps; good luck!