Struggling to pinpoint the specific property within a complex, deeply nested object

Trying to extract the message from the first error in a type-safe manner:

// Data returned from backend
const mutationError: unknown = {
    sources: {
        errors: [
            {
                message: 'Hello from error message'
            }
        ]
    }
};

const sources = (
    typeof mutationError === 'object'
    && mutationError
    && 'sources' in mutationError
    && typeof mutationError.sources === 'object'
    && mutationError.sources
);

const errors = (
    typeof sources === 'object'
    && sources
    && 'errors' in sources
    && Array.isArray(sources.errors)
    && sources.errors
);


const firstError = errors && errors[0];

const message = (
    typeof firstError === 'object'
    && firstError
    && 'message' in firstError
    && typeof firstError.message === 'string'
    && firstError.message
);

However, the variable message is wrongly typed as any. What could be the issue?

Explore more on this TS Playground.

Answer №1

The issue you are encountering stems from the fact that Array.isArray() has a specific call signature:

interface ArrayConstructor {
    isArray(arg: any): arg is any[];
}

This means that it restricts its argument to an array of type any[], where every element is of the any type. However, since any absorbs all other types (any & T simplifies to just any), this approach does not provide the desired results, as it leads to everything being treated as any thereafter.

It could be argued that instead of any[], perhaps it should return unknown[] using the safer unknown type. Nevertheless, the call signature was established before the introduction of unknown. While there has been discussion in microsoft/TypeScript#26188 about substituting any with unknown in the TypeScript standard library (with coincidentally Array.isArray() being a prime example), such a modification would constitute a significant breaking change and is unlikely to occur.

For now, until this situation changes, a workaround is necessary. One option is to create your own version of Array.isArray() that returns unknown[]. The simplest method to achieve this is by wrapping the function:

function isArray(x: any): x is unknown[] {
    return Array.isArray(x)
}

By utilizing this alternative implementation, you will obtain unknown rather than any:

const errors = (
    typeof sources === 'object'
    && sources
    && 'errors' in sources
    && isArray(sources.errors)
    && sources.errors
);
// const errors: false | unknown[] | null

const firstError = errors && errors[0];
// const firstError: unknown

This behavior is more effective, as unknown & T accurately resolves to T:

const message = (
    typeof firstError === 'object'
    && firstError
    && 'message' in firstError
    && typeof firstError.message === 'string'
    && firstError.message
);
// const message: string | false | null

Link to Playground showcasing code

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

Guidelines for converting an array into checkboxes using React Native with TypeScript

As I embark on my React Native journey, I have chosen to use TypeScript in my project. Currently, I am faced with the challenge of mapping an array into a checkbox. Enclosed below is a snippet from my JSON file: { "stud_name": "Adam", "sex": "male" ...

Creating a service class instance within an interceptor in NestJS

When working with interceptors in NestJS (view documentation), I encountered a situation where I needed to call a service within the interceptor. Here is the approach I took: export class HttpInterceptor implements NestInterceptor { constructor(privat ...

``"Resolving the Issue of Null User ID and Unavailable localStorage in Angular Firebase when using Server-Side Rendering with isPlatformBrowser"

dashboard.component.ts : ngOnInit() { const userId = this.authService.getUserId(); if (userId) { this.loadProfileImage(userId); } else { console.error('User ID is null. Cannot load profile image.'); } } auth.servi ...

Should await be used before dispatching hooks in redux?

I am facing a scenario where I need to execute 2 dispatch events consecutively, with no dependency between them. Could I implement it like the following code snippet? await dispatch(firstEvent) dispatch(secondEvent) My goal is to ensure that secondEvent ...

Angular 2: Dynamically Adjusting View Components Based on URL Path

Apologies for the unconventional title. I struggled to come up with a better one. My goal is to develop an application with a simple 3-part structure (header / content / footer). The header should change based on the active route, where each header is a s ...

Experiencing browser crashes following the incorporation of asynchronous functions into a JavaScript file. Seeking solutions to resolve this

In my recent project, I developed a basic online store application using vanilla javascript and ES6 classes. The shop items are stored in a JSON file which I used to populate the user interface. To implement functions like "addToCart", "quantityChange", a ...

Highcharts Angular experiencing issues with DataGrouping

In my current angular 11 project, I have the following dependencies listed in my package.json file: "dependencies": { ... "highcharts-angular": "^2.10.0", ... My goal is to display a chart with dates on the x-axis ...

The TypeScript inference feature is not functioning correctly

Consider the following definitions- I am confused why TypeScript fails to infer the types correctly. If you have a solution, please share! Important Notes: * Ensure that the "Strict Null Check" option is enabled. * The code includes c ...

Example TypeScript code: Use the following function in Angular 5 to calculate the total by summing up the subtotals. This function multiplies the price by the quantity

I have a table shown in the image. I am looking to create a function that calculates price* quantity = subtotal for each row, and then sum up all the subtotals to get the total amount with Total=Sum(Subtotal). https://i.stack.imgur.com/4JjfL.png This is ...

What is the process for overloading a Vue component decorator in a TypeScript environment?

I enjoy using Vue with TypeScript decorators: import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; @Component({ components: { .. }, ... }) ... Is it possible for me to add a custom property to pass to the decorator in this ...

Using String Class Constraint in TypeScript

I am attempting to create a class that includes a string constraint, but I keep encountering an error at the get scale() function. class Scaling<T extends string> { _scale = ""; constructor(props: T) { this._scale = props; } ...

What is the best way to handle optional parameters while also ensuring that a required parameter is passed in TypeScript?

Currently, I am working on a function that requires only one of the configurations specified in the SubNodeConfig interface. While I can set all of them as optional as shown below, I am looking for a way to make one of them mandatory, possibly using the no ...

Is it possible to link fields with varying titles in NestJS?

Currently, I am developing a NestJS application that interacts with SAP (among other external applications). Unfortunately, SAP has very specific field name requirements. In some instances, I need to send over 70 fields with names that adhere to SAP's ...

Issue with TypeScript retrieving value from an array

Within my component.ts class, I have defined an interface called Country: export interface Country{ id: String; name: String; checked: false; } const country: Country[] = [ { id: 'India', name: 'India', checked: false}, { ...

Angular MistakeORAngular Error

Every time I refresh the page, I encounter an error in my code while attempting to display a newly edited and saved text. I've initialized the variable, made the access variable public, but it still doesn't work. Can someone point out what I migh ...

We're sorry, the request was blocked due to a missing Access-Control-Allow-Origin header

Recently, while working on a blog application with the front end in react + typescript and backend in go iris, I encountered an issue when trying to fetch blog content using a get request. The backend was running on localhost:5000 and the node at localhost ...

Inform users once the new application build using Angular 2 with Angular CLI has been deployed

Currently using the angular cli, I am looking for a way to alert users once a new version of the app has been deployed. My current approach involves utilizing replace-in-file and an external JavaScript file that is executed prior to building the applic ...

Tips for importing modules in React JS/Next JS + Typescript as 'components/filename' rather than '../../../components/filename'

Is there a way to import pages, components, and containers directly using syntax like 'components/filename' or '@components/filename', rather than having to specify the full path to the parent folder such as '../../components/filen ...

Tips for restricting keys when using a union as an indexer without demanding all of them

Trying to create a type that only allows certain keys from a union using the key in statement has resulted in all keys being required. How can I define this type so that not all values have to be present? const widgetOptions = ['option1', 'o ...

Modify the property of the ChildComponent by utilizing the ViewChild method

I recently started exploring Angular and I've been experimenting with ViewChild and ViewChildren. In one scenario, I have a property called today = new Date() in my Component2. I'm accessing this property in Component1 using ViewChild and continu ...