Encountered an unexpected behavior with Typescript
I've been using a pattern to define a dictionary of string
keys mapped to Foo
values like this:
type Dictionary = { [id: string]: Foo }
What caught me off guard is that when attempting to access a value from the Dictionary using any key, Typescript returns the type as Foo
Normally, I would expect it to be Foo | undefined
, since if the key does not have a corresponding value, undefined should be returned.
The behavior can be illustrated by this example in the TypeScript Playground - see playground here
type Foo = { bar: number }
type Dictionary = { [id: string]: Foo }
const a: Dictionary = {
one: { bar: 1 },
two: { bar: 2 }
}
const addOneToBar = (foo: Foo) => foo.bar + 1
// No type error, maybe expected, because we 'know' from context that
// the one property exists on the Dictionary instance a
alert(addOneToBar(a.one))
alert(addOneToBar(a['one']))
try {
// There is still no type error here, though. Why?
// Shouldn't a.three / a['three'] return Foo | undefined
// instead of Foo, because we're not certain there is any three property?
alert(addOneToBar(a.three))
alert(addOneToBar(a['three']))
} catch (err) {
alert('Of course, there is a null pointer error: ' + err)
}
This code seems to lack type safety and leads to a runtime exception that is not identified by Typescript.
This behavior is surprising as returning undefined
for a non-existent key is inherent in JavaScript objects, as well as in most languages with generic dictionaries/maps. It's puzzling why Typescript overlooks this.
Maybe I made an error in the above code, or misunderstood the purpose of the { [id: string]: Foo }
pattern. Is there another way to type dictionaries in Typescript that handles this behavior correctly? (I attempted using the Record
type but encountered the same issue).
Can someone shed light on this?
Edit
Realized while writing this question that I could potentially use
type Dictionary = { [id: string]: Foo | undefined }
to achieve the desired outcome. But why isn't this the default behavior? Especially with a string
key, shouldn't there be a guarantee that a value may not always exist in the Dictionary?
Further Edit
As discussed in the comments, I now understand that the behavior of Typescript actually makes sense, and explicitly stating that the dictionary may include undefined values is the solution. It's about assuming that TS should infer things from types rather than explicitly declaring them. The approach I suggested works well when keys might be missing, but falls short when certainty is required. Conversely, the current behavior of TS caters to scenarios where assurance is needed.
Mods
This isn't a straightforward question with a definitive answer (TypeScript operates as intended), but I'll keep it unless close votes are received as it presents an interesting discussion on the behavior and rationale behind it.