TypeScript introduces a new `prop` method that handles missing keys in objects

Is there a way to create a prop function that can return a default type if the specified key is not found in object o?

type Prop = <K, O extends {}>(k: K, o: O) =>
  K extends keyof O
    ? O[K]
    : 'Nah';

/*
Argument of type 'K' is not assignable to parameter of type 'string | 
  number | symbol'.
  Type 'K' is not assignable to type 'symbol'.
*/
const p: Prop = (k, o) => o.hasOwnProperty(k) ? o[k] : 'Nah';

p('non-existence-property', { n: 1 });

/*
Type '"Nah" | O[K]' is not assignable to type 'K extends keyof O ? O[K] : "Nah"'.
  Type '"Nah"' is not assignable to type 'K extends keyof O ? O[K] : "Nah"'.
*/
const p1 = <K, O extends {}>(k: K, o: O): K extends keyof O ? O[K] : 'Nah' =>
    o.hasOwnProperty(k) ? o[k] : 'Nah';

Answer №1

To enhance type inference when invoking the p() function, we can adjust the definition of Prop as follows:

type Prop = <K extends keyof any, O extends {}>(k: K, o: O) =>
  K extends keyof O
  ? O[K]
  : 'Nah';

The modification restricts K to be string | number | symbol, potentially leading functions using type Prop to infer string literals for K rather than just string.


An issue arises because the compiler cannot verify if a type is assignable to an unresolved conditional type (T extends U ? X : Y when T or U rely on unspecified/uninferred generic type parameters). Generic functions returning conditional types aim to simplify life for the caller; the implementer may need to resort to type assertions to address the compiler's concerns:

const p: Prop = (k: any, o: any) => o.hasOwnProperty(k) ? o[k] : 'Nah'; // no error

Using the any annotation for k and

o</code allows us to craft our own implementation without triggering complaints from the compiler. This approach sacrifices type safety, necessitating caution to avoid deceiving the compiler.</p>

<pre><code>// handling missing optional keys  
const val: { a?: number } = (1 > 2) ? { a: 1 } : {};
const oops1 = p("a", val); // result will likely be "Nah" at runtime but inferred as number | undefined, causing issues!

// dealing with subtypes and unknown extra keys
interface Animal { limbs: number; }
interface Cat extends Animal { lives: number; }
const cat: Cat = { limbs: 4, lives: 9 };
const animal: Animal = cat;
const oops2 = p("lives", animal); // expected to be number at runtime but inferred as "Nah" during compilation, problematic!

// confronting prototype properties 
const regex = /hey/;
const oops3 = p("exec", regex); // expected outcome is "Nah" at runtime but inferred as function in compilation phase, creating discrepancies!

These scenarios illustrate situations where assumptions about p conforming to Prop are inaccurate. It is crucial to acknowledge these complexities and assess if they align with your specific use cases. Awareness of such nuances is essential.

In conclusion, I trust this information proves beneficial to you. Best of luck!

Answer №2

Correcting your implementation

The most straightforward way to fix your implementation is to restrict K by using extends string:

type Prop = <K extends string, O extends {}>(k: K, o: O) => /*...*/

The issue arises when you invoke the function as prop("foo", {foo: 'value'}), causing the type parameters to be inferred as

prop<string, {foo: string}>
. Due to the generic nature of K being 'string', it fails the keyof O check and yields "Nah".

By applying the extends string constraint, the type parameters will now be resolved as prop<"foo", {foo: string}>, delivering the expected functionality.


Nonetheless, this might not align with your intentions. If TypeScript cannot guarantee at compile-time that your key exists in the object, the type will default to "Nah":

// TS indicates it always returns "Nah", though the outcome may vary.
function test(obj: object, someKey: string) {
    return prop(someKey, obj);
}

Consideration:

An alternative approach is as follows:

function prop1<O>(k: string, o: O): O[keyof O] | "Nah" {
    return o.hasOwnProperty(k) ?
        o[k as keyof O] : 'Nah'; 
}

The key distinction from your initial version is the absence of conditional logic: given the uncertainty of whether k exists as a key within O, we opt for a simple binary result - either fetching a value from O (O[keyof O]) or returning "Nah".

If you have prior knowledge, during compilation, that k is indeed a key in

O</code, consider implementing overloads in this function to accommodate such scenarios. Nevertheless, managing this could become intricate, suggesting a direct usage of <code>o[k]
or avoiding this function altogether would probably be more effective.

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

"Error encountered: Unable to resolve dependency tree" message appears when attempting to run npm install

Encountering dependency errors while trying to execute the npm install command for my Angular application. As a newcomer to TypeScript and Angular, I'm unsure of the next steps to take. Any suggestions? Attempted solutions include clearing the npm ca ...

After transitioning to TypeScript, the CRA app is encountering issues loading CSS files compiled by SASS

My React application was originally created using create-react-app and I had been successfully loading scss files with the node-sass package. However, after deciding to switch to TypeScript following the steps outlined in , my css files are no longer being ...

Is there an option for keyPrefix in i18next?

For my current project, I am utilizing both i18next and react-i18next. One useful feature of using the useTranslation hook from react-i18next is the "keyPrefix" option, which helps in reducing code duplication. However, there are instances where I need to ...

Error: Attempting to change a read-only property "value"

I am attempting to update the input value, but I keep receiving this error message: TypeError: "setting getter-only property "value" I have created a function in Angular to try and modify the value: modifyValue(searchCenter, centerId){ searchCenter.va ...

The positioning of CSS arrows using the "top" attribute is not relative to the top of the page when using absolute values

I am currently working on positioning the arrow in the screenshot using TypeScript calculations. However, I am facing an issue where the position is being determined based on the top of the black popup instead of the top of the screen. From the top of the ...

Can someone please provide guidance on how I can access the current view of a Kendo Scheduler when switching between views, such as day view or week

<kendo-scheduler [kendoSchedulerBinding]="events" [selectedDate]="selectedDate" [group]="group" [resources]="resources" style="height: 600px;" [workDayStart]="workDayStart" [workDayEnd] ...

Can you identify the TypeScript type for an array containing various Angular components?

In my application, I have a diverse range of components that I would like to organize into an array. There are no restrictions on what types of components can be included in this array, as long as they are Angular components. What is the correct way to de ...

The type 'string' cannot be utilized to index type

Apologies for adding yet another question of this nature, but despite finding similar ones, I am unable to apply their solutions to my specific case. Could someone please assist me in resolving this TypeScript error? The element implicitly has an 'an ...

Dynamic Assignment of Object Values Based on Enum Keys in Typescript

Check out this TS Playground for this piece of code. Dynamically Assigning Object Values Based on Enum Key I am attempting to achieve the following: in the interface iAnimals, each animal key in the enum Animals should have its associated interface value, ...

Executing various tasks concurrently with web workers in Node.js

Looking to read multiple JSON files simultaneously and consolidate the data into a single array for processing on a Node.js server. Interested in running these file readings and processing tasks concurrently using web workers. Despite finding informative ...

Tips for resolving this unhandled error in React TypeScript

After creating a program in React TypeScript, I encountered an uncaught error. Despite running and debugging tests and conducting extensive research on Google, I have been unable to resolve this issue on my own. Therefore, I am reaching out for assistance ...

What is the best way to create an Office Script autofill feature that automatically fills to the last row in Excel?

Having trouble setting up an Excel script to autofill a column only down to the final row of data, without extending further. Each table I use this script on has a different number of rows, so hardcoding the row range is not helpful. Is there a way to make ...

How to access type properties in typescript without using the "this" keyword

Below is a snippet of code that I am working with: class Player implements OthelloPlayer { depth; constructor(depth: number) { this.depth = depth; } getMove(state: OthelloState) { return this.MinimaxDecision(stat ...

Enhancing NG Style in Angular 6 using a custom function

Today, my curiosity lies in the NG Style with Angular 6. Specifically, I am seeking guidance on how to dynamically update [ngStyle] when utilizing a function to determine the value. To better illustrate my query, let me present a simplified scenario: I ha ...

Troubleshooting path alias resolution issue in NextJS when using Typescript with index.ts files

I keep receiving a TypeScript warning stating that the module cannot be found. This issue has surfaced while I'm utilizing TypeScript in my NextJS application, particularly when using custom paths. Previously, this problem never arose. My project st ...

Is there a way for me to steer clear of having to rely on the Elvis Operator?

Throughout my journey building my Angular 2 website, I've found the Elvis Operator to be a crucial element that makes everything possible. It seems like every task I undertake involves mastering how to apply it correctly in every instance where data i ...

Retrieving selected item values in Angular 2 using ng2-completer

Recently, I decided to experiment with a new autocompleter tool that is unfamiliar to me: https://github.com/oferh/ng2-completer. I successfully imported it and it seems to be functioning properly. My current goal is to retrieve the values of the selecte ...

Changing Enum Value to Text

In my enum file, I have defined an object for PaymentTypes: export enum PaymentTypes { Invoice = 1, CreditCard = 2, PrePayment = 3, } When I fetch data as an array from the database, it also includes PaymentType represented as numbers: order: ...

Effortless code formatting with VS Code for TypeScript and JavaScript

Does anyone know of any extensions or JSON settings that can help me format my code like this: if(true) { } else { } Instead of like this: if(true){ } else { } ...

Having trouble obtaining the ref.current.offsetWidth?

I have been working on creating a contextMenu. My goal is to retrieve the offsetWidth and offsetHeight from ref.current, but when I console.log it, it shows as undefined. const ContextMenu: React.FC<ContextMenuProps> = props => { const thisCom ...