Converting an array of objects into a TypeScript dictionary with IDs as the key mapping

Is there a way to provide type hints for better autocompletion when accessing keys like dictionary.Germany from the following data and types?

type Entry = {
  tld: string;
  name: string;
  population: number;
};

const data: Entry[] = [
  {tld: 'de', name: 'Germany', population: 83623528},
  {tld: 'at', name: 'Austria', population: 8975552},
  {tld: 'ch', name: 'Switzerland', population: 8616571}
];

let dictionary = Object.fromEntries(data.map(item => [item.name, item]));

(The resulting type of dictionary is now { [key: string]: Entry }, such as

{ Germany: {tld: 'de', …}, …}
.)


To summarize, my goals are:

  1. To have the data stored in both a list of Entry objects…
  2. …and an object mapping with the Entry objects.
  3. I want to specify the name of an object only once, either as name or key.
  4. The IDE should recognize the keys in the dictionary, for example in WebStorm.
  5. dictionary.Germany === data[0]

Answer №1

Your code is currently facing multiple issues that are preventing it from functioning correctly.


Firstly, by annotating the type of data as Entry[], you are instructing the compiler to discard any specific information about the initializing array literal. This means that only the information that name is a string is retained within the Entry. However, if you want to maintain the string literal types of the name properties, consider using a const assertion instead of annotating.

To ensure that the initializer matches Entry[], you can utilize the satisfies operator like this:

const data = [
  { tld: 'de', name: 'Germany', population: 83623528 },
  { tld: 'at', name: 'Austria', population: 8975552 },
  { tld: 'ch', name: 'Switzerland', population: 8616571 }
] as const satisfies Entry[];

Subsequently, although TypeScript now understands "Germany", "Austria", and "Switzerland", it lacks knowledge regarding the keys produced by the Object.fromEntries() method. The current type definition specifies an output with a string index signature.

If you wish to define a more specific call signature yourself, you can do so in your code base and merge it in accordingly:

// declare global {
interface ObjectConstructor {
  fromEntries<E extends readonly [PropertyKey, any][]>(
    entries: E
  ): { [T in E[number] as T[0]]: T[1] };
}
// }

This implementation iterates over the elements of entries and uses key remapping to generate an object type.


Once these adjustments have been made, your dictionary will still contain a union of const-asserted values as its property value type. To correct this and ensure only Entry is present, use item satifies Entry as Entry.

Finally, here is the refined version:

let dictionary =
  Object.fromEntries(data.map(item => [item.name, item satisfies Entry as Entry]));
/* let dictionary: {
    Germany: Entry;
    Austria: Entry;
    Switzerland: Entry;
} */

You have now achieved the intended behavior for your code. Whether the modifications are worth it depends on your specific use cases.

Playground link to code

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

Expanding the Mui Typescript breakpoints within a TypeScript environment

Encountering a typescript error when attempting to utilize custom names for breakpoint values: Type '{ mobile: number; tablet: number; desktop: number;}' is not compatible with type '{ xs: number; sm: number; md: number; lg: number; xl: numb ...

Issue with importing and exporting external types causing failures in Jest unit tests for Vue 2

I am in the process of creating a package that will contain various types, enums, consts, and interfaces that I frequently use across different projects. To achieve this, I have set up a main.ts file where I have consolidated all the exports and specified ...

Error: The argument provided is of type 'unknown', which cannot be assigned to a parameter of type 'string'. This issue arose when attempting to utilize JSON.parse in a TypeScript implementation

I'm currently converting this code from Node.js to TypeScript and encountering the following issue const Path:string = "../PathtoJson.json"; export class ClassName { name:string; constructor(name:string) { this.name = name; } ...

Encountered a problem with regular expressions in Angular 2 - a Module parse error due to an octal literal in strict mode

Greetings, I have encountered an issue with a regular expression in my environment.ts file. export const environment = { passwordPolicy: "^(?!.*(.)\1\1)(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}.*$" }; Unfortunately, whe ...

Tips for efficiently awaiting outcomes from numerous asynchronous procedures enclosed within a for loop?

I am currently working on a search algorithm that goes through 3 different databases and displays the results. The basic structure of the code is as follows: for(type in ["player", "team", "event"]){ this.searchService.getSearchResult(type).toPromise ...

Deducing the return type of asynchronously generated functions

My expectation is to automatically determine the return type of async functions when they are yielded as values from a generator. In the following example, the inference of the type of the yielded async functions appears to work correctly (detected as () ...

What is the best way to attach events to buttons using typescript?

Where should I attach events to buttons, input fields, etc.? I want to keep as much JS/jQuery separate from my view as possible. Currently, this is how I approach it: In my view: @Scripts.Render("~/Scripts/Application/Currency/CurrencyExchangeRateCreate ...

Error: Typings: Invalid syntax - Unexpected symbol =>

Every time I run a typings command, I encounter the following error: AppData\Roaming\npm\node_modules\typings\node_modules\strip-bom\index.js:2 module.exports = x => { ^^ SyntaxError: Unexpected tok ...

Issue with TypeScript Generics: The operand on the left side of the arithmetic operation must be of type 'any', 'number', or 'bigint'

I seem to be encountering an error that I can't quite decipher. Even though I've clearly set the type of first as a number, the code still doesn't seem to work properly. Can someone provide insights on how to fix this issue? function divide& ...

A critical error has occurred: RangeError - The maximum call stack size has been exceeded while trying to

After attempting to filter a list of titles using Ng2SearchPipeModule, I imported the module in app.module.ts and created a new searchbar component. searchbar.component.ts import { FirebaseService } from './../../firebase.service'; import { Ang ...

Using the <head> or <script> tag within my custom AngularJS2 component in ng2

When I first initiate index.html in AngularJS2, the structure looks something like this: <!doctype html> <html> <head> <title>demo</title> <meta name="viewport" content="width=device-width, initial-scal ...

Difficulty Determining Literal Types that Expand a Union of Basic Data Types

Below are the components and function I am working with: interface ILabel<T> { readonly label: string; readonly key: T } interface IProps<T> { readonly labels: Array<ILabel<T>>; readonly defaultValue: T; readonly onChange ...

What is the correct way to set up a custom class instance with specific parameters at the top level?

Is it possible to utilize the defineString(), defineInt, ... functions within a top-level custom class constructor? defineString() returns a StringParam object which has a value() method. I am looking to use parameterized configuration to initialize an in ...

Update the router URL without switching pages, yet still record it in the browser history

One of the features on my search page allows users to perform searches and view results. Initially, I faced a challenge in updating the router URL without navigating, but I managed to overcome this by utilizing the "Location" feature. In my ngOnInit meth ...

How to use attributes in Angular 2 when initializing a class constructor

Is there a way to transfer attributes from a parent component to the constructor of their child components in Angular 2? The process is halfway solved, with attributes being successfully passed to the view. /client/app.ts import {Component, View, bootst ...

The Angular tag <mat-expansion-panel-header> fails to load

Every time I incorporate the mat-expansion-panel-header tag in my HTML, an error pops up in the console. Referencing the basic expansion panel example from here. ERROR TypeError: Cannot read property 'pipe' of undefined at new MatExpansionPanel ...

Is it possible to apply JavaScript object destructuring but make changes to certain values before assigning them to a new object?

After receiving movie data from an api, I am currently manually creating a new object with a subset of properties and modified values. Is there a more efficient way to achieve this using javascript/typescript object destructuring syntax? I specifically wa ...

How can we include additional types for external npm packages in a React TypeScript project?

Recently, I encountered an issue while using the react-microsoft-login package from npm. I included a button in the children property and received a typescript error stating that "property 'children' does not exist on type 'intrinsicattribut ...

Restricting types does not appear to be effective when it comes to properties that are related

I am working with a specific type that looks like this: type Props = { type: 'foo'; value: string; } | { type: 'baz'; value: number; }; However, when using a switch statement with the type property in TypeScript, the program in ...

Why is my npm installation generating an ERESOLVE error specifically linked to karma-jasmine-html-reporter?

Could someone help me understand this dependency error I encountered while running npm install and provide guidance on how to resolve such errors? View Error Screenshot I am currently using Angular 16, the latest stable version. npm ERR! code ERESOLVE ...