What is the reason behind the absence of possibly undefined value in the return from a generic dictionary type in Types

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.

Answer №1

When you use the syntax

type Dictionary = { [id: string]: Foo }
, you are essentially allowing any string to be a key for this type. The compiler will treat all strings as valid keys, without making any assumptions about their validity or uniqueness, ensuring that any property accessor will return a value of type Foo.

If you wish to restrict the keys in your object to only 'one' and 'two', you can explicitly define these properties within your dictionary:

type Dictionary = {
  one: Foo;
  two: Foo;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for retrieving Angular routing data from external sources outside of an Angular application

Is there a way to automatically generate a sitemap for each module during build time? The project structure is as follows: - cli - client -- Module A -- Routing A -- Module B -- Routing B -- Module C -- Routing C - server I am ...

Capturing a webpage through Record RTC integration with Angular

I am looking to record a specific section of the page as a video with audio. For example, when a user enters the page, I want it to automatically start recording the activities in that particular area (similar to how iframe videos work). The recording sh ...

Issue with Angular 5 Application - "Implementations cannot be declared in ambient contexts"

Recently in my Angular 5 project, I started encountering an issue with the observable object. Everything was working smoothly until about a week ago when I began receiving this error message: ERROR in node_modules/rxjs/Observable.d.ts(20,31): error TS1183 ...

Having trouble creating two bar charts with different data sets in ng2-charts using Angular 8

I attempted to display two charts simultaneously on a single page by utilizing the following code in HTTP: <div class="chk-block-content"> <canvas height="100" width="500" baseChart [datasets]="barChartData" [labels]="barChartLabels" [options ...

Transforming an object's type into an array of different types

Looking to create an array of types based on object properties: type T = { a: number | string; b: string | number; c: number; d: boolean; }; Desired Output: [number | string, string | number, number, boolean] Intending to use this as a ...

How can I customize a Vue component slot in Storybook 8.0.6 using Vue 3.4 and Typescript to display various subcomponents within a story?

Incorporating a variety of sub-components into my Vue 3 component based on context is proving to be a challenge. Utilizing slots seems to be the solution in Vue 3, but I'm struggling to make it work within Storybook 8, which I'm using to showcase ...

Iterate through each item in an object using Angular

I attempted to utilize a forEach loop, but it's indicating that it's undefined for some reason. Here is my code snippet: var array: MoneyDTO[] = prices array.forEach(function (money: MoneyDTO) { if (money.currency == 'QTW& ...

The module 'SharedModule' has imported an unexpected value of 'undefined'

When working with an Angular application, I want to be able to use the same component multiple times. The component that needs to be reused is called DynamicFormBuilderComponent, which is part of the DynamicFormModule. Since the application follows a lib ...

What is causing my React-Testing Library queries to not work at all?

In my current project, I am using Jest along with Testing-Library to create UI unit tests. One issue that I encountered was that the components were not rendering on the DOM. After some investigation, I found that the main culprit was a component called & ...

Challenges with compiling TypeScript in Angular2 Form components

In Angular2, I have created a form with the following structure: this.form = this._formBuilder.group({ password: ['',Validators.required], passwordRepeat: ['',Validators.required] }); The form is defined as: p ...

Troubleshooting Fontawesome Icons in React using TypeScript and state values

I am facing an issue while trying to dynamically change the type of an icon using state update. The error message I received is: Type 'string' is not assignable to type 'IconPrefix'. Below is a snippet of my code: import { useState } ...

Encountering deployment problems with React and TypeScript involving router on Github Pages

After successfully running locally, I encountered a 404 error when deploying the website using "npm run deploy." My application is built with React and TypeScript, utilizing react-router-dom BrowserRouter for navigation between pages. I've spent 7 h ...

What are the distinctions between using getStaticPaths + getStaticProps and useRouter in NextJS?

I'm currently diving into the world of NextJS and finding myself puzzled by the distinctions between getStaticProps & getStaticPaths compared to utilizing useRouter().query. At this point, it appears to me that both methods serve a similar purpos ...

Tips on integrating library project into angular

I've been encountering a challenge with angular library projects lately. I'm trying to style a project using a global stylesheet while ensuring that the styles only affect the specific project itself. My attempted solution was to create a compone ...

React Typescript Issue: Antd Checkbox value not updating correctly

Hey there, I'm having trouble filling the Checkbox value with my state data as it's not accepting it. Here is the Checkbox Code: <Form.Item label="Active"> <Checkbox value={this.state.selectedRecord.active} /> </Form ...

What is the best way to implement the usehook function in a function using react and typescript?

When attempting to utilize the useHook in a function, an error message appears stating that useHook is being used within a function which is neither a React function component nor a custom React hook. I encountered an error when trying to use the useOpen ...

Achieving intellisense functionality in TypeScript without the use of classes

Just dipped my toes into TypeScript, attempting to convert this basic JavaScript code to TypeScript. Here is the JavaScript code snippet: Item = {} Item.buy = function (id) {} Item.sell = function (id) {} I prefer not to use classes and would like to ut ...

Experimenting with TypeScript Single File Component to test vue3's computed properties

Currently, I am in the process of creating a test using vitest to validate a computed property within a vue3 component that is implemented with script setup. Let's consider a straightforward component: // simple.vue <script lang="ts" set ...

Tips for maintaining the selected state of a row using Typescript and the option tag

Here is the code for a dropdown: <div class="col-md-6"> <label for="inertiaStart" style="float: left; width: 17%;">Inertia Start</label> <select ng-model="selectedShiftChannel" style="float: left; width: 70%;height: 26 ...

Guide on setting up a route in Next.js

Recently, I developed a simple feature that enables users to switch between languages on a webpage by adding the language code directly after the URL - i18n-next. Here's a snippet of how it functions: const [languages, ] = React.useState([{ langua ...