Limitations of Typescript's Index Signature Templates

Currently, I have some Typescript Interfaces with repeated and similar fields. Here's an example:

interface Foo {
   person1Name: string;
   person1Address: string;
   person2Name: string;
   person2Address: string;
   category: string;
   department: string;
}

To streamline this structure, I attempted to utilize Typescript index signature templates by using [key: 'person${number}Name']. However, my goal is to limit the number of allowable keys for 'person' properties to 5, which isn't functioning as intended.

type AllowedIndex = 1 | 2 | 3 | 4 | 5;

interface Foo {
   [key: `person${AllowedIndex}Name`]: string;
   [key: `person${AllowedIndex}Address`]: string;
   category: string;
   department: string;
}

Upon implementing the above code, I encountered the error message

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.ts(1337)
.

Could anyone suggest a solution to achieve what I'm attempting to do? Or am I destined to repeat the keys in their current format?

For further reference, here is a TS Playground Link showcasing my attempts.

Answer №1

A new feature was introduced in TypeScript 4.4 that allows the use of pattern or placeholder template literal types in index signatures. However, the usage of `person${AllowedIndex}Name` is not a template literal type but rather a union of string literal types determined by TypeScript. This union cannot be used in an index signature.

Instead of using index signatures, you can utilize mapped types to create an object type with keys as a union of string literals. For required keys, you can define it like this: {[K in `person${AllowedIndex}Name`]: string}. If you want optional keys, you can declare it as follows: {[K in `person${AllowedIndex}Name`]?: string}. Another approach would be to combine Partial and Record utility types, such as Partial>.

Mapped types are standalone types, and properties cannot be added to them. To merge multiple mapped types, you can use intersection types. If you wish to have an interface, you can utilize interface extension to combine parent mapped types along with additional properties. Here is an example:

type AllowedIndex = 1 | 2 | 3 | 4 | 5;

interface Foo extends
   Partial<Record<`person${AllowedIndex}Name`, string>>,
   Partial<Record<`person${AllowedIndex}Address`, string>> {
   category: string;
   department: string;
}

To validate the behavior, inspect the ExpandedFoo type:

type ExpandedFoo = {[K in keyof Foo]: Foo[K]};
/* type ExpandedFoo = {
    category: string;
    department: string;
    person1Name?: string | undefined;
    person2Name?: string | undefined;
    person3Name?: string | undefined;
    person4Name?: string | undefined;
    person5Name?: string | undefined;
    person1Address?: string | undefined;
    person2Address?: string | undefined;
    person3Address?: string | undefined;
    person4Address?: string | undefined;
    person5Address?: string | undefined;
} */

For more details and to experiment with the code, check out this Playground link to code.

Answer №2

To implement this concept, you can use the following code snippet:

type AllowedIndex = 1 | 2 | 3 | 4 | 5;

type PersonDetails = 
   { [TKey in `person${AllowedIndex}Name`]: string } &
   { [TKey in `person${AllowedIndex}Address`]: string };

interface Bar extends PersonDetails {
   group: string;
   division: string;
}

For a live demonstration, check out TS Playground link.

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

Utilizing Anglar 16's MatTable trackBy feature on FormGroup for identifying unaltered fields

In my application, I am working with a MatTable that has a datasource consisting of AbstractControls (FormGroups) to create an editable table. At the end of each row, there are action buttons for saving or deleting the elements. My goal is to implement tr ...

What is preventing me from sending back an array of objects with an async function?

Working with node.js, my goal is to retrieve a list of bid and ask prices from a trading exchange website within an async function. While I can successfully log the object data using console.info() within the foreach statement on each iteration, when attem ...

Check for the presence of an Outlook add-in within a web application

I'm having trouble determining whether my hosted web application is being accessed through a browser or from within the Outlook 2013/2016 client. I have developed a web application that offers different functionalities depending on whether it is acce ...

Encountering an error in React js where it is unable to read property "0" when making an API call

I am facing an issue with accessing data from the fetch function in my project. The goal is to pass the data from the action to the reducer. The API is being called using a fetch function, which returns a promise. So, the API call is made separately and th ...

The Error Message: "404 Not Found - Your Form Submission Could Not

Greetings, I am currently immersing myself in learning NodeJS and the Express framework. However, I have encountered an issue when attempting to submit a form that is supposed to go to the '/users/register' URL. It appears that my app.js is unabl ...

Is there a way to generate random numbers that will create a chart with a general upward trend?

Are you looking for a solution to generate random numbers for charts that trend upwards and to the right? I am currently utilizing a JavaScript charting engine, so I will require numbers in JSON format eventually. However, I am open to alternative methods ...

What is the best way to access an error's body in order to retrieve additional error message details when using the forge-api with nodejs?

I'm struggling to retrieve the body content when an error is returned from the API request. I've attempted creating a bucket with uppercase letters, but all I receive is an error object with statusCode = "400" and statusMessage = "BAD REQUEST". ...

Executing a JavaScript function using document.write()

When I try to click on the SWF part1 links within the document.write() function in order to call the openswf function, nothing happens. Here is my code: <html> <a href="#" onclick="Popup();">show popup</a> <script> functio ...

React - Struggling to render an image received as a prop within a React component

Just starting out with React. I'm trying to figure out how to properly display an image from the props of my CheckoutProduct component inside an image HTML tag. Image displaying the Product item but failing to do so. Here's the code snippet: i ...

TypeScript incorporates a variety of @types versions for react

I made changes to my compilerOptions within the tsconfig.json file with the specified paths "paths": { "react": ["node_modules/@types/react"], "@types/react": ["node_modules/@types/react"] } However, I noticed that @types/react-router is using its o ...

What is the best way to assign string values from an array in data() to the src attribute of an image element?

I've been working on a feature where the <div.box> element appears based on the user's input through a form. In this project, I'm using vue3 and v-for to iterate over an array 'images' that contains URL strings linking to ima ...

What is the most efficient way to extract dynamically generated JavaScript content from a webpage?

Currently, my method involves using selenium alongside PhantomJS to scrape dynamic content generated by JavaScript on a website. Although it provides me with the desired results, the process is quite slow as I have to wait for the entire page to load befor ...

Displaying stack traces in a request using HttpInterceptor in Angular 9

I have created an HttpInterceptor and I would like to print the stack trace of the functions involved in making the request for development purposes: import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, ...

I. Discovering the Step-by-Step Guide on Retrieving User Information from Facebook Upon Generating App

I have set up a Facebook login for user registration on my website. However, I am only able to retrieve the profile name and user ID from Facebook. How can I access the email and other user information? Here is the data I am currently receiving from Faceb ...

Could it be that the TypeScript definitions for MongoDB are not functioning properly?

Hello everyone, I'm facing an issue with getting MongoDB to work in my Angular 4 project. In my code, I have the db object as a client of the MongoClient class: MongoClient.connect('mongodb://localhost:27017/test', (err, client) => { ...

Hi there, I am facing an issue with the modal window. Whenever I click on the image, it always displays the same image instead of the right one. Can you please suggest

The window opens, but instead of showing the image I clicked on, it displays the last image in the table. How can I fix this error? Below are my modal and map templates, which are the parent components of the modal. Thank you. <template> <di ...

Transferring multiple sets of data from Firestore to another collection proves to be ineffective

Currently, I have four separate collections stored in my firestore database: EnglishQuestions MathQuestions ScienceQuestions DzongkhaQuestions My goal is to consolidate all the data from these individual collections and organize it under one collection f ...

What is the method for determining someone's age?

I am trying to extract the date from a field called "DatePicker" and then enter that date into the field labeled "NumericTextBox" Thank you <div> <sq8:DatePicker runat="server" ID="dateDatePicker"> <ClientEvents OnDateSelected="get_curr ...

Adding an item to the EmberJS Data Store

Is there a way to add a record to the Ember Data Store without relying on the adapter? Whenever I use this.store.push({type: type, data: data}), the store always sets the hasDirtyAttributes flag to true. To work around this issue, I have been using this. ...

Could someone assist me in identifying the error or mistake?

For my project, I have implemented client and server sign-in & sign-up functionalities. However, after fetching the register API from the frontend, it is displaying an error message "please fill all fields" even though I have provided validation for al ...