Reputation: 644
I have an object for which at the start I don't know what keys are going to be in it, but it seems that typescript seems to not consider the possibility that a value for a key might not exist:
interface ById {
[key:string]: number[]
}
const test: ById = {
dummyValue: [1,2,3]
}
const doThing = (key: string) => {
// regardless of if this key exists, it still assumes it's an array
test[key].push(4)
}
doThing('test')
I figured I'd make an interface that makes it explicit that the value for this item might be undefined, so that it shows an error if I don't check if it exists, but for some reason it shows me an error, even though I check.
interface ByIdOptional {
[key:string]: number[] | undefined
}
const test: ByIdOptional = {
dummyValue: [1,2,3]
}
const doThing = (key: string) => {
if (typeof test[key] !== 'undefined') {
// Even though I'm testing for undefined, it's showing an error here
test[key].push(4)
}
}
doThing('test')
Is there a better way of achieving this, and to get type safety code that uses this object? I've added the code to a Typescript Playground as well (for testing)
Upvotes: 0
Views: 47
Reputation: 327624
Your | undefined
for the index signature value type is a good idea; right now index signature types are implausibly assumed to have defined values at each key. It's more correct but potentially annoying for the compiler to always expect a possibly-undefined
value in an index signature. There's an open GitHub issue tracking this (microsoft/TypeScript#13778) but I wouldn't expect any change here soon.
The problem with your check is that the compiler doesn't really have a way to track the sort of narrowing implied by typeof test[key] !== 'undefined'
. The variable key
is only known to be of type string
. The only possible narrowing the compiler could do would be to say "all string
-valued keys of test
have been verified not to be undefined
". That's not a desirable narrowing, so the compiler basically does nothing. There's a GitHub issue file about this, (microsoft/TypeScript#31445), which was closed as a design limitation. To address it the language would have to keep track of which individual variables were used as keys in type guards, which would be a lot of extra work for the occasional help it would provide.
The easiest workaround here is just to copy the value in question to a new variable, which can be checked via control flow analysis without worrying about re-doing property accesses:
const doThing = (key: string) => {
const testKey = test[key];
if (typeof testKey !== 'undefined') {
testKey.push(4)
}
}
Okay, hope that helps; good luck!
Upvotes: 2
Reputation: 1073968
Assign test[key]
to a local. TypeScript will then refine the type of the local based on the guard:
interface ByIdOptional {
[key:string]: number[] | undefined
}
const test: ByIdOptional = {
dummyValue: [1,2,3]
}
const doThing = (key: string) => {
const array = test[key]
if (typeof array !== 'undefined') {
array.push(4) // <=================== No error here
}
}
doThing('test')
Upvotes: 1