Reputation: 3
Can anyone tell my why I'm receiving:
Type 'string | number' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'.
When I try to assign value to object copied by JSON.parse(JSON.stringify()) object.
Here's my code:
interface X {
a: number
b: string
}
const one: X = {
a:1, b:'1'
}
let d:X = JSON.parse(JSON.stringify(one)) as X
const ke = Object.keys(d) as Array<keyof X>
ke.forEach(key => {
let nn = d[key];
nn = one[key];
d[key] = nn;
})
also playground link: TS-Playground
Upvotes: 0
Views: 561
Reputation: 330266
Since TypeScript 3.5 started to warn on possibly unsafe index access writes, this has been a pain point. In TypeScript 3.4 and below this would have been acceptable code since it would have been interpreting as assigning string | number
to string | number
; the downside is that it would also have been acceptable to write d[key] = Math.random() < 0.5 ? "str" : 123;
, which is clearly wrong.
In TypeScript 3.5 and above the compiler is now overly strict in cases like this; it insists that you can only assign something which is both a string
and a number
to d[key]
since it isn't sure which element of keyof X
the key
is. Since there are no values of type string & number
, the type is reduced to never
. So it's impossible to safely assign any value to d[key]
according to the compiler.
There's an open issue, microsoft/TypeScript#32693, asking to allow such assignments, in the case where the key is the exactly the same in both the read and the write, and not just the same type (since two values of type keyof X
could be different keys, but two references to the same value of type keyof X
cannot). This hasn't been implemented yet, though. If you want to see it implemented, you might want to go to that issue and give it a 👍. In the meantime, we have to work around it.
One workaround here (other than using a type assertion which is always an option, or writing redundant code as in the other answer) is to make your callback function generic in the type of key
:
ke.forEach(<K extends keyof X>(key: K) => {
let nn = d[key];
nn = one[key];
d[key] = nn;
})
There is no error there because the compiler does allow reads and writes to values of identical types, and when you have K
as an unresolved generic, the compiler thinks that X[K]
on the read is the same as the X[K]
on the write. (I got this from this comment, which is explained by the chief architect of TS).
Anyway, hope that helps; good luck!
Upvotes: 0
Reputation: 25850
This operation:
d[key] = nn;
is (correctly) rejected by the type-checker. Because neither key
nor nn
are known in advance, there is a possibility that the wrong value will be assigned to the wrong property.
key
is "a" | "b"
. This means that d[key]
should be either string | number
, depending on whether key
is "a"
or "b"
. You can't assign a string
to "a"
and a number to "b"
— because that wouldn't be an X
anymore.
So, we have two degrees of freedom with two options for each. This leaves us with 4 possible scenarios, out of which only two are valid.
A solution would be to ensure we're dealing with one of the correct combinations:
ke.forEach(key => {
let nn = d[key];
nn = one[key];
if (key === 'a' && typeof nn === 'number') {
d[key] = nn;
}
if (key === 'b' && typeof nn === 'string') {
d[key] = nn;
}
})
Upvotes: 2