What is the method to define a Typescript type with a property type determined by the value of another property?

I need to design a data structure for retrieving survey questions from the backend system.

The fields id, description, and optionType will be populated by the backend. Depending on the value of optionType, I aim to include the options for the question, which will display as radio buttons on the frontend.

export type QuestionDto<T = OptionMap> = T extends 1
  ? {
      id: number;
      description: string;
      optionType: T;
      options: YesNoOption;
    }
  : T extends 2
  ? {
      id: number;
      description: string;
      optionType: T;
      options: YesNoSomewhatOption;
    }
  : {
      id: number;
      description: string;
      optionType: T;
      options: AgeOption;
    };

type YesNoOption = {
  1: 'Yes';
  2: 'No';
};

type YesNoSomewhatOption = {
  1: 'Yes';
  2: 'No';
  3: 'Somewhat';
};

type AgeOption = {
  1: '1 Year old';
  2: '2 Year old';
  3: '3 Year old';
  4: '4 Year old';
};

export type OptionMap = 1 | 2 | 3 | 4;

This structure is applied when fetching data from the backend system

 return this.httpClient.get<QuestionDto[]>(
      `${environment.apiUrl}/assessments`
    );

The options field is not automatically generated for every question.

Answer №1

When TypeScript is compiled, it doesn't have the ability to predict what the API will provide in its response. This requires a reassessment of your data types. Why does strictness matter in this case? Using literal strings as values isn't feasible since they are eliminated during compilation.

An alternative approach would be to convert all answer types into enums, allowing for easier retrieval based on the API output.

Answer №2

Here is my solution based on the given question:

The options are not included in the backend response, so the frontend needs to generate options based on the optionType.

You cannot set values using type directly; you have to define a mapping for questionType => Array.

Below is a snippet of sample code: Run the code snippet here

/// Sample REST API response schema
type QuestionDtoRest = {
  id: number;
  description: string;
  optionType: number;
}

/// Service Layer data types
type QuestionOption = {
  key: number;
  label: string;
}

type QuestionDto = QuestionDtoRest & { options: Array<QuestionOption>  };

/// Mapping of optionType to corresponding options array
const optionTypeOptions = new Map<number, Array<QuestionOption>>([
  [1 , [ { key: 1, label: "Yes"}, { key: 2, label: "No" } ]],
  [2 , [ { key: 1, label: "Yes"}, { key: 2, label: "No" }, { key: 3, label: "Somewhat" } ]],
  [3 , [ { key: 1, label: "1 Year old'"}, { key: 2, label: "2 Year old'" }, { key: 3, label: "3 Year old'" }, { key: 4, label: "4 Year old'" } ]],
]);

const parseRestQuestion = (restQuestion: QuestionDtoRest): QuestionDto => {
  const options = optionTypeOptions.get(restQuestion.optionType);

  if (!options) {
    throw new Error(`Unsupported option type: "${restQuestion.optionType}"`);
  }

  return {
    ...restQuestion,
    options
  };
};

// Test the parsing function with sample REST responses

const restResponse: Array<QuestionDtoRest> = [
  { id: 1, optionType: 1, description: "This is option type 1" },
  { id: 20, optionType: 2, description: "This is option type 2" },
  { id: 45, optionType: 3, description: "This is option type 3" },
]

const parsedResponse = restResponse.map((r) => parseRestQuestion(r));

console.log(parsedResponse);

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

Files are nowhere to be found when setting up an angular project

After creating an Angular project, I noticed that some key files were missing in the initial setup, such as app.modules.ts and app-routing.modules.ts The project was generated using the command ng new name Here is a screenshot displaying all the files th ...

Ways to boost branch coverage in Jest when working with a for-in loop that has an if statement

Here is a piece of code that maps specific fields from one object to another and copies their values: //Function parameters var source = {"f1": "v1", "f2": "v2", "f3":"v3"}; var fieldsMapping = {"f1": "c1", "f2":"c2"}; //Function definition begins var co ...

The specified class is not found in the type 'ILineOptions' for fabricjs

Attempting to incorporate the solution provided in this answer for typescript, , regarding creating a Line. The code snippet from the answer includes the following options: var line = new fabric.Line(points, { strokeWidth: 2, fill: '#999999', ...

Utilizing Angular for Webcam Integration

After trying out this code snippet: <video autoplay playsinline style="width: 100vw; height: 100vh;"></video> <script> navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } }) .then(stream =&g ...

Encountered an issue where property scrollToBottom is unable to be read of null upon returning to

Your system information: Cordova CLI: 6.4.0 Ionic Framework Version: 2.0.0-rc.4 Ionic CLI Version: 2.1.14 Ionic App Lib Version: 2.1.7 Ionic App Scripts Version: 0.0.47 ios-deploy version: Not installed ios-sim version: Not installed OS: Windows 7 ...

Achieving a clean/reset for a fetch using SSR in Next 13

Is there a way to update the user variable in the validateToken fetch if a user signs out later on, such as within a nested component like Navigation? What is the best approach to handle clearing or setting the user variable? Snippet from Layout.tsx: impo ...

Integrating Octokit middleware in Next.js for enhanced functionality

Currently, I am in the process of honing my skills by creating a GitHub app. In Octokit, there is a feature called createNodeMiddleware that caught my attention. However, integrating it with next.js seems to pose some challenges. My main issue right now re ...

Is it possible to make my Toggle/Click event refresh the entire component every time it is clicked?

I'm trying to implement a toggle function to show/hide a specific DIV and dynamically change the button text based on the current state in React Hooks. However, every time I click on it, the entire page seems to re-render in Next.js. I'm not enti ...

Error message: Unable to locate Bootstrap call in standalone Angular project after executing 'ng add @angular/pwa' command

Having an issue while trying to integrate @angular/pwa, it keeps showing me an error saying "Bootstrap call not found". It's worth mentioning that I have removed app.module.ts and am using standalone components in various places without any module. Cu ...

Can the tiles in a grid-list be organized in a specific order?

I am facing an issue with a class named 'scenario' that has properties such as 'id', 'name', and 'number' among others. In the HTML, scenarios are displayed in this format: <mat-grid-list [cols]="breakpoint" r ...

How can I invoke TypeScript methods within a jQuery event handler in the ngAfterViewInit lifecycle hook?

I am currently utilizing Angular 4. I need to invoke methods from a typescript file within the ngAfterViewInit method. declare var $; @Component({ selector: 'app-details', templateUrl: './details.component.html', styleUrls: [&apo ...

Using NextJS: Adding a fresh query string value to an existing asPath or modifying the current query string

Trying to wrap my head around the workings of the NextJS router system: I have articles categorized under: Medical Charity Wedding Funeral Currently, I have a navbar where users can filter articles by category and search by keyword. The category filter i ...

Is it possible to use conditional logic on child elements in formkit?

I am a bit confused about how this process functions. Currently, I am utilizing schema to create an address auto complete configuration. My goal is to have the option to display or hide the fields for manual input. This is the current appearance of the ...

My Angular FormGroup patchValue method successfully applies changes, but the updates are not reflected on

I'm currently facing an issue with populating my Edit Form using user data emitted from an event in my code. Despite my efforts, the form is not displaying the information correctly. export class EditUserComponent implements OnInit{ construct ...

TS2345 error: Typescript compilation failed due to the argument type 'FlattenMaps' being incorrect

Encountering an issue with the compilation of typescript (npm run build) due to a nested schema problem. The step (hubs.push(h.toJSON());) is resulting in a TS2345 error code. Currently attempting to upgrade nodejs and mongodb versions but unsure of what m ...

Issue with create-react-app and Emotion.js: Uncaught ReferenceError: jsx is undefined

I am currently attempting to incorporate emotion.js into my create-react-app project using TypeScript. I followed the steps outlined in the documentation, which involved adding @emotion/core, importing {jsx, css} from '@emotion/core';, and includ ...

Clicking on the edit button will open the form for editing the selected item

I am facing an issue with my HTML code. I have a list of data where each row has an edit button to update the specific record of that row. However, when I click on the edit button, I want only the form under that particular row to open for updating the rec ...

TypeORM Error: Trying to access the 'id' property of an undefined object

I attempted to implement migration in TypeORM as shown below: TableExample.entity.ts @Entity({ name: 'table_example' }) export class TableExampleEntity { constructor(properties : TableExampleInterface) { this.id = properties.id; ...

What is the best way to create a straightforward interface using TypeScript?

Although I'm sure this question has been asked before, I couldn't find the answer on Google or SO. So here it goes: I am looking to create an interface with a key named id of type number. Additionally, there may be other keys with unknown names ...

Ways to dynamically access input errors

Exploring My Implementation I have devised a function that takes an argument to access specific formik errors dynamically. This requires using bracket notation instead of dot notation as shown below: import {useFormikContext} from 'formik'; fun ...