Using optional chaining with TypeScript types

I'm dealing with a complex data structure that is deeply nested, and I need to reference a type within it. The issue is that this type doesn't have its own unique name or definition. Here's an example:

MyQuery['system']['errors']['list'][number]

I generate the MyQuery type automatically from a graphql query using graphql-codegen. I'm trying to determine the type of a single error, but I face two challenges:

  1. All intermediate values are nullable.
  2. I lack a distinct identifier for the error in my auto-generated types.

I've attempted the following solutions:

  1. This method works, but it's difficult to read:
type Error = NonNullable<NonNullable<NonNullable<MyQuery>['system']>['errors']>['list'][number]
  1. This approach doesn't work (?.['field'] also fails)
type Error = MyQuery?['system']?['errors']?['list']?[number]
  1. This solution works, but introduces unnecessary variables:
const error = queryResult?.system?.errors?.list?.[0]
type Error: typeof error
  1. Although somewhat effective, this method results in non-null fields inside Error, which is not desired
import { DeepNonNullable } from 'utility-types'

type Error = DeepNonNullable<MyQuery>['system']['errors']['list'][number]

In essence, I am seeking a simpler way to implement "optional chaining for types" in TypeScript. Given that my API contains a lot of null values, it would be incredibly beneficial if there was a more straightforward approach than using multiple NonNullable<T> statements.

Answer №1

Is there a simpler way to implement "optional chaining for types"?

Unfortunately, at this time, there is no built-in method to "optionally chain" deeply nested types. However, it is possible to emulate this functionality using a complex recursive conditional generic type and paths. First, you would need a reusable helper function to handle index signatures:

type _IndexAccess<T, U extends keyof T, V extends string> = V extends "number" 
    ? Exclude<T[U], undefined> extends { [x:number]: any } ? 
        Exclude<T[U], undefined>[number]
        : undefined
    : V extends "string" ?
        Exclude<T[U], undefined> extends { [x:string]: any } ?
            Exclude<T[U], undefined>[string]
            : undefined
    : V extends "symbol" ?
        Exclude<T[U], undefined> extends { [x:symbol]: any } ?
            Exclude<T[U], undefined>[symbol]
            : undefined
    : undefined;

Next, you can create a helper type for recursively navigating the nested type using infer and template literal types to process the path:

type DeepAccess<T, K extends string> = K extends keyof T 
    ? T[K] 
    : K extends `${infer A}.${infer B}` 
        ? A extends keyof T 
            ? DeepAccess<Exclude<T[A], undefined>, B>
            : A extends `${infer C}[${infer D}]`
                ? DeepAccess<_IndexAccess<T, C extends keyof T ? C : never, D>, B>
                : undefined
    : K extends `${infer A}[${infer B}]` 
        ? A extends keyof T 
            ? B extends keyof T[A] 
                ? T[A][B] 
                : _IndexAccess<T, A, B>       
            : undefined
    : undefined;

While not the most elegant solution, this approach allows for seamless lensing into nested types:

type MyQuery = {
    system?: {
        errors?: {
            list?: [{
                answer: 42,
                questions: { known: false }[]
            }]
        }
    }
};

// false
type t1 = DeepAccess<MyQuery, "system.errors.list[number].questions[number].known">;

// [{ answer: 42; questions: { known: false; }[]; }] | undefined
type t2 = DeepAccess<MyQuery, "system.errors.list">;

// 42
type t3 = DeepAccess<MyQuery, "system.errors.list[number].answer">;

// undefined
type t4 = DeepAccess<MyQuery, "system.errors.list.unknown">;

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

Exploring the mechanics behind optional chaining, known as the Elvis operator, in TypeScript. How does this feature operate within the

Can someone explain the concept of optional chaining (Elvis operator) in TypeScript and how it can be used effectively? public static getName(user: IUser){ if(user.firstName != null && user.firstName != ""){ return user.firstName; } ...

Associate a unique identifier string with a randomly generated integer identifier by Agora

For my current web project, I am utilizing a String username as the UID to connect to the channel in an Agora video call. However, I now need to incorporate individual cloud recording by Agora into the project. The challenge lies in the fact that cloud r ...

Unable to access structuredClone on the global object within a Node.js application

structuredClone is causing issues in my NodeJS application. Whenever I try to utilize it, I encounter the error: structuredClone is not defined nodejs. To troubleshoot, I created a simple file and executed the following: console.log({ globals: Object. ...

How to Restrict the Number of Rows Displayed in an Angular 4 Table

Currently, I am faced with a situation where I have a lengthy list of entries that I need to loop through and add a row to a table for each entry. With about 2000 entries, the rendering process is slowing down considerably. Is there a way to limit the disp ...

Display a loading indicator with the shortest possible delay whenever utilizing the React Router v6 Link functionality

Integrate React and Router v6 App.tsx: const Page1 = lazy(() => pMinDelay(import('./views/Page1'), 500)) const Page2 = lazy(() => pMinDelay(import('./views/Page2'), 500)) return ( <Suspense fallback={<Loading/>}gt ...

Accessing video durations in Angular 2

Can anyone help me with retrieving the video duration from a list of videos displayed in a table? I attempted to access it using @ViewChildren and succeeded until encountering one obstacle. Although I was able to obtain the query list, when attempting to a ...

Angular 1.5 component causing Typescript compiler error due to missing semi-colon

I am encountering a semi-colon error in TypeScript while compiling the following Angular component. Everything looks correct to me, but the error only appears when I insert the this.$routeConfig array: export class AppComponent implements ng.IComponentOp ...

Property of object (TS) cannot be accessed

My question relates to a piece of TypeScript code Here is the code snippet: export function load_form_actions() { $('#step_2_form').on('ajax:before', function(data) { $('#step_2_submit_btn').hide(); $(&ap ...

Utilize Array.push to add 2 new rows to a table using Angular 4

I have two arrays that are almost identical, except for two items which are the fakeDates: this.prodotti.push({ idAgreement: this.idAgreement,landingStatus: this.landingStatus, landingType: this.landingType, startDate: this.startDate, expirationDate: thi ...

Eliminate the usage of JSON.stringify in the Reducer function

I have a system where I store chat messages in a dictionary with the date as the key and a list of messages as the value. Whenever a new message is added, the following code snippet is executed. Is there a way to enhance the existing code to eliminate the ...

Steps for configuring type definitions for an Apollo error response

Apollo's documentation explains that an error response can take the following form: { "data": { "getInt": 12, "getString": null }, "errors": [ { "message": "Failed to get s ...

What is the best way to configure webpack for ng build instead of ng serve?

My .NET web application is hosted in IIS and it also hosts an Angular application. This setup requires both applications to be served on the same port by IIS, primarily because they share the same session cookie. Additionally, they are integral parts of th ...

The switch statement and corresponding if-else loop consistently produce incorrect results

I'm currently facing an issue where I need to display different icons next to documents based on their file types using Angular framework. However, no matter what file type I set as the fileExtension variable (e.g., txt or jpg), it always defaults to ...

The concept of overloaded function types in TypeScript

Is it possible to create an overloaded function type without specifying a concrete function? By examining the type of an overloaded function, it appears that using multiple call signatures on an interface or object type is the recommended approach: functi ...

What could be the reason my component is not displaying the ContentChild associated with a directive?

It appears that utilizing a directive to target a content child from another directive is the recommended approach (source). However, why isn't my component able to recognize the component marked with the directive? ./my.component.ts import { Comp ...

Setting the desired configuration for launching an Aurelia application

After creating a new Aurelia Typescript application using the au new command from the Aurelia CLI, I noticed that there is a config directory at the root of the project. Inside this directory, there are two files: environment.json and environment.productio ...

"Production mode is experiencing a shortage of PrimeNG(Angular) modules, while they are readily accessible in development

I've been diligently working on an Angular application that heavily relies on PrimeNG as the UI component framework. Initially, I had no issues deploying my app with Angular version 9 and PrimeNG version 8. However, a while ago, I decided to upgrade t ...

Angular2 RC definitions are not recognized by tsc

Currently, I am utilizing angular version 2.0.0-rc.1, however, I am encountering issues with the typescript compiler (Typescript version 1.8.10). Whenever I run tsc on my project, I am bombarded with numerous errors similar to this one: app/app.componen ...

What is the reason a type is able to cast to an indexed collection when it is inferred, while an explicit type that seems identical is

I am puzzled by why my inferred types are considered as instances of my more general collection type while my explicit types are not. My goal was to: Have a specific part of my application work with tightly defined collections (e.g., IParents vs IBoss ...

Angular Material 2: Tips for Differentiating Multiple Sortable Tables in a Single Component

Greetings, esteemed members of the Stackoverflow community, As per the Angular Material 2 documentation, it is mentioned that you can include an mdSort directive in your table: Sorting The mdSort directive enables a sorting user interface on the colum ...