Issue
I'm trying to loop over the properties of an object and overwrite an existing object of the same type. It works in the runtime, but Typescript says:
pet2[key]: Type 'string | number' is not assignable to type 'never'.ts(2322)
type Pet = { name: string; age: number };
const pet1: Pet = { name: 'Speedy', age: 4 };
const pet2: Pet = { name: 'Bibi', age: 5 };
// overwrite manually - no typescript error:
pet2['name'] = pet1['name'];
pet2['age'] = pet1['age'];
// overwrite with a loop - typescript error:
const keys = Object.keys(pet1) as Array<keyof Pet>;
for (const key of keys)
pet2[key] = pet1[key];
How can I achieve this with a loop, so that Typescript doesnt complain?
To me it looks like Typescript evaluates all possible types on the right side of the assignment (string | number) and then tries to combine all properties of the left side (string & number) and comes the the conclusion: (string | number) doesnt fit (never).
Edit: This is just a simplified version of my problem. I run into this problem over and over again. Today i ran into it with a svelte-store-subscribe:
for (const filterColum of filterColumns)
selected[filterColum].subscribe(($value) => { $selected[filterColum] = $value })
Solution
This is a known pain point of TypeScript, reported at microsoft/TypeScript#32693. When you have two objects pet1
and pet2
of type Pet
, and a key key
of a union type assignable to keyof Pet
, the compiler will not generally let you write
pet2[key] = pet1[key]; // error!
because it doesn't pay attention to the fact that key
is the very same value on either side. It treats it like you might have two keys key1
and key2
, both of the same union type:
pet2[key2] = pet1[key1]; // error
And you want that to be an error, because if keyof Pet
is "name" | "age"
, then you want to avoid the situation where key1
is "name"
while key2
is "age"
and you assign a number to a string property.
It would be very nice if the compiler could distinguish those two assignments, since one is always safe and the other rarely is. But until and unless microsoft/TypeScript#32693 is addressed, you'll need to work around it.
The easiest workaround for the example as given is to forget about loops and just use the Object.assign()
method like Object.assign(pet2, pet1)
. But if you need to have something looplike in your code, that won't work.
The general recommended approach to situations like this where you want the compiler to keep track of correlated union types is to refactor to use generics instead. So if Obj[keyof Obj]
doesn't work, you can try Obj[K]
where K extends keyof Obj
:
(Object.keys(pet1) as Array<keyof Pet>).forEach(
<K extends keyof Pet>(key: K) => {
pet2[key] = pet1[key];
});
Here I'm using the forEach()
method because it takes a callback and that's a convenient place to put generics. You could stick with a for...of
loop but it's much clunkier to use:
const keys = Object.keys(pet1) as Array<keyof Pet>;
for (const key of keys) {
(<K extends keyof Pet>(key: K) => { pet2[key] = pet1[key] })(key);
}
If none of those are to your liking, you are always free to use a type assertion to appease the compiler, such as the following assertion that pet2
is of the intentionally loose any
type:
for (const key of keys)
(pet2 as any)[key] = pet1[key]; // okay
Note that it is generally not recommended to use the //@ts-ignore
compiler directive to silence an error unless it's the only way for you to proceed. The //@ts-ignore
directive is like putting a piece of tape over a warning light; it does not actually resolve the error, it just prevents you from seeing it, and can cause weird things to happen elsewhere in the code. It also cannot be used to silence a particular error; anything else that's wrong on the same line will be ignored:
for (const key of keys)
//@ts-ignore
pet3[key] - pet1[kay]; // okay?!
Presumably you'd like to get some kind of warning on undeclared variables or incorrect operators. As it says in the handbook documentation for //@ts-ignore
, "we recommend you use these comments very sparingly".
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.