Discovering the specific type of an object property in TypeScript

I am currently working on implementing a lookup type within an object. Imagine my object structure as follows:

class PersonList {
  persons = {
    john: 'description of john',
    bob: 'description of bob'
  }
}

I want to create a getter function that retrieves a person from the persons object without explicitly specifying it.

I came across the getProperty function in the documentation:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];  // Inferred type is T[K]
}

However, this function requires an obj parameter which I am looking to eliminate in my getter implementation. I attempted to use a proxy for the getter, but ran into issues:

class PersonList {
  persons = {
    john: 'description of john',
    bob: 'description of bob'
  };

  getPerson(name) {
    return this.getProperty(this.persons, name);
  }

  private getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];  // Inferred type is T[K]
  }
}

Unfortunately, this approach did not throw an error when attempting something like

personList.getPerson('someonenotincluded');
, and autocomplete functionality was also not working correctly.

Answer №1

I would suggest taking the inline type provided and giving it a name (but feel free to continue reading if you prefer not to):

interface Persons {
  john: string;
  bob: string;
}

You can then utilize keyof Persons as the parameter type in the method getPerson:

class PersonList {
  persons: Persons = {
    john: 'description of john',
    bob: 'description of bob'
  };

  getPerson(name: keyof Persons) {
    return this.persons[name];
  }
}

Therefore, if pl is an instance of PersonList:

console.log(pl.getPerson('john')); // Works
console.log(pl.getPerson('someonenotincluded')); // Error

Live on the playground.

Alternatively, if you would rather keep it inline, you can achieve that by using keyof PersonList['persons'] as the parameter type:

class PersonList {
  persons = {
    john: 'description of john',
    bob: 'description of bob'
  };

  getPerson(name: keyof PersonList['persons']) {
    return this.persons[name];
  }
}

Live on the playground.


In a previous comment, you inquired:

Is it possible to implement this in an abstract class? ... It would be great to have the getter implemented in the abstract class, but I haven't figured out a solution yet.

...with reference to this code template:

abstract class AbstractPersonList {
  protected abstract persons: { [name: string]: string };
}

class Persons extends AbstractPersonList {
  persons = {
    john: 'this is john',
  }
}

class MorePersons extends AbstractPersonList {
  persons = {
    bob: 'this is bob',
  }
}

You could introduce a parameter in the AbstractPersonList class:

abstract class AbstractPersonList<T extends {[name: string]: string}> {
  protected abstract persons: T;
  public getPerson(name: keyof T): string {
    return this.persons[name];
  }
}

This leads to:

class Persons extends AbstractPersonList<{john: string}> {
  persons = {
    john: 'this is john',
  }
}

class MorePersons extends AbstractPersonList<{bob: string}> {
  persons = {
    bob: 'this is bob',
  }
}

Which results in the behavior you were seeking:

let n = Math.random() < 0.5 ? 'john' : 'bob';

const p = new Persons();
console.log(p.getPerson('john'));  // Works
console.log(p.getPerson('bob'));   // FAILS: Argument of type '"bob"' is not assignable to parameter of type '"john"'.
console.log(p.getPerson(n));       // FAILS: Argument of type 'string' is not assignable to parameter of type '"john"'.

const mp = new MorePersons();
console.log(mp.getPerson('john')); // FAILS: Argument of type '"john"' is not assignable to parameter of type '"bob"'.
console.log(mp.getPerson('bob'));  // Works
console.log(mp.getPerson(n));      // FAILS: Argument of type 'string' is not assignable to parameter of type '"bob"'.

Live on the 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 it possible to include the term 'public' exclusively within a .ts file in a TypeScript TSLint React environment?

I'm struggling to understand why I am encountering an error in VSCode while working on a react typescript project with tslint setup. The error message states: 'public' can only be used in a .ts file. [Also, I'm wondering why I' ...

analyzing properties through unit testing

Currently in the process of writing unit tests for computed properties. I have a file called fileOne.ts : export const fileOne = () => { const fx1 = computed ( () => { ... } ); const fx2 = computed ( () => { ... } ); const fx3 = comp ...

What is the best way to exceed the capacity of a function in TypeScript by incorporating optional

I've been working on converting some code from JavaScript to TypeScript, and I have a specific requirement for the return type of a function. The function should return void if a callback parameter is provided, otherwise it should return Promise<o ...

Bringing in a script and invoking a function on a specific variable

As a newcomer to Typescript, I've been experimenting with some code I came across online to enhance the appearance of links on my website. <script src="https://wow.zamimg.com/widgets/power.js"></script> <script>var wowhead_tooltips ...

Creating a Build-Free Workflow in a TypeScript Monorepo

Imagine having this monorepo structure: /apps /apps/app1 /apps/app1/src (includes index.ts and various other files and subdirectories) /apps/app1/tsconfig.json /apps/app1/package.json /apps/app2 /apps/app2/src (contains index.ts and many other files an ...

Issue encountered when attempting to deploy a node/express API with now.sh

Currently, I am in the process of deploying a node/express API with multiple endpoints on now.sh. I am seeking guidance on properly configuring the now.json file for this deployment. In order to provide a visual representation of my project's comple ...

Displaying a collection of objects in HTML by iterating through an array

As someone new to coding, I am eager to tackle the following challenge: I have designed 3 distinct classes. The primary class is the Place class, followed by a restaurant class and an events class. Both the restaurant class and events class inherit core p ...

Discover how to retrieve service response data from an API and populate it into the Select Option with Angular 2

Api.services.ts getListOfNames() { return this.http.get(this.BaseURL + 'welcome/getnama') .pipe(map(response => { return response; })); } After making the API call, I receive the following resp ...

Issues encountered when attempting to refresh page with react-router-dom

I successfully implemented a private route system in my React application using react-router-dom. However, I encountered an issue where upon reloading a page, it briefly redirects to /login before properly displaying the page. How can I resolve this unexpe ...

Utilizing Typescript and RequireJS for Incorporating jqueryui

Recently, I've been encountering issues with getting jQueryUI to function properly. Strangely enough, prior to attempting to integrate jQueryUI, using jQuery alone worked perfectly fine. The current problem I'm facing is receiving a "TypeError: ...

Utilize a module within a script in the continuous integration setup of a React application

I've created a module to verify if all the necessary environment variables are properly set in a React application. Here's a simple example of how it works: const getEnvironmentVariable = (key: string): string => { if (process.env[key]) { ...

Unable to provide any input while utilizing npm prompts

After installing npm prompts, I encountered a strange issue. When trying to run the example code for npm prompts, I found that I couldn't enter any input at all. The underscore in the input field would blink for a few seconds, then the cursor would ju ...

Unit testing the error function within the subscribe method in Angular

I've been working on a unit test for the subscribe call, but I'm struggling to cover the error handling aspect of the subscribe method. The handleError function deals with statusCode=403 errors and other status codes. Any assistance would be grea ...

Transforming an array of JSON items into a unified object using Angular

I need to convert an array list into a single object with specific values using TypeScript in Angular 8. Here is the array: "arrayList": [{ "name": "Testname1", "value": "abc" }, { "name": "Testname2", "value": "xyz" } ] The desired ...

Completion of TypeScript code is not working as expected, the variable that is dependent upon is not

Looking for assistance with creating code completion in TypeScript. Variable.Append1 Variable.Append2 Variable.Append3 I have defined the following class: class Variable { Append1(name: string){ if (name == undefined) ...

Pattern matching to eliminate line breaks and tabs

Hey there, I'm working with a string: "BALCONI \n\n\t\t\t\t10-pack MixMax chocolade cakejes" and trying to tidy it up by removing unnecessary tabs and new lines. I attempted using .replace(/(\n\t)/g, '&apo ...

Angular: Navigating through two levels of fetched data from Firebase

I'm currently working on parsing retrieved data from Firebase within an Angular (Typescript) project. The structure of my JSON data in Firebase resembles the following: "customer" : { "customerId1" : { "documents" : { "documentId1" : { ...

Encountered an issue when utilizing the useRef hook in Next.js version 13

I am currently exploring nextjs13 and typescript. I encountered an issue when attempting to use the useRef hook. Below is the code snippet in question: import { useEffect, useRef } from "react" export const useDraw = () => { const canvas ...

Using React.ReactNode as an argument in Storybook

This is a unique button component type that I have created import React from 'react' export type ButtonProps = { label: string; color?:'primary' | 'secondary' | 'tertiary'; size?:'mobile' | 'tabl ...

Transform the function into an observable form

Is there a way to transform this function into an observable? I need it to check for the existence of a document based on a query, and I want to be able to subscribe to it in order to create a new document if one does not already exist. Unfortunately, I a ...