Verify if TypeScript object contains a specific key dynamically without the need for a custom type guard

Using TypeScript's in keyword allows us to check if an object contains a specific key in a type-safe manner when the key is defined as a string literal:

function guardHasTest <Data extends object> (
  value: Data
): Data & Record<'test', unknown> {
  if (!('test' in value)) {
    throw new Error('Missing key')
  },
  return value
}

However, if the key is dynamic and not a string literal, the same approach does not narrow down the type:

function guardHasKey <Data extends object, Key extends string> (
  value: Data,
  key: Key
): Data & Record<Key, unknown> {
  if (!(key in value)) {
    throw new Error('Missing key')
  }
  return value
  // Type 'Data' is not assignable to type 'Data & Record<Key, unknown>'.
  Type 'object' is not assignable to type 'Data & Record<Key, unknown>'.
    Type 'object' is not assignable to type 'Data'.
      'object' is assignable to the constraint of type 'Data', but 'Data' could be instantiated with a different subtype of constraint 'object'.
        Type 'Data' is not assignable to type 'Record<Key, unknown>'.
          Type 'object' is not assignable to type 'Record<Key, unknown>'.ts(2322)
}

Avoiding custom type guards is preferred due to their lack of type safety in logic:

export function isKeyOf<T, const Key extends string>(
  obj: T,
  key: Key
): obj is T & Record<Key, unknown> {
  return true // No error, even though the logic is not correct
}

The challenge is how to create a function that can dynamically verify if an object contains a key in a completely type-safe manner?

Answer №1

The functionality of guardHasTest() is made possible by leveraging the support for unlisted property narrowing with the in operator, which was introduced in TypeScript 4.9 through the release of microsoft/TypeScript#50666. This feature addresses an issue that would have previously resulted in the same error seen in guardHasKey(), prior to version 4.9.

The pull request at microsoft/TypeScript#50666 was created in response to a feature request logged at microsoft/TypeScript#21732. It caters to keys of a literal type like "test", but does not tackle cases involving generic keys:

Although this PR resolves #21732, it does not handle scenarios where key in obj involves a generic type for key. When the key has a non-finite type, such as string, the only verifiable outcome post a key in obj check is that obj[key] should be a valid expression (of type

unknown</code), provided neither <code>key
nor obj have been altered since the check. While this can be implemented in certain conditional branches like an if statement, it's not feasible across all control flow scenarios.

This limitation stems from the challenges associated with implementing in narrowings for non-literal keys while ensuring compatibility with various instances of control flow analysis. Should you desire an alternative approach, submitting a feature request remains an option, albeit with cautious expectations regarding its adoption.


Consequently, there presently exists no straightforward method for the compiler to automatically verify the correctness of guardHasKey(). Considerations arise on how

guardHasKey(obj, Math.random()<0.5 ? "a" : "b")
should be narrowed. Should it align with
typeof obj & {a: unknown; b: unknown}
per Record? Or rather
typeof obj & ({a: unknown} | {b: unknown})
, or another configuration? This predicament mirrors hurdles hindering efforts to mend microsoft/TypeScript#13948.)

Currently, employing a type assertion to compel compilation of the return line or crafting a bespoke type guard function akin to the one within microsoft/TypeScript#21732 are viable alternatives. Nevertheless, these methods lack absolute assurance of type safety endorsed by the compiler. Here lies the essence of such features – enabling users to convey insights beyond the compiler’s scope. Though one may strive for purity in type guarding, remember that TypeScript doesn't guarantee 100% compiler-verified type safety, and customized type guard functions stand as crucial components for segregating unsafe aspects (function implementation) from secure segments (function callers).

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

Struggling to convert a JSON response into an object model using TypeScript in Angular?

I'm encountering a problem when trying to convert a JSON response into an object. All the properties of my object are being treated as strings, is that normal? Below is my AJAX request: public fetchSingle = (keys: any[]): Observable<Medal> =&g ...

Removing a method signature from a type that extends a function returning any in TypeScript

I am currently developing an API in typescript where I aim to return a function that includes multiple functions as properties, one of which is the same function. My approach to achieving this includes: type MainFunc = () => PublicInterface type Publi ...

The type '{ children: Element[]; }' does not include the properties 'location' and 'navigator' that are present in the 'RouterProps' type

Struggling to implement React Router V6 with TypeScript, encountering a type error when including Routes within the `<Router />` component. The error message indicates that the children property passed to the Router is of an incorrect type, despite u ...

Can Bun automatically bundle my TypeScript files when I save them in VS Code?

Is it feasible for Bun to bundle my TypeScript upon saving a file in VS Code? The instruction manual suggests running bun run index.ts in the command line and including it in the package.json in this manner. However, I am unsure how to automate this proce ...

What causes the Angular child component (navbar) to no longer refresh the view after a route change?

Hello everyone, I'm excited to ask my first question here. Currently, I am working on developing a social network using the MEAN stack and socket.io. One of the challenges I am facing is displaying the number of unread notifications and messages next ...

Exploring the integration of React.Components with apollo-client and TypeScript

I am in the process of creating a basic React component using apollo-client alongside TypeScript. This particular component is responsible for fetching a list of articles and displaying them. Here's the code: import * as React from 'react' ...

Tips for showcasing with [displayWith] in Material2's AutoComplete feature

My API returns an array and I am using Material2#AutoComplete to filter it. While it is working, I am facing an issue where I need to display a different property instead of the currently binded value in the option element. I understand that I need to uti ...

Script execution is disabled on this system preventing the loading of content - ANGULAR V14

Every time I try to run my Angular project or any ng command, I keep encountering the following error message: ng : File C:\Users\achra\AppData\Roaming\npm\ng.ps1 cannot be loaded because running scripts is disabled on this ...

The Angular Observable continues to show an array instead of a single string value

The project I am working on is a bit disorganized, so I will try to explain it as simply as possible. For context, the technologies being used include Angular, Spring, and Maven. However, I believe the only relevant part is Angular. My goal is to make a c ...

Creating several light beams from a rotated structure

My current challenge involves shooting multiple rays from a rotating mesh in various directions targeting points on a circle divided by the number of rays. To assist with debugging, I have added ArrowHelpers for each ray with a goal for the arrows to turn ...

Extracting an array from an HTTP response in Angular/Typescript using the map operator or retrieving a specific element

Q1: How can I extract an array of objects from a http response using map in Angular? Q2: Is there a way to retrieve a specific object from a http response by utilizing map in Angular? Below are the example URL, sample data, CurrencyAPIResponse, and Curre ...

The issue with npm modules not appearing in EMCA2015 JS imports persists

I am currently in the process of developing a mobile application with Nativescript using the Microsoft Azure SDK. To get started, I installed the SDK via npm by running this command: $ npm install azure-mobile-apps-client --save However, upon attempting ...

Transmit the timestamp information to Supabase using Next.js

Hello there! I'm facing an issue while attempting to send time data to supabase using next.js. Unfortunately, I haven't been successful in sending the data yet. I suspect that the problem lies in the type not being valid for supabase, but I&apos ...

Type of Express middleware not defined

As I work on developing an authentication middleware for my express server, I encounter an issue where there are no Type errors in my IDE, but during the compilation process, I face a TypeError: Cannot read properties of undefined (reading protect) error. ...

What is the best way to eliminate commas from an array within an Angular project?

I am attempting to retrieve a list of actors from movies; however, in the database I created, each actor's name has a comma at the end of the string. When calling the array, the content shows up with double commas next to each other. I need help figur ...

What is the best way for me to generate a fresh object?

In one of my components, I have implemented a feature where clicking on an image toggles a boolean variable to show or hide a menu. The HTML structure for this functionality is as follows: <img src="../../assets/image/dropdown.png" class="dropdown-imag ...

What is the most efficient method for defining a string structure in TypeScript?

Consider the following example of a JavaScript object: const myObject = { id: 'my_id', // Base value which sets the structure for the following values (always pascal case). upperID: 'MY_ID', // Uppercase version of `id`. camel ...

Break apart the string and transform each element in the array into a number or string using a more specific type inference

I am currently working on a function that has the ability to split a string using a specified separator and then convert the values in the resulting array to either strings or numbers based on the value of the convertTo property. Even when I call this fun ...

Show a condensed version of a lengthy string in a div using React TS

I've been tackling a React component that takes in a lengthy string and a number as props. The goal of the component is to show a truncated version of the string based on the specified number, while also featuring "show more" and "show less" buttons. ...

The value specified as type '{ value: BigNumber; }' cannot be assigned to the parameter type 'Overrides & { from?: string | Promise<string> | undefined; }'

I am currently working on a smart contract using Solidity (version 0.8.0) at my buildspace project. Below is a snippet of my code written in TypeScript (4.5.x)/JavaScript and Node.js 16.13.x: ... const waveContractFactory = await hre.ethers.getContractFact ...