Creating object interfaces in TypeScript dynamically based on function parameters

I'm currently working on a small project that involves creating lists of products in a certain format. For example:

let products = createList(
   new Product('product1'),
   new Product('product2')
)

When it comes to accessing a specific product, such as product1, I typically use products.list['product1'] and then proceed with further actions. Each product within the list is designed with a name property that corresponds to the first parameter passed in the constructor. Right now, products.list utilizes a simple string index signature. What I really want to achieve is for intellisense to suggest properties like product1, product2, and so forth for the list object. This functionality would be akin to how express handles request parameters:

https://i.stack.imgur.com/d0p4d.png

In my quest to address this issue, I delved into express' type declarations and extensively reviewed TypeScript documentation on utility types, particularly focusing on the Record type. Despite this effort, I still couldn't quite grasp the solution. It's possible that I may have been searching for the wrong information. Therefore, I would greatly appreciate any guidance in directing me towards the proper documentation.

Answer №1

In order for this approach to be successful, the Product class must have a generic defined for the string literal type of the name property. Without this, the compiler would only recognize the name property as a string, resulting in the loss of strong typing possibilities for the keys in the output of createList(). Assuming Product is structured like below:

class Product<K extends string> {
    constructor(public name: K) { }
    size = 123;
    cost = 456;
}

(Note: This definition simplifies things quite a bit, and currently, Product<K> is covariant in K. This means that a union of different Product types can be assigned to a single Product type. For example,

Product<"product1"> | Product<"product2">
could be assigned to
Product<"product1" | "product2">
. The code relies on this assignment. If Product<K> were more complex and became invariant in K, this union assignment would fail. It's possible to work around this issue by adjusting the code, but we won't delve into that further here.)

Now, let's define createList():

function createList<K extends string>(...products: Product<K>[]) {
    return products.reduce(
      (accumulator, product) => ({ ...accumulator, [product.name]: product }), 
      {} as { [P in K]: Product<P> }
    );
}

The function takes multiple arguments of type Product<K> for some K, which will comprise the union of all the name properties from individual Product instances. We use the reduce() method of the products array to construct the desired object, assigning each product p with a property using key p.name and value p.

The return type of createList() is {[P in K]: Product<P>}, a mapped type consisting of one property P for every member within the K union. Each property with key P is assigned the type Product<P>. Since the compiler may not fully comprehend or infer that reduce() generates a value of this type, a type assertion is used to assume that the initial empty value passed has that type.

Let's verify if it works:

let products = createList(
    new Product('product1'),
    new Product('product2')
)

/*let products: {
    product1: Product<"product1">;
    product2: Product<"product2">;
} */

Seems like it's working!

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

Why is it possible to assign a variable holding a char array to a pointer in C, but attempting to take the address of that same pointer is not allowed?

Let's examine the following code snippet: char stringy[] = "There's too much confusion, I can't get no relief!"; char *pStringy; pStringy = stringy; It's interesting how this code compiles without any errors. Although stringy ...

Using React with Typescript to display components generated from the `map` function

In my scenario, I have retrieved data from a JSON file and am also utilizing a utility function that selects 5 random entities from an object Array. This particular array contains 30 entities. Struggling with displaying the 5 random jockeys stored in the ...

React with Typescript - Type discrepancies found in Third Party Library

Recently, I encountered a scenario where I had a third-party library exporting a React Component in a certain way: // Code from the third party library that I cannot alter export default class MyIcon extends React.Component { ... }; MyIcon.propTypes = { ...

Is it considered an anti-pattern in TypeScript to utilize BehaviorSubject for class or object properties?

When working with Angular, I find myself frequently displaying members of classes in an Angular HTML template. These classes often share common members structured like this: class Foo { bar: string; bas: Date; } There are instances where I need to ...

Storing a byte array in a local file using JavaScript: A Step-by-Step Guide

Recently, I encountered an issue while working with an openssl certificate. Specifically, when I tried to download the certificate from my API, it returned byte arrays that I needed to convert to a PEM file in order to access them through another API. The ...

The Angular test spy is failing to be invoked

Having trouble setting up my Angular test correctly. The issue seems to be with my spy not functioning as expected. I'm new to Angular and still learning how to write tests. This is for my first Angular app using the latest version of CLI 7.x, which i ...

Is it possible to create a function that only accepts a union type if it includes a specific subtype within it?

Is there a way to define the function processArray so that it can handle arrays of type string[], without explicitly mentioning individual types like type1 or type2? For instance, could we indicate this by using arr: type1 | type2? The objective here is t ...

Angular 11 now includes the ability to implement lazy loading for modules

Here is the configuration of my app-routing.module.ts: const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: '', canActivate: [AuthGuard], component: HomeComponent, children ...

Angular 6 - The requested resource does not have the necessary 'Access-Control-Allow-Origin' header

I am currently working on an Angular 6 project that includes a service pointing to server.js. Angular is running on port: 4200 and Server.js is running on port: 3000. However, when I try to access the service at http://localhost:3000/api/posts (the locat ...

Struggling with importing aliases in TypeScript for shadcn-ui library

I am facing a challenge with resolving TypeScript path aliases in my project. I have set up the tsconfig.json file to include path aliases using the "baseUrl" and "paths" configurations, but alias imports are not functioning as intended. My goal is to imp ...

Angular 6: Issues with API Get Method not executing when an integer value is passed with an empty string

I'm experiencing an issue in my angular application when trying to call an API method from angular. The method requires two parameters - one integer value and one string value, which is optional. Below is the code snippet in Typescript: let id:numbe ...

Exploring TypeScript: Implementing a runtime data mapping in place of an interface

Take a look at this code snippet that defines two command handlers for a server: import { plainToClass } from "class-transformer"; enum Command { COMMAND_1, COMMAND_2, } class Command1Data { foo1!: string } class Command2Data { foo2!: ...

The process of running npx create-react-app with a specific name suddenly halts at a particular stage

Throughout my experience, I have never encountered this particular issue with the reliable old create-react-app However, on this occasion, I decided to use npx create-react-app to initiate a new react app. Below is a screenshot depicting the progress o ...

Add integer to an array of strings

Currently, I am utilizing an autocomplete feature and aiming to save the IDs of the selected users. My goal is to store these IDs in a string array, ensuring that all values are unique with no duplicates. I have attempted to push and convert the values u ...

The revalidation process in react-hook-form doesn't seem to initiate

Stumbled upon a code example here Decided to fork a sandbox version (original had bugs and errors) I am trying to implement custom validation callbacks for each form input element by providing options in the register function, but the validate is only tr ...

Changing the function to operate asynchronously

How can I convert the following code into an asynchronous function? It is currently returning referralUrl as undefined: controller async createReferralUrls() { this.referralUrl = await this.referralService.generateReferralUrl(this.userData.referral ...

Type of Data for Material UI's Selection Component

In my code, I am utilizing Material UI's Select component, which functions as a drop-down menu. Here is an example of how I am using it: const [criteria, setCriteria] = useState(''); ... let ShowUsers = () => { console.log('Wor ...

TypeScript - Issue with generic function's return type

There exists a feature in typescript known as ReturnType<TFunction> that enables one to deduce the return type of a specific function, like this function arrayOf(item: string): string[] { return [item] } Nevertheless, I am encountering difficulti ...

Angular has the ability to round numbers to the nearest integer using a pipe

How do we round a number to the nearest dollar or integer? For example, rounding 2729999.61 would result in 2730000. Is there a method in Angular template that can achieve this using the number pipe? Such as using | number or | number : '1.2-2' ...

Images with spaces in their names are failing to load in React Native

I am currently trying to utilize an image within my react native application. At this point, my code is very minimal. The following code snippet is all I have for now: import React from 'react'; import { ScrollView, View, Text } from 'reac ...