Is it possible in Typescript to dynamically adjust the type based on the value of an attribute?

My goal is to dynamically change the object type based on the value of the "key" attribute.

I have various types such as AElement, BElement, ..., ZElement. Each element shares a common attribute called "name". Therefore, the type should be determined based on this attribute.

For instance:

// Elements
type AElement = {name:"a"; propA: string;}
type BElement = {name:"b"; propB: number;}
type CElement = {name:"c"; propC: string;}
// ...
type ZElement = {name:"z"; propZ: string; propZZ: number;}

// Map interface
interface ElementsMap {
    "a": AElement;
    "b": BElement;
    "c": CElement;
    //...
    "z": ZElement
}

// Custom type
type Elem<K extends keyof ElementsMap = keyof ElementsMap> = ElementsMap[K];

// Usage
let elements:Elem[] = [{
    name: "a",
    propA: "123",
},{
    name: "c",
    propC: "321",
},{
    name: "z",
    propZ: "123",
    propZZ: 123,
}];

// Test
let test1 = elements[2];
let test2: Elem = {
    name: "a",
    propA: "123",
}

When I use Element[], I expect each type to correspond to the key attribute "name" and utilize the specific props of that type. However, the variables test1 and test2 are currently of type:

 AElement | BElement | CElement | ZElement
for each one, whereas I anticipate ZElement and AElement.

Answer №1

Regrettably, this issue is inherent to the nature of "Arrays" and functions as designed; when indexing an array at a specific index, it will always return the Arrays type even if that type is part of a union.

Regardless of the value stored at that index, it will consistently return the Arrays type.

Only tuples have the ability to be indexed at a key to retrieve the type associated with that key, as they have a known length at runtime unlike Arrays.

For instance, consider the following code snippet:

const testTuple: [AElement, CElement, ZElement] = [{
    name: "a",
    propA: "123",
},{
    name: "c",
    propC: "321",
},{
    name: "z",
    propZ: "123",
    propZZ: 123,
}];
const TestTypeAtIndex = testTuple[0] // Returns AElement instead of a union.

There are various ways to work around this limitation - feel free to leave a comment if you would like to learn more. However, the simplest solution is to implement a user-defined typeguard. While more complex workarounds offer improved functionality, they require a more intricate implementation.

// Using it as an array.
const testTuple: Elem[] = [{
    name: "a",
    propA: "123",
},{
    name: "c",
    propC: "321",
},{
    name: "z",
    propZ: "123",
    propZZ: 123,
}];

const isAElement = (ele: ElementsMap[keyof ElementsMap]): ele is AElement => {
    return "propA" in ele;
}

const testTypeAtIndex = testTuple[0] // Returns a union of all elements
if(isAElement(testTypeAtIndex)) {
    const newTestType = testTypeAtIndex; // Within this block, it is exclusively AElement due to the typeguard.
}

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

Sending parameters in GraphQL with Typescript results in an empty set of curly braces being returned

I am new to learning GraphQL with Typescript and I am trying to pass an argument in a GraphQL function to return something dynamically. I have been struggling with this issue for the past hour and could not find any solutions. Here are the relevant code sn ...

Indicate the location of tsconfig.json file when setting up Cypress

Having trouble integrating Cypress with Typescript? I've encountered an issue where Cypress is unable to locate the tsconfig.json file I created for it. My preference is to organize my project with a custom directory structure, keeping configuration f ...

The element is implicitly assigned an 'any' type as the expression of type 'any' cannot be used to index a type with createStyles

My stylesheet looks like this: const badgeStyle = createStyles({ badge: { borderRadius: "12px", padding: "5px 12px", textTransform: "uppercase", fontSize: "10px", fontWeight: 700, lineHeight ...

Upon updating my application from Angular 14 to 16, I encountered an overwhelming number of errors within the npm packages I had incorporated

After upgrading my angular application from v14 to v16, I encountered numerous peer dependencies issues, which led me to use the --force flag for the upgrade process. However, upon compiling, I am now faced with a multitude of errors as depicted in the scr ...

After triggering an action, I am eager to make a selection from the store

To accomplish my task, I must first select from the store and verify if there is no data available. If no data is found, I need to dispatch an action and then re-select from the store once again. Here is the code snippet that I am currently using: t ...

Utilizing a monorepo approach enables the inclusion of all *.d.ts files

Scenario: In our monorepo, we have 2 workspaces: foo and bar. foo contains the following files: src/file.ts src/@types/baz.d.ts The bar workspace is importing @monorepo/foo/src/file. While type-checking works for the foo workspace, it does not work fo ...

Triggering React State Changes to Render on Second Click

After much trial and error, I am still unable to solve this puzzle. Despite successfully retrieving a list from an API, parsing it, and pushing it into another array in my state, the render does not reflect the data properly. The logic seems sound as per ...

Substitute a value in a list with a distinctive identification code

I have a list of dailyEntries. Each entry has a unique identifier called id. I am given an external dailyEntry that I want to use to replace the existing one in the array. To achieve this, I can use the following code: this.dailyEntries = this.dailyEntri ...

New Requirement for Angular Service: Subclass Constructor Must Be Provided or Unable to Resolve all Parameters for ClassName (?)

During a recent project, I encountered an issue while working on several services that all extend a base Service class. The base class requires a constructor parameter of HttpClient. When setting up the subclass with autocomplete, I noticed that their con ...

The type 'TaskListProps[]' cannot be assigned to type 'TaskListProps'

I'm struggling with handling types in my TypeScript application, especially with the TaskListProps interface. export default interface TaskListProps { tasks: [ { list_id: string; title: string; description: string; status ...

How to pre-fill 3 formgroups when initializing in Angular 2?

I currently have this code that allows users to add a new set of fields. However, I am looking to have the component render out 3 sets of data upon initialization. Currently, one set of fields is generated using the initRateRow function. How can I modify ...

Steps for building a personalized pipe to paginate data within Angular

Creating a custom pagination pipe in Angular to filter data and display 100 records per page. For example, page 1 shows records 0-99, page 2 shows records 100-199, and so on. The data.json file contains an array with 1300 objects. Sample data: https://pas ...

Hold off on addressing the nested loops within a TypeScript subscription

Goal: Ensure all nested loops complete processing before returning final value. Problem: Final value returned prematurely, before completion of loop processing. In the code snippet below, I am sending paramListToComplete to a data service for creating a ...

Mastering the proper implementation of OneToMany and ManyToOne relationships in MongoDB involves understanding and utilizing the

I am currently working on setting up a oneToMany, ManyToOne relation method and here is my progress so far (pseudocode provided below). I am using Typegoose which is essentially Mongoose with types. If you are unfamiliar with it, that's okay because t ...

Angular 2 ngFor generates a collection of rows and columns forming a single large column

It seems that ngfor is generating divs one by one, resulting in a poor design where they are stacked on top of each other. I would like to achieve a layout like this: [1] [2] [3] [4] [5] [6] However, the current outcome looks like this: [ 1 ] [ 2 ] [ 3 ...

utilizing a decimal point that is not in accordance with my cultural norms

I have encountered a bug where, despite setting the language of Windows 10 to Italian and adjusting the decimal separator in the control panel, our website is unable to display numbers with it. Is there a way to instruct devextreme to use the comma speci ...

Tips for passing a usestate via props using interfaces in TypeScript and react?

I am currently working on implementing a light/dark theme and I have 2 components involved in the process. The first component code snippet is shown below, where I have successfully implemented a boolean to toggle between styles: export interface Props ...

Error message: In the combination of NextJs and Redux, an issue has occurred where the program is unable to access properties of null, specifically in

I am just getting started with Next and redux, but I am facing an issue. https://i.sstatic.net/CZTO2.png The error shown above occurs when trying to select a redux value from the store. I have attempted using raw useSelector from redux toolkit, but it s ...

Encountering a Vueify Typescript error in a Vue project

Recently diving into the world of Vue, I was able to successfully create a sample app using gulp, vueify, and TypeScript. To showcase what's happening and shed light on an issue I'm facing, here are snippets of the key code segments: Menu.ts im ...

A class or another interface is the only type that an interface is allowed to extend

We are currently using typescript version 2.9.2 I encountered an issue while trying to extend the interface DropDownOption. I received the error "error TS2312: An interface may only extend a class or another interface." Is there an alternate approach to ...