The template in Typescript is constrained

Upon creating the HashMap interface:

export interface HashMap<K, V> {
    [name: K]: V;
}

I envisioned utilizing it in this manner:

const map: HashMap<String, String>;

Unfortunately, I encountered an error indicating that name must only be of type string or number.

How can I enforce constraints on the K template here?

Answer №1

When it comes to data types, avoid using primitive type names in uppercase. Instead of String, opt for string.

In TypeScript, the parameter type for an index signature must be either string, number, or symbol. The usage of symbol is limited to --target es2015 or higher.

If you want to restrict the key type to a subset of string values, a type alias declaration should be used instead of an interface.

export type HashMap<K extends string, V> = {
  [P in K]?: V;
}

The syntax [P in K] iterates through each string literal type within the specified set of strings represented by K.

This approach is beneficial as it allows us to limit the contents of the map by defining a union type with string literals.

For instance:

const map: HashMap<'firstName' | 'lastName', string> = {
  firstName: 'John', // OK
  nickname: 'Johnny' // Error
};

Essentially, the key type should be a string, but it can be constrained to specific strings or a defined set of strings using a union type.

In practice, the string union type often depends on another type.

For example:

interface Item {
  name: string;
  id: number;
}


interface PropertyMetadata {
  kind: 'data' | 'accessor';
}

type LazyItem = {
  [P in keyof Item]: PropertyDescriptor
};

keyof is a Type Operator that generates a type consisting of the property keys as string unions.


If utilizing an arbitrary key type bound by certain constraints, consider using an ES2015 Map object. Before the introduction of this type in JavaScript, achieving this mapping cleanly was challenging and string was predominantly used as a key type.

By leveraging an ES2015 map alongside TypeScript generics, one can approximate the desired functionality.

For example:

interface Category {
  name: string;
  products: Product[];
}

interface Product {
  name: string;
  category: Category;
}

const categoriesToProducts = new Map<Category, Product[]>();

declare function getProducts(): Product[];

const products = getProducts();

products.forEach(product => {
  const mapped = categoriesToProducts.get(product.category);
  if (mapped) {
    mapped.push(product);
  } 
  else {
    categoriesToProducts.add(product.category, [product]);
  }
});

Answer №2

When it comes to indexable types, the usual options for keys are limited to primitive types like string and number. So, there may not be much advantage in parameterizing on them, but let's explore it anyway.

type IndexableTypes<V> = {
    [s: string]: { [n: string]: V };
    number: { [n: number]: V };
}

type SN = { [n: number]: 'number', [s: string]: string };


type HashMap<K extends string | number, V> = IndexableTypes<V>[SN[K]];

const map: HashMap<string, string> = {}; // inferred as {[n: string]: string; }
const map1: HashMap<number, string> = {}; // inferred as  {[n: number]: string;}

const map2: HashMap<number, HashMap<string, number>> = {};
// inferred as  
{[n: number]: 
  {[n: string]: number;};
}

const map4: HashMap<boolean, {}> = {};
// error: Type 'boolean' does not satisfy the constraint 'string | number'.

Alternatively, if your runtime supports it, you could simply use the es2015 Map class:

type MyMap<K extends string | number, V> = Map<K, V>

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

Unfortunately, CORS is preventing me from sending a POST request through AJAX

I'm currently working on integrating an API into my website. I'm attempting to send a POST request with JSON data, but I keep encountering an error code when trying to make the request. Interestingly, sending the request using curl does not pose ...

Retrieve Gravatar image using JSON data

I am currently working on extracting data to show a user's Gravatar image. The JSON I have is as follows: . On line 34, there is 'uGava' which represents their gravatar URL. Ideally, it should be combined with / + uGava. Currently, I have ...

Guide to dynamically setting the index element with ngFor in Angular

When working with NgFor in Angular, I am interested in dynamically handling attributes using an index. I have a collection of properties/interfaces that look like this: vehicle1_Name, vehicle2_Name, vehicle3_Name vehicle4_Name, totalVehCount To achieve t ...

After a successful login, Angular will once again redirect the user to the login page

I successfully implemented the back-end using nest-js to handle authentication with mongo-db. Registration and login are working fine, but I face an issue after logging in with a successful result - when I refresh the page, it redirects me back to the logi ...

Supporting multiple types for matching object structures is a feature in Jest

I'm currently working on a test using jest to verify if an object key is either a string or a number. It seems like a basic task, but surprisingly there isn't much guidance in the documentation. Test Example: test('Checking asset structure ...

Using GitHub Actions to automatically publish a Typescript Package: A Step-by-Step Guide

Utilizing GitHub actions, I have automated the process of publishing my npm package whenever I push code to the master branch. However, I am facing an issue with my .gitignore file where I have excluded the /dist/ folder. As a result, when the code is push ...

`Scrolling through a horizontal menu with no scrollbar present`

Is there a way to create a horizontal menu that can scroll left or right without the scrollbar? I'm looking for a solution like adding on-screen arrow buttons or implementing mouseover functionality. What would be the best approach? div.scrollmenu ...

What is the best way to retrieve the value of an object based on its key?

I'm working on a function that returns another function, and I need some guidance: fn: <T extends Object>(key: keyof T) => (value: ???) => void I want the type of ??? to be determined by the type of instanceOfT[key]. For instance, in the ...

Executing a Javascript function post AJAX page loading

I'm not a coding expert, so I hope my question is still clear. Essentially, I have an index.php page with filters (by project, year, month) that send variables to filterData.php after clicking submit. These variables are then used in SQL statements t ...

Update the HTML table by changing the first cell in the first row to span multiple

I currently have a table structured like this: <table> <thead> <tr> <th></th> <th>Col1</th> <th>Col2</th> <th>Col3</th> <th>Col4& ...

Loading jQuery via JavaScript in the head section of the HTML document

I'm currently developing an application using jQuery and jQuery Mobile. I have a script in the head section that dynamically loads both libraries. However, my body contains a script that relies on jQuery ($) and is unable to access it because it loads ...

The variable 'BlogPost' has already been declared within the block scope and cannot be redeclared

When working with Typescript and NextJS, I encountered the following Typescript errors in both my api.tsx and blogPost.tsx files: Error: Cannot redeclare block-scoped variable 'BlogPost'.ts(2451) api.tsx(3,7): 'BlogPost' was also dec ...

Having issues with templateURL not working in AngularJS routeProvider

I've been experimenting with ngRoute and I'm facing an issue with templateURL not working as expected. The functionality works perfectly except for when I attempt to use templateURL. Here are the 4 files involved: test.html <p>{{test}}&l ...

guide on utilizing the Thingspeak API with jQuery AJAX

I am attempting to call the Thingspeak REST API and display the value on an HTML page. However, I am encountering an issue where the value is showing as undefined. Here is the code snippet I am using: <script type="text/javascript"> $(document) ...

The variable $http request is not defined in this context

When I perform a GET request on my backend to fetch JSON data, I am encountering an issue with storing a portion of the data in a variable for later use. Despite following similar steps in another controller where it worked fine, the variable always ends u ...

What is the best way to modify and execute js and css (less) files dynamically using ajax and jQuery?

Whenever I click a button, an ajax call is triggered to load various html code into a div with the id 'main'. While displaying the html content is not an issue, integrating and applying css and js code to my current page has proven to be challeng ...

Inaccurate Feedback on Maquest Direction Route API

Currently, I am in the process of implementing the MapQuest API Direction Routing function on my website. However, upon submitting a request to the API, it is returning inaccurate routing points between two destinations. Here is the request form that I am ...

Retrieve four distinct values using only a single text box input and display them individually on separate lines

Is there a way to receive four input values from the user using just one textbox and display them on separate lines? function collectData() { var userInput, i; var textOutput = " "; for (i = 0; i <= 3; i++) { userInput = document.g ...

Is it possible for Typescript to allow extracted interfaces while excluding properties from another interface?

I've been searching for information on the specific features of this. Despite my efforts on Google, I have been unable to find the details. Any help would be greatly appreciated! interface Numbers { number: number; number2: number; number ...

What is the best way to compare keys and values in a JSON object in Lodash when one object contains additional keys?

Consider the following two JSON objects: var obj1 = {a: "apple", b: "banana", c: "carrot"} var obj2 = {a: "apple", e: “egg” b: "banana", c: "carrot", d: "dog"} I'm looking for ...