Determine the appropriate data type for an object property using its key

I have a constant object with known keys. Each item within the object may or may not have a specific property. I need a function that, when given a key, will return the value along with its corresponding type.

My development environment is TypeScript 4.6.2.

Instead of manually typing in data.key?.property, I require a programmatic approach for this functionality.

Here's an example scenario to illustrate what I'm looking for:

const data = {
 alice: {loves: 3},
 bob: {loves: 'hippos'},
 charlie: {hates: 'toast'},
 denise: {loves: (a:number) => `hello`}
} as const;


function personLoves(name:keyof typeof data) {
    const item = data[name]
    const loves = 'loves' in item ? item.loves : undefined;
    // ^ this line needs improvement!
    return loves;
}

const aliceLoves = personLoves('alice');
// Expecting `aliceLoves` to have type number. 

const bobLoves = personLoves('bob');
// Expecting `bobLoves` to have type string or 'hippo'

const charlieLoves = personLoves('charlie');
// Expecting `charlieLoves` to be of type undefined

const deniseLoves = personLoves('denise');
// Expecting `deniseLoves` to be type (a:number) => string

Playground

Answer №1

If you find yourself in this scenario, I suggest utilizing a generic signature on the personLoves function to indicate that the name parameter is a specific key within the data object. This approach enables TypeScript to accurately determine the return type based on the provided argument during invocation.

In addition to the generic signature, employing an overload signature can address the two possible scenarios: when the person's data includes a loves key (resulting in inferring the return type from the value of that key), and when it doesn't (leading to a return type of undefined).

// Defining a helper type for identifying keys with values containing a 'loves' property
type KeysExtending<O, T> = {
    [K in keyof O]: O[K] extends T ? K : never;
}[keyof O];

// Determining keys in `data` that have a 'loves' key
type KeysWithLoves = KeysExtending<typeof data, {loves: unknown}> // resolves to: "alice" | "bob" | "denise"

// Creating overloads for the identified keys and all other cases
function personLoves<T extends KeysWithLoves>(name: T): (typeof data)[T]["loves"];
function personLoves(name: keyof typeof data): undefined;
function personLoves(name: keyof typeof data) {
    const item = data[name]
    const loves = 'loves' in item ? item.loves : undefined;
    return loves;
}

By following this methodology, you should achieve the desired outcome:


const aliceLoves = personLoves('alice');
// Returns number type: 3

const bobLoves = personLoves('bob');
// Returns string type: 'hippo'

const charlieLoves = personLoves('charlie');
// Returns type: undefined

const deniseLoves = personLoves('denise');
// Returns type: (a:number) => string

Typescript Playground

Answer №2

To streamline the process, it is recommended to define an intermediary type named LovesType, which can automatically deduce the type of the loves property from the given data object.

type LovesType<T> = T extends { loves: infer L } ? L : undefined;

function personLoves<T extends keyof typeof data>(name: T): LovesType<typeof data[T]> {
  const person = data[name];
  return ('loves' in person ? person.loves : undefined) as LovesType<typeof data[T]>;
}

Enhanced Clarity

In addition, introducing a middleman type like Person can represent an individual entry within the data structure effectively reducing repetition when declaring types.

type Person = typeof data[keyof typeof data];
type LovesType<T> = T extends { loves: infer L } ? L : undefined;

function personLoves<T extends keyof typeof data>(name: T): LovesType<Person> {
  const person = data[name] as Person;
  return ('loves' in person ? person.loves : undefined) as LovesType<Person>;
}

Detailed Example:

Here is a comprehensive illustration showcasing the utilization of both middleman types (Person and LovesType):

const data = {
  alice: { loves: 3 },
  bob: { loves: 'hippos' },
  charlie: { hates: 'toast' },
  denise: { loves: (a: number) => `hello` }
} as const;

type Person = typeof data[keyof typeof data];
type LovesType<T> = T extends { loves: infer L } ? L : undefined;

function personLoves<T extends keyof typeof data>(name: T): LovesType<Person> {
  const person = data[name] as Person;
  return ('loves' in person ? person.loves : undefined) as LovesType<Person>;
}

const aliceLoves = personLoves('alice');
console.log(aliceLoves); // 3 (number)

const bobLoves = personLoves('bob');
console.log(bobLoves); // 'hippos' (string)

const charlieLoves = personLoves('charlie');
console.log(charlieLoves); // undefined

const deniseLoves = personLoves('denise');
console.log(deniseLoves); // (a: number) => string (function)

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: Failed to retrieve the name property of an undefined value within the Array.forEach method

Upon pressing the button to display the task pane, I encountered an error message in the console window that reads: "Uncaught (in promise) TypeError: Cannot read property 'name' of undefined". This error persists and I am unable to resolve or com ...

The error "Failed to log in. Cannot read property getPackageManager of undefined in Angular 2

Recently, I came across the nativescript-appList Plugin, but unfortunately encountered a runtime error stating "Cannot read property getPackageManager of undefined." My code implementation within the constructor of an Angular2-NativeScript project is as f ...

Error in Angular 7: Trying to assign a 'void' type to a 'ObservableInput<{}>' type

I can't seem to understand why I am encountering this error fetchFromURL(url: string): any { return this.apiService.get(url).pipe(map(data => data)); } fetchNextPage(): Observable<any> { const url = 'url'; this.f ...

Guide on transferring map-marker from mapclick event to a function within the google-map component

I am currently encountering some difficulties when attempting to open a map-info-window within the Google Map component in an Angular 14 project using the guidelines provided here. For this task, I am utilizing the Google Maps package for Angular available ...

Maintain the active tab selection in Angular 2 even after the page reloads

<nav class="nav-sidebar"> <ul class="nav tabs" id="myTab"> <li class="active" ><a href="#tab3" data-toggle="tab" >Tabl 1</a> </li> <li class="tab2"><a href="#tab2" ...

Using Tailwind classes as a prop functions correctly, however, it does not work when directly applied

Here's a component snippet I'm working on: export const TextInput = ({ label, wrapperClassName = "", inputClassName = "", labelClassName = "", placeholder = "", ...props }: InputProps & Fiel ...

What is preventing me from including a typescript file in a VS2015 folder?

I have been following a tutorial on creating a C# project using TypeScript and Angular 2. However, when I attempt to add a TypeScript file to a folder, the window I see is different than what is shown in the tutorial. The window I get looks like this: http ...

Revamping Modal Interface for CRUD Operations (React / C# / Entity Framework Core)

After successfully implementing Create, Read, and Delete operations in my project using C# and Entity Framework Core, I encountered a roadblock while trying to update data. Despite updating the data as expected, I keep facing an issue at the .CommitAsync() ...

Utilizing TypeScript to extract elements from nested arrays

I'm working with an array structured like this: [{ "id": 1, "city": [{ "name": "BERLIN", } ], }, { "id": 2, "city": [{ ...

When a const variable is declared within the composition-api setup(), it remains unchanged unless redeclared within the function scope

Being primarily a back-end developer, the front-end side of things is still relatively new to me. I'm aware that there are concepts in this domain that I haven't fully grasped yet, and despite my efforts, I'm unable to resolve a particular i ...

Creating a Redux slice with ENUM values can be done by defining an initial state with

I'm facing an issue while trying to set the initial state of a redux slice with an enum value. Whenever I attempt to do so, it results in a Uncaught TypeError: undefined has no properties error. Here is the code snippet: export const themeTokenSlice ...

Attempting to implement a typeguard in Typescript that relies on the presence of specific content within an element

Currently, I am attempting to develop a Typescript conditional that verifies if a particular word is already present in the name. The function in question is as follows: isOrganic() { for (let i = 0; i < this.items.length; i++) { if(this.ite ...

Where is the optimal location for placing a JavaScript listening function within an Angular component?

Summary: Incorporating a BioDigital HumanAPI anatomical model into my Angular 5 application using an iFrame. The initialization of the API object is as follows: this.human = new HumanAPI(iFrameSrc); An important API function human.on(...) registers clic ...

Angular: Keeping array bindings synchronized and up-to-date

I am working on a project that involves managing a list of items using a service. I need an angular component to display these items and update in real-time whenever changes occur. The list is quite extensive, so I prefer updating it based on change event ...

Receiving an error with React Proptypes when using the union type Breakpoint

Struggling to assign the correct proptype to the material-ui Breakpoint type. The breakpoint values are: export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; In my App.tsx file, I have the following ...

Error in TypeScript: Module 'stytch' and its corresponding type declarations could not be located. (Error code: ts(2307))

I'm currently developing a Next.js application and encountering an issue while attempting to import the 'stytch' module in TypeScript. The problem arises when TypeScript is unable to locate the module or its type declarations, resulting in t ...

Using Webpack and Typescript to Import Images

Currently, I am developing a React project with Webpack and Typescript. I need to include an image in one of my <img/> tags, but I am facing difficulties accessing the image files. webpack.config.js: ... module: { rules: [ ... ...

"Implemented a fresh pathway within the app-routing.module.ts file, but unfortunately, ngxAdmin is experiencing functionality issues

While customizing the ngx-admin template, I attempted to incorporate a new module into the app module and added its route in app-routing.module.ts. However, upon trying to open it, the module seems to be stuck at loading without any errors appearing in the ...

Securing your React app with private routes and context-based authentication

I'm currently working on implementing basic authentication for a React+Typescript application using private routes and context. In my setup, I have a login component with a button that toggles a boolean variable authenticated in the context to true up ...

Choose the default setting if angular is null

I'm having an issue with my dropdown select function. <select name="name" [(ngModel)]="name"> <option value="ACTIVE" [selected]="name.status=='ACTIVE' || name.status==null">Active</opt ...