Tips for efficiently verifying existence of object attribute in conditional object type using Typescript

Below is a simplified version of the code I am working with, showcasing a type that can be an interface or another:

interface ChatBase {
    roomId?: string
    type: "message" | "emoji"
    configs: unknown
}
interface ChatMessage extends ChatBase {
    type: "message",
    configs:{
        text?: string
    }
}
interface ChatEmoji extends ChatBase {
    type: "emoji",
    configs: {
        emoji?: string
    }
}
type Chat =  ChatMessage | ChatEmoji

You can also explore this on the typescript playground

In my actual code, trying to check if "emoji" is defined in configs has become overly complicated. Is there a simpler way?

const chats: Chat[] = [
    { type: "message", configs: { text: "string" } },
    { type: "emoji", configs: { emoji: "string" } }
]

chats.map(chat=>{
    if(chat.configs.emoji){ // <=== THROWS ERROR SHOWN BELOW
        console.log("Has an emoji")
    }
    if("emoji" in chat.configs && chat.configs.emoji){ // <= Works but ridiculously long
        console.log("Has an emoji")
    }
    if(chat.type === "emoji" && chat.configs.emoji){ // <= Works but sometimes I test for shared properties
        console.log("Has en emoji")
    }
})

However, TypeScript is giving me an error:

Property 'emoji' does not exist on type '{ text?: string | undefined; }'.

The question now is, how can I simplify

if("emoji" in chat.configs && chat.configs.emoji)
without it being excessively lengthy?

Answer №1

To simplify the process, consider using a type predicate: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates

function checkForEmoji(chat: Chat): chat is ChatEmoji & {configs: {emoji: string}} {
    return chat.type === "emoji" && Boolean(chat.configs.emoji);
}
if(checkForEmoji(chat)){
    console.log("This chat has an emoji: ", chat.configs.emoji) // success
}

Be cautious when creating the predicate function to avoid errors that could slip through TypeScript's checks:

function checkForEmoji(chat: Chat): chat is ChatEmoji & {configs: {emoji: string}} {
    return true;
}

Answer №2

To simplify your code, create type predicates for each type in a separate file:

const isChatEmoji = (chat: ChatBase): chat is ChatEmoji => chat.type === "emoji";
const isChatMessage = (chat: ChatBase): chat is ChatMessage => chat.type === "message";

Then, you can use them like this:

chats.filter(isChatEmoji).forEach(chat => console.log(chat.configs.emoji));

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

What is the capability of dynamically generating an index in Typescript?

Can you explain why the Typescript compiler successfully compiles this code snippet? type O = { name: string city: string } function returnString(s: string) { return s } let o1: O = { name: "Marc", city: "Paris", [returnString("random")]: ...

Tips on setting a singular optional parameter value while invoking a function

Here is a sample function definition: function myFunc( id: string, optionalParamOne?: number, optionalParamTwo?: string ) { console.log(optionalParamTwo); } If I want to call this function and only provide the id and optionalParamTwo, without need ...

Iterate over Observable data, add to an array, and showcase all outcomes from the array in typescript

Is there a way to iterate through the data I've subscribed to as an Observable, store it in an array, and then display the entire dataset from the array rather than just page by page? Currently, my code only shows data from each individual "page" but ...

Compilation error occurred when running Angular with mat-form: ngcc encountered an issue while processing [email protected]

Currently dealing with a compile error in a small mat-form example that I created. Unfortunately, I am unable to pinpoint the exact issue causing this error. If you have a moment, please take a look at the code here: https://stackblitz.com/edit/angular-iv ...

Exploring the Wonderful World of Styled Components

I have a query regarding styled components and how they interact when one is referenced within another. While I've looked at the official documentation with the Link example, I'm still unclear on the exact behavior when one styled component refe ...

Using jest-dom without Jest is definitely an interesting challenge that many developers may

Can anyone help me with extending Typescript interfaces? I have come across a situation that I am trying to solve. In my tests, I am utilizing expect without using Jest directly (I installed it separately and it functions properly). Now, I am interested ...

Is there a way to turn off linting while utilizing vue-cli serve?

I am currently running my project using vue-cli by executing the following command: vue-cli-service serve --open Is there a way to stop all linting? It seems like it's re-linting every time I save, and it significantly slows down the process of ma ...

Error in Typescript: "Cannot assign to parameter that is of type 'never'"

Here is the code snippet that I am working with: FilesToBlock: []; //defined in this class //within a method of the class this.FilesToBlock = []; this.FilesToBlock.push({file: blockedFile, id: fileID}); However, I'm encountering an issue with fil ...

"Utilizing Firebase Functions to update information in the Firebase Realtime Database on a daily basis

Currently, I am in the process of working on a project where I aim to provide users with a daily percentage of points based on their current available points and update this data in my Firebase database. My goal is to add points for users on a day-to-day b ...

A class in Typescript containing static methods that adhere to an interface with a string index

Take a look at this code snippet: interface StringDoers { [key: string]: (s: string) => void; } class MyStringDoers implements StringDoers { public static print(s: string) { console.log(s); } public static printTwice(s: string) { conso ...

What is the most effective method for locating and modifying the initial instance of an element within a group?

In my Javascript/Typescript collection, I have the following items: [ {"order":1,"step":"abc:","status":true}, {"order":2,"step":"xyz","status":true}, {"order":3,"step":"dec","status":false}, {"order":4,"step":"pqr","status":false}, {"order":5,"step":" ...

Encountering a SassError while trying to create unique themes with Angular Materials

I'm currently in the process of designing a unique theme for Angular Materials by following the instructions provided in this guide:https://material.angular.io/guide/theming#defining-a-custom-theme However, I've encountered an issue while attemp ...

Achieving Jest integration with Angular 9 in a Storybook setup

We are currently utilizing Storybook 5 alongside Angular 9, with Jest 26 for some of the testing procedures. The issue we're facing arises when using Typescript version below 3.8.0 - a requirement for Angular 9's ng build --prod. This results in ...

Tips for setting variable values in Angular 7

I'm encountering an issue with assigning values to variables in my code. Can anyone provide assistance in finding a solution? Here is the snippet of my code: app.component.ts: public power:any; public ice:any; public cake:any; changeValue(prop, ...

Retrieving JSON Information in HTML using Angular 4

Is it possible to retrieve specific data from a JSON file in my HTML using Angular 4's MatCellDef? Specifically, I am interested in accessing the FROM, TO, PERCENT, and SUBTRACT values of the RateBands Array. JSON Data Sample: [ { "year ...

Storing and Retrieving User Identifiers in Next.js

Currently, I am developing a project using Next.js and I have the requirement to securely store the userId once a user logs in. This unique identifier is crucial for accessing personalized user data and creating dynamic URLs for the user profile menu. The ...

Can we specify the type of a destructured prop when passing it as an argument?

I have implemented Material UI's FixedSizeList which requires rendering rows in the renderRow function and passing it as a child to the component. The renderRow function accepts (index, style, data, scrolling) as arguments from the FixedSizeList comp ...

What is the best way to delay an observable from triggering the next event?

In my Angular project, I am implementing RxJs with two subjects. s1.next() s1.subscribe(() => { // perform some operation and then trigger the event for s2 s2.next() }); s2.subscribe(() => { // perform some operat ...

The array used within the useEffect hook and the getCoordinates function appears to be distinct when printed with console

Utilizing GoogleMap API for Custom Location Display I have an imported array of JSON objects named data which includes an address property. The Google Maps API is used to retrieve coordinates from the addresses in order to generate custom markers displaye ...

Ditch the if-else ladder approach and instead, opt for implementing a strategic design

I am currently working on implementing a strategic design pattern. Here is a simple if-else ladder that I have: if(dataKeyinresponse === 'year') { bsd = new Date(moment(new Date(item['key'])).startOf('year&apos ...