Object.keys
always returns a string[]
, regardless of the input. It might be expected to return Array<keyof typeof obj>
, but that is not the case.
For more information, please refer to this list of issues on github.
In such cases, it is advisable to use a type assertion
.
(Object.keys(obj) as Array<keyof typeof obj>)
However, the struggle does not end there.
There is still an error present:
this[key] = obj[key]; // error
In general, TypeScript tends to discourage mutations. To delve deeper into this topic, you can check out my article and this answer.
The types of this[key]
and obj[key]
are both string | boolean
.
Consider the following code snippet:
type Key = "name" | "check";
let _name: Key = 'name'
let _check: Key = 'check'
obj[_name] = obj[_check] // error
This code bears close resemblance to yours, except for the fact that your mutation occurs within an iterator while mine does not. There is no direct correlation between the iteration index and the type of key
.
Take this example:
(Object.keys(obj) as Array<keyof typeof obj>)
.forEach((key, index) => {
if (index === 0) {
const test = key // keyof modal and not "name"
}
})
This behavior is correct because even according to JavaScript specifications, there is no guarantee that the first key
will be name
. JS engine reserves the right to return keys in any order. While you may usually get the anticipated order in 99.99% of cases, it doesn't assure you of a specific sequence.
So, why does the error message mention never
? TypeScript combines the expected keys in a union to ensure a common type. The intersection of string & boolean
results in never
, hence the error message.
To avoid using type assertions
, one effective approach is to utilize reduce
:
interface modal {
name: string;
check: boolean;
}
type Key = "name" | "check";
const obj: modal = fn();
function fn(): any {
return {
name: "hello"
}
}
class Modal implements modal {
name: string;
check: boolean;
constructor() {
this.name = "";
this.check = false;
const result = (Object.keys(obj) as Array<keyof typeof obj>)
.reduce((acc, key) => ({
...acc,
[key]: obj[key]
}), this)
Object.assign(this, result)
}
}
Playground
It is recommended to either use implements modal
or capitalize the modal
interface.