What is the best way to determine the property type dynamically in TypeScript based on the value of another property?

I have been working with Polymorphic relationships and currently have the following TypeScript interface defined:

interface SubjectA {}
interface SubjectB {}
interface SubjectC {}

enum SubjectType {
  SubjectA = 'Subject A',
  SubjectB = 'Subject B',
  SubjectC = 'Subject C',
}

interface ExampleSubject {
  type: SubjectType;
  subject: SubjectA | SubjectB | SubjectC
}

In this scenario, it is clear that ExampleSubject.subject can be one of three possible subject types (SubjectA, SubjectB, SubjectC). My goal is to dynamically resolve its type based on the value of ExampleSubject.type. For instance, if ExampleSubject.type is set to SubjectType.SubjectA, then ExampleSubject.subject should correspond to SubjectA.

I would appreciate any guidance on how to achieve this dynamic resolution. Thank you

Answer №1

One way to achieve this functionality is by using a discriminated union type, which allows for defining multiple interfaces that share a common property to differentiate between them (see documentation for more information)

interface SubjectX { attribute: 'x' }
interface SubjectY { attribute: 'y' }
interface SubjectZ { attribute: 'z' }

enum SubjectCategory {
  SubjectX = 'Subject X',
  SubjectY = 'Subject Y',
  SubjectZ = 'Subject Z',
}

interface ExampleSubjectX {
  category: SubjectCategory.SubjectX;
  subject: SubjectX
}
interface ExampleSubjectY {
  category: SubjectCategory.SubjectY;
  subject: SubjectY
}
interface ExampleSubjectZ {
  category: SubjectCategory.SubjectZ;
  subject: SubjectZ
}

type ExampleSubjectType = ExampleSubjectX | ExampleSubjectY | ExampleSubjectZ
const subjectX: SubjectX = {
  attribute: 'x'
}
const subjectY: SubjectY = {
  attribute: 'y'
}

const exampleValidSubject: ExampleSubjectType = {
  category: SubjectCategory.SubjectX,
  subject: subjectX
}
const exampleInvalidSubject: ExampleSubjectType = {
  category: SubjectCategory.SubjectX,
  subject: subjectY
}
/* TypeScript error: Type '{ category: SubjectCategory.SubjectX; subject: SubjectY; }' is not assignable to type 'ExampleSubjectType'.
  The attributes of 'subject.attribute' are incompatible between these types.
    Attribute '"y"' is not compatible with attribute '"x"'.(2322) */

Try out the code on the TypeScript playground

Note: It's important to make sure that the interface definitions like SubjectX have unique properties to distinguish them in a discriminated union. In this example, the attribute property serves this purpose.

Answer №2

If you want to distinguish between different types, consider using a discriminated union instead of an interface. Utilize the existing discriminant (SubjectType). Construct the union like this:

type ExampleSubject =
    | { type: SubjectType.SubjectA; subject: SubjectA }
    | { type: SubjectType.SubjectB; subject: SubjectB }
    | { type: SubjectType.SubjectC; subject: SubjectC };

When dealing with x of type ExampleSubject, you can refine the type of x.subject by verifying x.type:

if (x.type === SubjectType.SubjectA) {
    console.log(x.subject);
    //             ^? (property) subject: SubjectA
}

Check out the Playground example

Remember to give some content to SubjectA, SubjectB, and SubjectC so TypeScript can differentiate them properly. Since TypeScript's typing is based on structure rather than name, providing unique content helps in avoiding confusion between types.

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

Issue with Promise not resolving in Node when using Edge

As I explore the best way to utilize my C# dlls with Edgejs for Node, I encountered a situation where one proxy function in Node appears like this (a class method in Typescript): readSettings(args: ReadSettingsParams) : Promise<response> { let $ ...

When React first fetches data and users move to chat with each other, the state value does not update

Recently, I started learning about React and Node.js as I dive into building a chat application. When a user is clicked for one-on-one chat, I retrieve records from the database using backend Node.js and also incorporate socket.io. In my Chat.js file: imp ...

Is there a way to choose multiple IDs in this code that all share a common word?

I'm attempting to target several duplicate ids such as "img1, img2" in my code, but I haven't had any success. How can I select all the ids that contain the same word without relying on jQuery or any other external libraries? Below is the code s ...

When I use the AngularJS otherwise function, it sends me to the incorrect route

My routes are defined in the following way: $routeProvider .when('/settings', { templateUrl: 'settingsTemplateURL', controller: settingsCtrl }) .when('/profile', { templateUrl: 'profileTemplateURL', controll ...

Employing strict mode, albeit with certain exceptions

When using the compiler strict mode ("strict": true), errors occur for my models that are structured like this: @Entity class MyModel { @Column() public name: string; @Column() public email: string; ... } The specific errors enc ...

Does CausesValidation validate all validators, including validation groups?

I have encountered an issue with my web page where I have 3 separate validation groups, but when I attempt to submit the form, I want all of the groups to validate simultaneously. It appears that using causesValidation="true" on the button does not trigge ...

Assignment of type 'Angular Promise<void>' is not compatible

In the process of developing a website with Angular4 and retrieving data from Contentful CMS API, I am encountering an issue with assigning proper types to the returned data despite seeing the correct types in the console. The example mock data is as foll ...

Shifting JSON Arrays in JavaScript - Changing Order with Ease

Consider the following JSON data: [ { "name": "Lily", "value": 50 }, { "name": "Sophia", "value": 500 }, { "name": "Ethan", "value": 75 } ] I am looking to verify and organize it in ...

showing text from chosen selection with an additional element included in the output

I could use some assistance with this code snippet. My goal is to showcase the options in the format: "Fried Rice = 10.000" as the outcome. However, the issue I am facing is that the select option box also contains the price. What I actually need is for th ...

XMLHTTPRequest is experiencing issues with displaying the progress bar

I'm facing an issue with uploading images in PHP while showing the progress status. The image uploads correctly using XMLHttpRequest, but I can't see the progress bar moving. Below is my code. Can someone help me solve this problem? <html> ...

Utilizing Cordova for Windows 8.1 in Visual Studio 2015 for external image retrieval and insertion into img tag

I am encountering an issue with displaying external images in a Cordova app. While the DOM is functioning correctly, the images are failing to load. I am specifically trying to resolve this for Windows 8.1 only. In my Cordova project for JavaScript, I have ...

Navigational menu routing with AngularJS2 using router link paths

Currently, I am working on developing a navigation menu using angularJS2. Here is the snippet from my app.component.ts: import {provide, Component} from 'angular2/core'; import {APP_BASE_HREF, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, HashLocati ...

Exploring the process of transforming a dynamic PDF into a static PDF using PHP or NodeJS

Issue I am looking for a PHP/NodeJS API that can convert editable PDF files to non-editable PDFs online. Our client application requires the user to download PDF files that cannot be modified using software like Foxit Reader or Adobe. We are currently us ...

Converting an HTMLElement to a Node in Typescript

Is there a method to transform an HTMLElement into a Node element? As indicated in this response (), an Element is one specific type of node... However, I am unable to locate a process for conversion. I specifically require a Node element in order to inp ...

The error persists in Ajax requests despite the correct usage of json_encode and everything functioning correctly

In the realm of e-commerce, I have successfully implemented an AJAX request to dynamically add and remove items from the cart. Interestingly enough, every time the jQuery script is executed, everything seems to be working seamlessly. The server always resp ...

How can we transfer parameters in JavaScript?

My vision may be a bit vague, but I'll try to explain it as best as I can. I want to implement multiple buttons that can toggle the visibility of a div (I have this functionality working already). Each button should carry two values (a number and a l ...

Guidance on implementing a source map in a Node.js VM

Currently, I am analyzing JavaScript bundled source code in Node.js using the following snippet of code: const javascriptCode = "..." const javascriptSourceMap = "..." const wrapper = NativeModule.wrap(javascriptCode); const script = ne ...

Using Fabric JS to update the image source of an element with a new URL

Can the src attribute of a fabric js object be changed to a CDN url? { version: '4.4.0', objects: [ { type: 'image', version: '4.4.0', originX: 'left', ... src:'www.cdn ...

"Use Highcharts to visually compare the data from one month in two different years

How can I use Highcharts Columns to compare data from two different years within the same month? Check out the example image below: https://i.sstatic.net/8MMsA.png The data structure is as follows: myData[0] = { data1:"0.00", data2:"0.00", data3:"868.0 ...

What happens when 'grid' property is undefined in modal?

I encountered an issue with my modal where I wanted to display pre-selected rows, but kept getting a 'cannot read 'grid' of undefined' error. The UI Grids are defined within the modal, and I have declared two Grid APIs with different na ...