What is the best way to define the type of an object when the Key is already known?

If I have the code snippet below, how can I properly define the data object type based on the known value of data.type?

In this scenario, I aim to assign a specific type to data when its type property is either "sms" or "email"

const payload = '{"type":"sms","destination":123}'

type PayloadType = 'sms' | 'email'

interface BasePayload {
    type: PayloadType
}

interface SmsPayload extends BasePayload {
    destination: number
}

interface EmailPayload extends BasePayload {
    destination: string
}

const data: SmsPayload | EmailPayload = JSON.parse(payload) 

Thanks in advance!

*PS: I am aware that phone numbers are not actual numbers... this is purely for demonstration purposes.

Answer №1

Leaving behind an inheritance can be a difficult decision, but in this case, it is not necessary.

All you need to do is create two different types that share a common discriminating field called type:

interface TextPayload {
    type: 'text'
    content: string
}

interface ImagePayload {
    type: 'image'
    url: string
}

You can then create a union type by combining these two:

type PayloadCombo = ImagePayload | TextPayload

Now, you can easily parse the JSON payload from the backend like this:

const info: PayloadCombo = JSON.parse(payload) // assuming data integrity

And use the shared discriminator field to distinguish between the types:

if (info.type === 'text'){
    // When inside this block, `info` will be of type TextPayload
}

Answer №2

When looking at your example, it's clear that both

{"type":"sms","destination":123}
and
{"type":"email","destination":123}
are categorized under the same type SmsPayload.

To resolve this overlap, we need to first adjust the interface definitions:

const payload = '{"type":"sms","destination":123}';

type PayloadType = "sms" | "email";

interface BasePayload {
  type: PayloadType;
}

interface SmsPayload extends BasePayload {
  type: "sms";
  destination: number;
}

interface EmailPayload extends BasePayload {
  type: "email";
  destination: string;
}

Now, the only check required is for the type key:

const data: SmsPayload | EmailPayload = JSON.parse(payload);

if (data.type === "email") {
  const email = data; // vscode will infer type email: EmailPayload.
}

It's important to note that this assumes the payload string is correctly formatted. If there is a mistake, such as assigning a string value to the destination of an SMS, like in

const payload = '{"type":"sms","destination":"123"}';
, TypeScript won't catch it during compilation because it checks code before runtime.

For runtime validation, I recommend using the library zod.

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 TypeORM @BeforeInsert causing a field in Entity not to be populated with value

Currently, I am facing an issue where I am attempting to update or insert into a token field before the record is saved. However, when utilizing the @BeforeInsert hook, I encounter the following error: "error": "Cannot read property 'co ...

Encountered an error while trying to load config.ts file because of an issue

Trying to set up a new protractor project to conduct tests on an angular site. Node.js, typescript, protractor, and jasmine are all installed globally. After running webdriver-manager update and webdriver-manager start in the project folder, I proceed to b ...

In the else-branch, a type guard of "not null" results in resolving to "never."

After creating a type guard that checks for strict equality with null: function isNotNull<T> (arg: T): arg is Exclude<T, null> { return arg !== null } Testing it showed that the then-branch successfully removes null from the type. const va ...

Unable to create a loop within the constructor to assign API values

I created an export type shown below: export type Program{ key: string; value: string; } An array of values is returned from the API like this: apival = ["abc", "xyz" ...etc] In my component's constructor, I am implementing the f ...

Tips for converting a date string to a date object and then back to a string in the same format

I seem to be encountering an issue with dates (shocker!), and I could really use some assistance. Allow me to outline the steps I have been taking. Side note: The "datepipe" mentioned here is actually the DatePipe library from Angular. var date = new Dat ...

Steps to globally modify the font in Ionic

In my Ionic app running version 3.9.2, I am attempting to customize the default font to a specific custom one. After researching, I discovered that I need to set the font face in the app.scss file located within the app folder. Here is the code snippet I ...

Retrieving Vue data from parent components in a nested getter/setter context

<template> <div id="app"> {{ foo.bar }} <button @click="meaning++">click</button> <!--not reactive--> <button @click="foo.bar++">click2</button> </div> </templ ...

Navigating the complexities of managing numerous checkboxes in React

I am a beginner with react and recently received a task to complete. The requirements are: Show multiple checkboxes. The order of checkbox names may change in the future, allowing the client to decide the display order. Display checkboxes based on their a ...

How can Material UI Textfield be configured to only accept a certain time format (hh:mm:ss)?

Looking for a way to customize my material ui textfield to allow input in the format of hh:mm:ss. I want to be able to adjust the numbers for hours, minutes, and seconds while keeping the colons automatic. Any suggestions would be welcomed. ...

Accessing properties within nested objects in a class

In my Angular 7 application, I have two object classes filled with data - employee and company (data retrieved through a web API from a database). The Employee class has fields - emp_id, name, surname, and a company object. The Company class has - c_id, ...

Adding an anchor tag to an ngx-datatable-column can be done by utilizing the properties

My task involves loading data from the server and populating the ngx-datatable. When a specific column is clicked (with either a link <a href="randomurl"/> or [routerLink]="randomcomponent"), it should redirect to a different page or display a modal ...

After integrating React Query into my project, all my content vanishes mysteriously

Currently, I am utilizing TypeScript and React in my project with the goal of fetching data from an API. To achieve this, I decided to incorporate React Query into the mix. import "./App.css"; import Nav from "./components/Navbar"; impo ...

Encountering Duplicate Identifier Error while working on Angular 2 Typescript in Visual Studio Code

Currently attempting to configure a component in Angular 2 with Typescript using Visual Studio Code on Mac. Encounter the following errors when trying the code below: duplicate identifier 'Component'. and Duplicate identifier' DashboardCompo ...

What is the best way to combine the attributes of multiple objects within a union type?

I have a clearly defined schema type Schema = { a: { a: 1 } b: { b: 2 } } I am in need of a function that can generate objects that adhere to multiple schemas. function createObject<K extends keyof Schema>(schema: Array<K>, obj: Sche ...

Transforming an object in TypeScript to another object while retaining the properties of the original type

Issue Struggling with TypeScript type casting here. Trying to convert an object of type B to type A, but without carrying over the properties from type B. Inquiry Is there a way to achieve this conversion without explicitly mentioning the otherName prop ...

Guide to Making a Basic TypeScript Metadata Tag in Your Code

I'm looking for a way to format certain fields before sending them to the server-side. My goal is to serialize specific fields of my TypeScript classes using custom serializers. An example of what I'm aiming for is as follows: export class Pers ...

How is it possible for the igx-expansion-panel to close when there is a nested angular accordion present?

Currently, I am faced with the challenge of closing the igx-expansion-panel within my Angular project. While everything functions smoothly with a standard panel, things get a bit tricky when dealing with nested angular accordion structures using igx-accord ...

Can sweetalert2 be used as a tooltip?

I have a query, is it feasible to include a tooltip in the alert message? Alternatively, could there be another tooltip option available? Swal.fire({ title: '<strong>An example with HTML tags</strong>', icon: 'info', ...

Unable to locate dependencies while testing the react package locally

Recently, I came across this npm package designed for React using Typescript. To debug it locally, I initiated npm link in a new React project but encountered an error: https://i.sstatic.net/nObH6.png I suspect it may not be reading the packages correct ...

Utilizing Google Sheets as a secure, read-only database for Angular applications without the need to make the sheet accessible to the

Seeking a way to utilize Google Sheets document as a read-only database for my Angular application, I have attempted various methods. However, the challenge with all these approaches is that they necessitate public sharing of the Sheet (accessible to anyon ...