Ensuring complete type safety by passing an object literal as a function parameter to a TypeScript type with rigorous type validation

I have a script written in JavaScript that I am currently converting to TypeScript. The code I am working with includes the following:

const shapes = [];

shapes.push({ name: 'Circle', radius: 12 });
shapes.push({ name: 'Rectangle', width: 34, height: 56 });
...

To enhance type safety, I have implemented the following interfaces:

interface Shape {
   name: string;
}

interface Circle extends Shape {
   radius: number;
}

interface Rectangle extends Shape {
   width: number;
   height: number;
}

In an attempt to make the code more type checked, I initially tried this approach:

const shapes: Shape[] = [];

shapes.push({ name: 'Circle', radius: 12 } as Circle);
shapes.push({ name: 'Rectangle', width: 34, height: 56 } as Rectangle);
...

The issue with using as Type syntax is that it does not provide full type checking. I encounter problems like the one where the 'height' property is missing but the line still passes:

shapes.push({ name: 'Rectangle', width: 34 } as Rectangle);

I found a workaround by defining the object outside the function call, which helped catch errors like missing properties:

const r: Rectangle = { name: 'Rectangle', width: 34 };  // ERROR: missing height (Yay!)
shapes.push(r);

However, this approach results in messy code due to the lengthy list of shapes.

Are there any alternatives to using as Type for casting object literals to types inline that offer full type checking when used as parameters in functions?

PS: I am not interested in refactoring the code right now; I just want to explore other options besides as Type for achieving complete type checking with object literals passed as function parameters. This example may not directly relate to my current code, but it serves to illustrate the problem.

TypeScript Playground

Answer №1

To ensure the object being passed has the required properties based on its name, I recommend using a discriminated union. This way, TypeScript can guarantee the necessary properties:

interface Circle {
    name: "Circle";
    radius: number;
}

interface Rectangle {
    name: "Rectangle";
    width: number;
    height: number;
}

type Shape = Circle | Rectangle;

const shapes: Shape[] = [];

// Valid examples:
shapes.push({ name: "Circle", radius: 12 });
shapes.push({ name: "Rectangle", width: 34, height: 56 });

// This will result in an error:
shapes.push({ name: "Rectangle", width: 34 });

Playground example

The relationship between Shape and Circle/Rectangle is now inverted where Shape derives from Circle, Rectangle, and potentially other shapes down the line. Each shape has a string literal property for its unique name. By creating a union of shapes, we can narrow down members to specific shapes using the name property.


Is there a way other than casting an object literal inline as a parameter to a function for full type checking?

TypeScript doesn't support casting, only type assertions. Casting in some languages modifies values, unlike TypeScript's type assertion which validates that something meets the requirements of a given type.

A new feature added by TypeScript allows expecting something to satisfy a type contract without enforcing it. You can use this concept but it may not be aesthetically pleasing:

// Valid cases:
shapes.push({ name: "Circle", radius: 12 } satisfies Circle as Shape);
shapes.push({ name: "Rectangle", width: 34, height: 56 } satisfies Rectangle as Shape);

// This will cause an error: 
shapes.push({ name: "Rectangle", width: 34 } satisfies Rectangle as Shape);

Playground example

The purpose of using satisfies is to trigger type checking. Type assertions are then needed due to excess property checks triggered by the objects defined through inline literals. It wouldn't be necessary if objects weren't defined inline [example], but adhering to your requirement of defining them inline warranted their use.

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

When trying to incorporate aws-sdk into Angular2, an error message stating "Module 'stream' cannot be found" may occur

I encountered the following issues: Error TS2304: Cannot find name 'Buffer', https://github.com/aws/aws-sdk-js/issues/994 and Using aws-sdk with angular2 Even though my typings and @types/node seem to be properly installed, I am still encount ...

Swap references between two components at the same level

There are two instances of custom-component spawned by a shared parent with distinct data, each displayed as a tab in the mat-tab-group. <mat-tab-group> <mat-tab label="TAB1"> <ng-template matTabContent> <custom-componen ...

Deactivate the Mention and Hash tag in ngx-linkifyjs

I am currently utilizing ngx-linkifyjs to automatically convert URLs in text to clickable hyperlinks. However, I am facing an issue where it is also converting # and @ tags into links. Is there a way to prevent the conversion of # and @ while maintain ...

What is the best way to retrieve data in my client component without risking exposing my API key to unauthorized users?

To retrieve information, I plan to use pagination in order to specify a particular page number within the API URL and fetch additional data by updating the value of page. The process of fetching data in my server component is as follows: // fetchData.tsx ...

Guide on Applying a Dynamic Color in VueJs 3 Composition API/Vuetify Using CSS

Currently, my project utilizes Vue 3 with the composition API and Vuetify for the UI. I am looking to utilize a color that is already defined in a Vuetify theme variable within my CSS, similar to how I have done it previously in JavaScript. Although I at ...

Upon transitioning from typescript to javascript

I attempted to clarify my confusion about TypeScript, but I'm still struggling to explain it well. From my understanding, TypeScript is a strict syntactical superset of JavaScript that enhances our code by allowing us to use different types to define ...

Best practice for saving a User object to the Request in a Nest.js middleware

Currently, in my nest.js application, I have implemented a middleware to authenticate Firebase tokens and map the user_id to my database. Within this middleware, I retrieve the user_id from Firebase, fetch the corresponding User object from the database, a ...

The specified type '{ data: any; }' is incompatible with the type 'IntrinsicAttributes'. The property 'data' is not found in the type 'IntrinsicAttributes'

I'm encountering issues with the data property. interface Props { params: { slug: string }; } const Page = async ({ params }: Props) => { const data: any = await getPage(params.slug); // This section dynamically renders the appropriate orga ...

NextJs Route Groups are causing issues as they do not properly exclude themselves from the app's layout.tsx

As far as I know, the layout.tsx in the app directory serves as the root layout. To customize the layout structure for specific segments, you can use Route Groups. More information can be found here. In this setup, any page.tsx file inside a directory nam ...

Angular: Array in the template re-renders even if its length remains unchanged

Below is the template of a parent component: <ng-container *ngFor="let set of timeSet; index as i"> <time-shift-input *ngIf="enabled" [ngClass]="{ 'mini-times' : miniTimes, 'field field-last&ap ...

Angular Build Showing Error with Visual Studio Code 'experimentalDecorators' Configuration

Currently experiencing an issue in VSC where all my typescript classes are triggering intellisense and showing a warning that says: "[ts] Experimental support for is a feature that is subject to change in a future build. Set the 'experimentalDeco ...

What is the process for bringing my API function into a different Typescript file?

I've successfully created a function that fetches data from my API. However, when I attempt to import this function into another file, it doesn't seem to work. It's likely related to TypeScript types, but I'm struggling to find a soluti ...

Developing a node module that includes nested subfolders

I'm currently working on an npm module and have the following index.ts file: export * from './src/A/index'; Right now, when importing in my app, it looks like this: import {something} from 'myModule'; Now, I want to enhance my ...

What are the steps for utilizing CzmlDataSource within React Resium?

Currently, I'm attempting to showcase the data from a local czml file on a map using React, Typescript, and Resium. Unfortunately, an error keeps popping up stating "This object was destroyed, i.e., destroy() was called." Can someone point out where m ...

Error TS2322: Cannot assign type 'Foo | Bar' to type 'Foo & Bar'

I am attempting to save an item in an object using the object key as the discriminator for the type. Refer to the edit below. Below is a simple example: type Foo = { id: 'foo' } type Bar = { id: 'bar' } type Container = { foo ...

What is the best way to create a linear flow when chaining promises?

I am facing an issue with my flow, where I am utilizing promises to handle the process. Here is the scenario: The User clicks a button to retrieve their current position using Ionic geolocation, which returns the latitude and longitude. Next, I aim to dec ...

What is the best way to save an array of objects from an Axios response into a React State using TypeScript?

Apologies in advance, as I am working on a professional project and cannot provide specific details. Therefore, I need to describe the situation without revealing actual terms. I am making a GET request to an API that responds in the following format: [0: ...

The process of assigning a local variable to a JSON response in Angular 2

I need to store a JSON response that includes an array with the following data: 0 : {ID: 2, NAME: "asd", PWD_EXPIRY_IN_DAYS: 30} 1 : {ID: 1, NAME: "Admin", PWD_EXPIRY_IN_DAYS: 30} In my code, I have defined a local variable called groups of type ...

Struggling with module dependencies in Nest.js

I have been diving into the documentation provided on the NestJs website, but I've noticed that it can be a bit scattered. My goal is to retrieve an RPG Character from a Mongo database using TypeORM. Unfortunately, I seem to be running into dependency ...

invoking a function at a designated interval

I am currently working on a mobile application built with Ionic and TypeScript. My goal is to continuously update the user's location every 10 minutes. My approach involves calling a function at regular intervals, like this: function updateUserLocat ...