Determining the parent type in Typescript by inferring it from a nested member

Typescript has the ability to infer the type of a value based on queries made within if statements. For instance, the type of one member of an object can be deduced based on another:

type ChildType = 'a' | 'b';

type Child<T extends ChildType> = T extends 'a' ? { type: 'a', color: 'blue' } : T extends 'b' ? { type: 'b', color: 'red' } : never;

interface Parent<T extends Child<ChildType>> {
    child: T extends Child<infer R> ? Child<R> : never;
}


function test<T extends Parent<Child<any>>>(parent: T) {
    if (parent.child.type === 'a') {
        parent.child.color === 'red'; // raises error as it should be blue
    }
}

However, when trying to achieve the same functionality by checking nested members of a type, the behavior seems different.

type ChildType = 'a' | 'b';

interface BaseParent<T extends Child<ChildType>> {
    child: T;
}

interface Child<T extends ChildType> {
    type: T;
}

type Parent<T extends ChildType>
    = T extends 'a' ? { color: 'blue' } & BaseParent<Child<'a'>>
    : T extends 'b' ? { color: 'red', opacity: 2 } & BaseParent<Child<'b'>>
    : never;

function test<T extends Parent<ChildType>>(parent: T) {
    if (parent.child.type === 'a') {
        parent.color === 'red'; // no error thrown even though there should be
    }
}

This issue can be resolved using type guards, however I am exploring alternative methods that do not require them.

Answer №1

Initially, it's important to note that having parent data dependency on its child may not align with proper architecture principles. Generally, dependencies should flow downwards rather than upwards. It is crucial to consider this aspect from the beginning.

However, if you still wish to pursue this approach, one way of implementing it could be illustrated through the following example involving animals:


type AcceptedAnimal = 'dogs' | 'cats';

interface ParentBase {
  accepts: AcceptedAnimal
}

interface Cat {
  meows: boolean;
}

interface Dog {
  race: string;
}

type Parent = DogOwner | CatOwner;

interface DogOwner extends ParentBase {
  animal: Dog
}

interface CatOwner extends ParentBase {
  animal: Cat;
}

function isDogOwner(parent: ParentBase): parent is DogOwner {
  return parent.accepts === 'dogs';
} 

function test(parent: Parent) {
  if (isDogOwner(parent)) {
    parent.animal.race; // This is okay for a dog owner
    return;
  }

  parent.animal.meows; // This is acceptable for a cat owner
}

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

Remove the package from the @types folder within the node_modules directory

I currently have the 'mime' library in my node_modules directory and I am looking to completely remove it from my project, along with its @types files. The reason for this is that the old mime package is not functioning correctly for me, so I wan ...

TypeScript's HashSet Implementation

I'm working on a simple TypeScript task where I need to extract unique strings from a map, as discussed in this post. Here's the code snippet I'm using: let myData = new Array<string>(); for (let myObj of this.getAllData()) { let ...

Custom typings for Next-Auth profile

I'm experiencing an issue with TypeScript and Next Auth type definitions. I followed the documentation guidelines to add my custom types to the NextAuth modules, specifically for the Profile interface in the next-auth.d.ts file. It successfully adds t ...

What is the correct regex expression for validating decimal numbers between 1.0 and 4.5?

I'm having trouble creating an expression to validate numbers between 1.0 to 4.5 accurately. The current expression I'm using is not working as intended: /^[1-4]{0,1}(?:[.]\d{1,2})?$/ The requirement is to only allow values between 1.0 to ...

Using Angular filter pipe to customize markers in Leaflet maps

I am currently working on a select element called district, which lists all the districts in the city. My objective is to apply a filter that will dynamically display only the leaflet markers corresponding to the selected district on the map. Any suggesti ...

How can users create on-click buttons to activate zoom in and zoom out features in a Plotly chart?

I am currently working on an Angular application where I need to implement zoom in and zoom out functionality for a Plotly chart. While the default hoverable mode bar provides this feature, it is not suitable for our specific use case. We require user-cr ...

Import statement cannot be used except within a module

I am currently facing an issue with running the production version of my code. I have Node 20.10 and TypeScript 5 installed, but for some reason, I am unable to run the built version. Here are the contents of my package.json and tsconfig.json files: { & ...

A Project built with React and TypeScript that includes code written in JavaScript file

Is it an issue when building a React project with Typescript that includes only a few JS components when moving to production? ...

Setting up next-i18next with NextJS and Typescript

While using the next-i18next library in a NextJS and Typescript project, I came across an issue mentioned at the end of this post. Can anyone provide guidance on how to resolve it? I have shared the code snippets from the files where I have implemented the ...

Trigger a table refresh to display updated information

When I select the select option to make an API call for a new datasource, my table appears before fetching data and it remains empty. Additionally, if I choose another select option, it displays the previous data from the previous selection. I have attemp ...

Encountering a TypeError while attempting to retrieve an instance of AsyncLocalStorage

In order to access the instance of AsyncLocalStorage globally across different modules in my Express application, I have implemented a Singleton class to hold the instance of ALS. However, I am wondering if there might be a more efficient way to achieve th ...

Disabling the intellisense feature for locale suggestions in Monaco is recommended

Switch the keyboard language to a different one (in this case Japanese using alt + shift), and when typing in Monaco editor, an intellisense menu appears with options to remove and search. Monaco Editor Version: V0.33.0 https://i.stack.imgur.com/SIyeV.pn ...

How to utilize FileReader for parsing a JSON document?

I'm currently facing an issue while attempting to read and copy a JSON file uploaded by the user into an array. When using .readAsText(), the returned data includes string formatting elements like \" and \n. Is there a way to utilize FileRe ...

Exploring the compatibility of Next.js with jest for utilizing third-party ESM npm packages

Caught between the proverbial rock and a hard place. My app was built using: t3-stack: v6.2.1 - T3 stack Next.js: v12.3.1 jest: v29.3.1 Followed Next.js documentation for setting up jest with Rust Compiler at https://nextjs.org/docs/testing#setting-up-j ...

What is the best way to sort through this complex array of nested objects in Typescript/Angular?

tableData consists of an array containing PDO objects. Each PDO object may have zero or more advocacy (pdo_advocacies), and each advocacy can contain zero or more programs (pdo_programs). For example: // Array of PDO object [ { id: 1, ...

Unusual class title following npm packaging

Currently, I am working on developing a Vue 3 library with TypeScript. We are using Rollup for bundling the library. Everything works as expected within the library itself. However, after packing and installing it in another application, we noticed that th ...

The @angular/fire package is unable to locate the AngularFireModule and AngularFireDatabaseModule modules

I am facing some challenges while trying to integrate Firebase Realtime Database into my Angular project. Specifically, I am encountering difficulties at the initial step of importing AngularFireModule and AngularFireDatabaseModule. To be more specific, I ...

Error TS7053 occurs when an element is given an 'any' type because a 'string' expression is being used to index an 'Object' type

When attempting to post data directly using templateDrivenForm and retrieve data from Firebase, I encountered the following type error message. Here are the relevant parts of my code: // Posting data directly using submitButton from templateDrivenForm onC ...

Throw TypeError: The `pipe` property of `ngrx/store` is undefined during testing

Here is the code snippet from my TypeScript file: this.store.pipe(select(subscribe.getRegCategories)).pipe(takeUntil(this.ngUnsubscribe)).subscribe(data => { if (data && data.length) { this.allRegCategories = data; ...

Transmit information using JSON format in Angular 8 using FormData

i am struggling with sending data to the server in a specific format: { "name":"kianoush", "userName":"kia9372", "email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bcd7d5ddd8ce85...@example.com</a>" } H ...