Retrieve a potential property on a type that may not actually be present and receive the value of undefined

Here is the code snippet I am working with:

type A = {
    x: number;
} | {
    y: number;
}

const a = { x: 0 } as A;
const b = a.x;

After running this code, I encountered the following error message:

Property 'x' does not exist on type 'A'.
  Property 'x' does not exist on type '{ y: number; }'.ts(2339)

The value of b has been inferred as being of type any.

I understand that the property x may not exist on a, but in regular Javascript, trying to access a non-existent property would typically return undefined. Therefore, I expected TypeScript to infer the type as number | undefined.


Let's consider a simpler example:

const bar = ({}).foo;

In this case, bar should ideally be undefined, but it is inferred as type any.


Is there a way to instruct Typescript to provide me with undefined instead of any, and to flag an error when attempting to access a potentially non-existent property?

Answer №1

Typescript Objects Can Have More Properties Than Declared

Objects in Typescript are considered open or extensible, not closed or sealed. This means that a type can have additional properties beyond what is specified in its declaration. Simply because a property is not explicitly mentioned in the type does not mean it cannot exist in a value of that type. This inability to create "exact" object types was discussed in more detail on microsoft/TypeScript#12936.

For instance:

interface Y {
  y: number;
}

Although a value of type Y must include a property y of type number:

let y: Y;
y = {}; // error, Property 'y' is missing in type '{}' but required in type 'Y'.
y = { y: "oops" }; // error, Type 'string' is not assignable to type 'number'.
y = { y: 123 }; // okay

It may appear that adding other properties is restricted. However, assigning an object literal with extra properties results in an error due to excess property checking. But this check serves as more of a linter rule for object literals only and not all values:

const xy = { x: "oops", y: 123 };
y = xy; // okay, no error

This assignment is valid because xy retains the x property, making it accessible somewhere. When assigned as y = xy, xy functions as a value rather than an object literal, thus avoiding an error.

In practice, allowing excess properties is necessary for extending interfaces as intended:

interface XY extends Y {
  x: string;
}

const val: XY = xy; // okay

If properties not stated in every union member could hold any value if present:

type A = { x: number; } | Y;

const a: A = Math.random() < 0.5 ? { x: 123 } : val; // okay

To access properties of a union safely, they need to be declared in each member of the union. If a property might be missing or undefined, specify it as optional or using the impossible never type:

type A = { x: number, y?: never } | { x?: never, y: number };

let a: A;
a = val; // error!

Subsequently, a.x will be either number or undefined:

a = Math.random() < 0.5 ? { x: 123 } : { y: 123 }; // okay
a.x?.toFixed(); // okay

The Use of the in Operator in TypeScript

The limitation of accessing properties of unions unless defined in every member emphasizes that object types aren't precise. The compiler enforces type safety to avoid violations. Yet, despite such checks, some unsoundness exists in TypeScript, leveraging human considerations over full correctness.

An example of unsoundness lies in using the in operator for narrowing unions which can lead to runtime errors:

if ("x" in a) {
  a.x.toFixed(2); // no compiler error
} else {
  a.y.toFixed(2); // no compiler error
}

By checking for the existence of an x key in a, the compiler narrows a to {x: number} allowing access without warning. Despite potential issues, the TypeScript team prioritizes usability over strict type enforcement.

Hence, the use of the in operator before property access provides a workaround, assuming scenarios like XY won't materialize frequently.

Link to Interactive Code Playground

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

Is there a way to determine if a string is empty, even if it contains hard returns?

I am currently working on a function that checks if a string is empty or not, but it seems to be missing the detection of new lines. export const isStrEmpty = function(text: string): boolean { return !text || text.match(/^ *$/) !== null; }; I attempted ...

How can I modify my Axios Post request to receive a 201 status code in order to successfully save the data?

I am facing an issue when attempting to make a POST request locally with Axios on my NodeJS front-end app to my .NET core local server. The server returns a 204 status code and the axios request returns a pending promise. How can I change this to achieve a ...

Analyzing past UTC date times results in a peculiar shift in time zones

When I receive various times in UTC from a REST application, I encounter different results. Examples include 2999-01-30T23:00:00.000Z and 1699-12-30T23:00:00.000Z. To display these times on the front end, I use new Date(date) in JavaScript to convert the ...

Save visuals in the typescript distribution folder

Is there a way to properly include images in a Typescript project? I attempted to include the files or directory in tsconfig.json, but it didn't seem to work as expected. "include": [ "src/resources/**/*.png", // <- ...

Problem with Typescript compilation in lerna package

Presently, my project is structured with lerna/react/TS setup as shown below: . ├── lerna.json ├── package.json ├── packages │ ├── patient │ │ ├── package.json │ │ ├── src │ │ │ └── ...

Experiencing difficulties establishing a connection with my NodeJs server socket and TypeScript

I've been struggling to run the code from this post and I really need some help. The code can be found at: https://medium.com/@mogold/nodejs-socket-io-express-multiple-modules-13f9f7daed4c. I liked the code as it seems suitable for large projects, but ...

Angular dynamic array binding binds to multiple elements rather than just one

In my code, I am working with an array object structured as follows: let myArray=[ { "id":"100", "child1":[ {"id":"xx","Array":[]}, {"id":"yy","Array":[]}, {"id":"zz","Array":[]} ] }, { "id":"200", "child1":[ {"id":"xx","Array ...

Is it recommended to employ cluster connection within my Redis client when utilizing Azure Redis Cluster?

It seems that the Azure documentation on clustering can be a bit confusing. According to the docs: Will my client application need any modifications to support clustering? Once clustering is activated, only database 0 will be accessible. If your client ...

What is the best way to troubleshoot substrings for accurately reading URLs from an object?

While a user inputs a URL, I am attempting to iterate through an object to avoid throwing an error message until a substring does not match the beginning of any of the URLs in my defined object. Object: export const urlStrings: { [key: string]: string } = ...

Is it advisable for a component to handle the states of its sub-components within the ngrx/store framework?

I am currently grappling with the best strategy for managing state in my application. Specifically, whether it makes sense for the parent component to handle the state for two subcomponents. For instance: <div> <subcomponent-one> *ngIf=&qu ...

What is the most efficient method for sharing types within an extensive TypeScript project?

I'm currently developing a complex React application using TypeScript. I have numerous common types defined in separate files, and I find myself importing them every time I need to use them. While this approach is functional, it results in a large num ...

Tips for formatting dates in Angular 6

I am currently working on a function that displays real-time dates based on user input. Currently, when the user enters the input, it is displayed in the front end as follows: 28.10.2018 10:09 However, I would like the date to change dynamically based on ...

Delay the execution until all promises inside the for loop are resolved in Angular 7 using Typescript

I am currently working on a project using Angular 7. I have a function that contains a promise which saves the result in an array as shown below: appendImage(item){ this.imageCompress.compressFile(item, 50, 50).then( result => { this.compressedI ...

Struggling with rotating an image in React

Currently I'm troubleshooting an issue with the rotate button on my React-Avatar Editor. The functionality is not working as intended. For reference, here is a link to the code: https://codesandbox.io/s/example-for-react-avatar-editor-ofoz4 ...

Can you please provide an explanation on the functioning of Dependency Injection in Nestjs?

I have been delving into Nest.js and incorporating it into my project structure. Additionally, I have integrated TypeORM into the mix. The concept of Dependency Injection in Nest.js has me feeling a bit perplexed. Project Structure |-APP_MODULE |-app.co ...

Organize an array based on its ratio

I am attempting to organize an array based on the win and lose ratio of each player. This is how my code currently looks: const array = [{playerName: 'toto', win: 2, lose: 2}, {playerName: 'titi', win: 0, lose: 0}, {playerName: &apo ...

After a successful login, Angular will once again redirect the user to the login page

I successfully implemented the back-end using nest-js to handle authentication with mongo-db. Registration and login are working fine, but I face an issue after logging in with a successful result - when I refresh the page, it redirects me back to the logi ...

Encountering issues with reading undefined properties within azure-maps-animations

Attempting to reimplement the Azure Maps Animations example with TypeScript has been a challenging task for me. Here's the link to the sample: I'm encountering several issues and could really use some assistance. I'll describe my problems a ...

Issue Arising from Printing a Custom Instruction in a Schema Generated Document

When dynamically adding a directive, the directive is correctly generated in the output schema. However, it seems to be missing when applied to specific fields. Here is how the directive was created: const limitDirective = new graphql.GraphQLDirective({ na ...

Increasing a number after a delay in an Angular 2 AppComponent using TypeScript

I'm attempting to create a straightforward Angular2 Application with TypeScript. Despite its apparent simplicity, I'm struggling to achieve my desired outcome. My goal is to display a property value in the template and then update it after 1 sec ...