TypeScript Conditional type failing to detect conditional parameter

I am in need of a simple function that can return either a type "Person" or a type "PersonWithSurname". I have successfully implemented this using a conditional type. However, I am facing difficulties in structuring the result without encountering TypeScript errors.

interface Person {
  name: string;
}

type PersonWithSurname = Person & { 
  surname : string 
};

type PersonNameConditional<T> = T extends true 
  ? PersonWithSurname 
  : Person; 

function getPerson<T extends boolean>(returnSurname: withSurname
  ): PersonNameConditional<T> {
    if (returnSurname) {
      return { name: "John", surname: "Wic-k" } as PersonNameConditional<withSurname>
    } else {
      return { name: "John" } as PersonNameConditional<withSurname>
    }
}

// Implementation is correct ✅

The code above functions properly and IntelliSense accurately displays the returned type based on the parameter received, as demonstrated below.

const result = getPerson(true) // { name: "John", surname: "Wic-k" }: PersonWithSurname
const result = getPerson(false) // { name: "John" }: Person

// All good here ✅

However, I encounter an issue where TypeScript does not recognize the "surname" property from the response.

const requestedSurname: boolean = req.query.withSurname

let result = getPerson(requestedSurname)

if ("surname" in result)
  // Validation successful  ✅
  result.surname = result.surname.replace(/-/g, '')

if (result.surname)
  // ❌ Property "surname" does not exist on type { name : string } | PersonWithSurname
  result.surname = result.surname.replace(/-/g, '')

// Desired scenario
if (requestedSurname) 
  // ❌ Property "surname" does not exist on type { name : string } | PersonWithSurname
  result.surname = result.surname.replace(/-/g, '')

return result;

Is it not possible to effectively utilize the conditional variable in this case?

Answer №1

Regrettably, this cannot be achieved with TypeScript. The language only offers control flow analysis in specific scenarios, none of which apply to your situation. Various GitHub issues such as microsoft/TypeScript#37223, microsoft/TypeScript#54041, and microsoft/TypeScript#54252 demonstrate instances where users expected this behavior but were told that TypeScript does not operate in that manner.


Unlike what might seem logical, TypeScript does not spread control flow analysis across all union-typed values it encounters. Consider an example like:

declare const requestedSurname: boolean;
let result = getPerson(requestedSurname);
// let result: Person | PersonWithSurname
if (requestedSurname) {
    result.surname
} 

The compiler does not try every potential narrowing of `requestedSurname` separately. Instead, it treats true and false possibilities independently, resulting in situations where `result.surname` is either accessed or never executed based on the branch taken. This approach would lead to exponential iterations for complex scenarios that are practically unfeasible.

I once proposed an opt-in feature for enhanced analysis as discussed in microsoft/TypeScript#25051, but this idea was rejected. Unfortunately, developers do not have a way to trigger this behavior as it goes against the core functionality of the compiler.


In reality, TypeScript handles types like `result` and `requestedSurname` as separate unions without any correlation between them. Therefore, if you want to check the type of `result`, direct inspection is required since the association with `requestedSurname` is not maintained by the compiler.

A simple workaround could be:

if ("surname" in result) { result.surname }

This utilizes the `in`-operator narrowing method to distinguish unions based on the presence or absence of a specific property key.

You can test out the code in the TypeScript Playground using this Playground link.

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

Relationship requirement between two attributes of an entity

My goal is to enhance the types for a frontend route configuration object by linking the `path` and `prepare` fields together. This way, the `prepare` function field will receive the appropriate parameters based on the `:keys` in the path. Each route item ...

When all observables have finished, CombineLatest will only execute one time

I've encountered a problem that requires me to use a combination of 3 route observables to open a modal. Here is the logic in my code: combineLatest([obs1, obs2, obs3]) .subscribe(([s1, s2, s3]: any) => { openModal(); }); The issu ...

Implementing setState callback in TypeScript with React Hooks

Hello everyone! I am currently working on defining my component types using TypeScript, and I am faced with a challenge. I am trying to set it up so that I can either pass an array of objects or a callback function containing the previous state. Below is t ...

Inversify is a proven method for effectively injecting dependencies into a multitude of domain classes

Struggling to navigate dependencies and injections in a TypeScript-built rest web service without relying heavily on inversify for my domain classes, in line with the dependency inversion principle. Here's an overview of the project structure: core/ ...

Ways to sort through a Unix timestamp

Recently, I encountered an issue with my Ionic mobile application. It has a search button and filter feature that works well for filtering words. However, the problem arises when it comes to filtering dates displayed in the app as timestamps using Angular ...

The RouteParams encounter a problem because it is unable to resolve all parameters

I'm encountering an issue with the RC3 router. The error message I am receiving is: Can't resolve all parameters for RouteParams: (?). Below is my code: //route.ts import {provideRouter, RouterConfig} from '@angular/router'; import {H ...

Encountering unexpected errors with Typescript while trying to implement a simple @click event in Nuxt 3 projects

Encountering an error when utilizing @click in Nuxt3 with Typescript Issue: Type '($event: any) => void' is not compatible with type 'MouseEvent'.ts(2322) __VLS_types.ts(107, 56): The expected type is specified in the property ' ...

Error: Undefined property 'pipe' cannot be read - Could this be due to missing configuration in my unit tests for Angular and ActivatedRoute?

I'm currently encountering a problem with an Observable that is based on the paramMap of my activatedRoute this.current$ = this.route.paramMap.pipe(map(paramMap => paramMap.get('title'))); Everything works fine on the front-end, but now ...

The data in the object bound to [ngmodel] does not update properly when changed within a method

Hello everyone, I am in need of some assistance with a puzzling issue. Currently, I am generating a set of inputs using JSON. When I make changes to the data in the web interface, everything works fine. The problem arises when I try to modify the value ...

Unable to modify the Express Request User type, however, I have the ability to incorporate new attributes to Request object

Encountering a familiar issue with what appears to be a simple fix. The Express Request object includes a user property that is specified as Express.User (an empty object). Attempting the common approach to redefining it: // index.d.ts import { User as P ...

The process of running npx create-react-app with a specific name suddenly halts at a particular stage

Throughout my experience, I have never encountered this particular issue with the reliable old create-react-app However, on this occasion, I decided to use npx create-react-app to initiate a new react app. Below is a screenshot depicting the progress o ...

Is there a way for me to implement a feature akin to the @deprecated annotation?

In my TypeScript Next.js application, I rely on Visual Studio Code for coding purposes. One feature that I particularly enjoy is the ability to add a JSDoc annotation of @deprecated above a function, which then visually strikes through the function name t ...

Leveraging symbols as object key type in TypeScript

I am attempting to create an object with a symbol as the key type, following MDN's guidance: A symbol value may be used as an identifier for object properties [...] However, when trying to use it as the key property type: type obj = { [key: s ...

Transforming a material-ui component from a class to a function

Currently, I am in the process of learning material-ui, and it seems that most of the code examples I come across are based on classes. However, the new trend is moving towards using functions instead of classes due to the introduction of hooks. I have be ...

Expo project encountering issues with nested navigation in React-Native

When configuring the header in my app using React Native Stack Navigation and nesting a Drawer Navigation inside of it, I encountered a strange issue. While everything worked fine in the android emulator, opening the app using Expo resulted in nothing but ...

Can a custom structural directive be utilized under certain circumstances within Angular?

Presently, I am working with a unique custom structural directive that looks like this: <div *someDirective>. This specific directive displays a div only when certain conditions are met. However, I am faced with the challenge of implementing condit ...

Is there a way for me to maintain a consistent layout across all pages while also changing the content component based on the URL route in Next.js?

I'm currently working with Typescript and Next.js My goal is to implement a unified <Layout> for all pages on my website. The layout comprises components such as <Header>, <Footer>, <Sidenav>, and <Content>. Here is the ...

What is the reason that a generic type which extends an interface cannot be assigned to a corresponding object?

I encountered the following error message [ts] Type '{ type: string; }' is not assignable to type 'A'. when using this code snippet interface Action { type: string; } function requestEntities<A extends Action>(type: string ...

Feeling lost with the concept of getcontext in js/ts and uncertain about how to navigate through it

Recently, I've been encountering undefined errors in my browser and can't seem to figure out how to resolve them. It seems that the usage of the keyword "this" in JavaScript and even TypeScript is causing quite a bit of confusion for me. Let&apo ...

How can I avoid transpiling in TypeScript when setting ESNext as the target in tsconfig.json?

I came across a similar question that I thought would be helpful: How to maintain ES6 syntax when transpiling with Typescript, but unfortunately, it didn't solve my issue... It's slightly different. When running yarn tsc --project tsconfig.json ...