How can one prevent the error message saying "Type 'foo' does not exist on property" and instead return undefined?

I have a similar type defined as follows:

interface Foo {
  bar: string
  baz: number
}

My goal is to ensure that both members are either present or neither. I attempted type X = Foo | {}, but encountered the error

property 'bar' does not exist on type X
when trying to access the property (the actual result being undefined). After some exploration, I found that this type achieves the desired behavior:

type X = Foo | { [k in keyof Foo]?: undefined }

This approach works well for my current scenario, but it could become more complex with additional types. Is there a more concise method to achieve object access without triggering the

property 'bar' does not exist on type X
error, without resorting to type guards or casting to any?

Solution using never

A suggestion in the comments proposed using Record<keyof Foo, never>, however, this did not produce the expected result. A second suggestion of

Partial<Record<keyof Foo, never>>
yielded success for the original problem.

interface Foo {
  a: number
}

type X = Foo | Record<keyof Foo, never>

const x: X = {} // Error: Type '{}' is not assignable to type 'X'.
console.log(x.a)

type Y = Foo | Partial<Record<keyof Foo, never>>

const y: Y = {}
console.log(y.a)

Extended Example with multiple types

Expanding on my initial question regarding usage with multiple types, here is an example:

interface X {
  a: string
  b: string
}

interface Y {
  c: string
  d: string
}

type Z = X | Y | Partial<Record<keyof (X & Y), never>>

// These assignments should trigger errors, but they don't.
const z: Z = {a: "x", b: "x", c: "x"}
const z: Z = {a: "x", b: "x", c: "x", d: "x"}

Interestingly, the last assignments seem to work due to X | Y behavior, rather than the record. This behavior persists even if I use type instead of interface. It appears that this may be the most optimal solution available.

Answer №1

Unfortunately, TypeScript lacks a straightforward way to prevent extra properties in object types; these types are intentionally open and extendible, without an easy utility type like Exact<T> to seal them off. This limitation is discussed in detail in the relevant feature request for Exact<T> on GitHub (microsoft/TypeScript#12936), along with possible workarounds.

If we had access to Exact<T>, it would be useful to transform Foo into

Exact<Foo> | Exact<{}>
, creating a union that ensures either a precise instance of Foo or an empty object. Extending this concept to a union like X | Y would involve converting it to
Exact<X> | Exact<Y> | Exact<{}>
, effectively disallowing any additional properties. But since Exact<T> doesn't exist, such implementations are not feasible at present.


To mimic the functionality of prohibiting extra properties, one approach is to explicitly define the keys to restrict and designate them as optional properties with the impossible never type.

A utility type called AllKeys<T> can be crafted to aggregate all keys from a particular type, including different union members:

type AllKeys<T> =
  T extends unknown ? keyof T : never;

This technique leverages a distributive conditional type to break down T into its union parts, extract the keys from each part, and merge them back together cohesively.

By combining a union type T with a specified list of keys

K</code, the customized <code>Exclusify<T, K>
type enforces strict property limitations based on those keys:

type Exclusify<T, K extends PropertyKey> =
  T extends unknown ? (T & { [P in Exclude<K, keyof T>]?: never }) : never;

Analogous to the previous pattern, another variation named

ExclusifyOrNone<T></code excludes excess properties across all members of the type <code>T
, incorporating allowances for an empty object as well:

type ExclusifyOrNone<T> =
  Exclusify<T, AllKeys<T>> |
  Exclusify<{}, AllKeys<T>>

Although consolidating this into

Exclusify<T | {}, AllKeys<T>></code might seem viable, splitting it into two distinct components prevents unwarranted collapses if <code>T</code extends <code>{}
. This separation ensures robust exclusification before any potential data merging.


Let's try testing this methodology using some examples:

interface Foo { bar: string; baz: number; }
type FooOrNone = ExclusifyOrNone<Foo>;
/* Output:
  (Foo & {}) | 
  { bar?: undefined; baz?: undefined; } 
*/

let f: FooOrNone;
f = { bar: "", baz: 1 }; // Ok
f = {}; // Ok
f = { bar: "" }; // Error

For a more complex scenario involving a union type like X | Y:

interface X { a: string; b: string }
interface Y { c: string; d: string }

type Z = ExclusifyOrNone<X | Y>
/* Output:
  (X & { c?: undefined; d?: undefined; }) | 
  (Y & { a?: undefined; b?: undefined; }) | 
  { a?: undefined; b?: undefined;
    c?: undefined; d?: undefined; } */

let z: Z;
z = {}; // Ok
z = { a: "", b: "" }; // Ok
z = { c: "", d: "" }; // Ok

z = { a: "", b: "", c: "", d: "" }; // Error
z = { a: "" }; // Error
z = { a: "", b: "", c: "" }; // Error

The results appear promising, successfully rejecting non-conforming keys while permitting an empty object within the specified constraints.

Custom Playground link here

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

I am interested in placing nested type names within the initial argument of the setValue function in react-hook-form

I am trying to achieve My goal is to prevent a typescript error from occurring when passing "profile.firstName" as the second argument to the onChange function. Error Explanation The error message indicates that arguments of type '" ...

The journey of communication: uncovering the essence of @input between parent and

I'm diving into Angular and currently working on the @Input phase. Within my main app, there's a child component. Inside app.component.ts, I've declared a test variable that I wish to pass from app.component.ts to child.component.ts. // ap ...

The current version of Firebase functions is not reflecting the most recent modifications when running "firebase serve"

Exploring firebase functions has been a fun journey for me. Everything works smoothly when I deploy to the firebase server using the command firebase deploy --only functions. However, I wanted to test my functions locally before deploying them, and encount ...

combine string inputs when ng-click is triggered

Is there a way to pass a concatenated string using ng-click to MyFunction(param: string)? I have tried but so far, no luck: <input id="MeasurementValue_{{sample.Number}}_{{$index}}" ng-click="Vm.MyFunction('MeasurementValue_{{sample.Number ...

Is there a way for me to determine if there are elements in one object array that exist in another object

Is there a method to check if two object arrays have any common elements, and if so, find the intersecting object? Similar to a Contains function. For instance, in the provided example, ProductId3 in Object Array 1 is also present in Object Array 2. I&apo ...

Creating a unique user interface for VSCode extension

Recently, I've delved into the world of developing extensions for Visual Studio. Unfortunately, my expertise in TypeScript and Visual Studio Code is quite limited. My goal is to create an extension that mirrors the functionality of activate-power-mod ...

Ways to implement debounce in handling onChange events for input fields in React

I've been attempting to implement debounce functionality in my React app without relying on external libraries like lodash or third-party node modules. I've tried various solutions found online, but none have worked for me. Essentially, in the h ...

Activate the child for an update

Welcome! I am a newcomer to Angular and would greatly appreciate any assistance. The parent component of my picker has the ability to create various rules for each option. However, these rules are dynamic and can change frequently. I need to ensure that ...

Is there a Typescript function error that occurs when attempting to rename a key, stating it "could potentially be instantiated with a different subtype"?

I am currently working on a Mongoify type that accepts a type T, removes the id key, and replaces it with an _id key. type Mongoify<T extends {id: string}> = Omit<T, "id"> & { _id: ObjectId }; function fromMongo<T extends ...

How can Angular display an alert when no items are visible?

I want to display a message saying "Item doesn't exist" when the item is not found. Suppose this is my list: user 0 user 1 user 2 The following code displays the list above: <ng-container *ngFor="let user of users | async; let i = index"> ...

"Obtaining subnet identification using the name or CIDR: A step-by-step

I am seeking help on retrieving the subnet id based on subnet name or cidr in order to deploy a nat gateway. Can someone provide guidance on how to obtain the subnet id? Alternatively, does anyone have any best practices for utilizing typescript function ...

Utilize JSX attributes across various HTML elements

I'm looking for a solution to efficiently add JSX attributes to multiple elements. Here are the example attributes I want to include: class?: string; id?: string; style?: string; And here are the example elements: namespace JSX { interface Int ...

Angular child component displaying of table data is not looping properly

Currently, I am using Angular 13 along with Bootstrap 3 to develop an application that consists of two main components: form-component (dedicated to inputting user details) and list-component (designed to showcase all data in a table format). Within the HT ...

Is there a way to prevent passing the mouseover event to children elements while still allowing the parent element to respond to the event across its entire area?

I am working with a dynamically generated list that contains complex components which need to perform actions on mouseover. With Angular, I attempted to implement this functionality by using (mouseover)="onhover($event)" and (mouseout)="onhover($event)" o ...

Guide on exporting Excel data using Angular and TypeScript

My project involves managing a table of information that includes fields for FirstName, LastName, PhoneNumber, Age, and Date. I've created a function that allows me to export the data to an Excel file, but I only want to export specific fields like Fi ...

How can we utilize Typescript to check if the intern 4 page has finished loading?

I've managed to set up a function in intern 4 using TypeScript that waits for the page to load. However, there are instances where it doesn't work and throws a TimeOutError even when I catch the error within the function. Can someone please take ...

Tips for anticipating a string that begins with a particular variable

One of the challenges I'm facing involves a simple function that creates a string from a complex object. To illustrate, consider the following implementation: public generateMessage(property: string): string { return `${property} more text.`; } ...

The reason for not being able to access the template DOM element within the constructor of the component

Exploring RxJS and attempting to create a basic stream of button clicks, I tried the following approach: export class AppComponent { button : HTMLElement = document.querySelector('button'); refreshClickStream$ = Observable.fromEvent(th ...

Using Typescript allows for the possibility of invoking a function with an incorrect parameter type

In the world of software development, there exists a function with the following signature: const validate = (input?: string) => void Behold, there is a Component with props type: type ValidateButtonProps = { onClick: () => void; } And lo, anothe ...

The template literal expression is being flagged as an "Invalid type" because it includes both string and undefined values, despite my cautious use of

I am facing an issue with a simple component that loops out buttons. During the TypeScript build, I encountered an error when calling this loop: 17:60 Error: Invalid type "string | undefined" of template literal expression. In my JSX return, I ...