Creating a method that generates an object containing both a getter and setter functionality, which is determined by a string parameter

I'm struggling to come up with the correct typing for a function that creates an object with setter and getter properties. I believe using template string literals might be the way to go, but I'm having trouble figuring out the right combination of types. In pseudo-code JS, I envision something like this:

function createGetterAndSetter( key ) {
  return {
    [`get${capitalize(key)}`] : ( model ) => {
      return model[key]
    },
    [`set${capitalize(key)}`] : ( model, value ) => {
      return {
        ...model,
        [key] : value
      }
    }
  }
}

This is what I have attempted so far:

interface Model {
  [key:string]: any
}
type ModelKey = keyof Model & string
type Setter = ( model : Model, value : unknown ) => Model
type Getter = ( model : Model ) => unknown
type GetterKey<T extends string> = `get${Capitalize<T>}`
type SetterKey<T extends string> = `set${Capitalize<T>}`
type GetterSetterKeys<T extends string> = GetterKey<T> | SetterKey<T>
type GetterSetter<T extends GetterKey | SetterKey> = Record<GetterSetterKeys<T>, Getter | Setter>

function createGetterAndSetter<T extends ModelKey>( key : ModelKey ) : GetterSetter<T> {
  const keyName = `${key[0].toUpperCase() + key.substring( 1 )}`

  return {
    [`set${keyName}`] : createSetValue( key, setOpts ) as Setter,
    [`get${keyName}`] : createGetValue( key, getOpts ) as Getter
  } as GetterSetter<T>
}

I'm relatively new to TypeScript, so there are probably mistakes in my approach, but it does seem to make the compiler aware that the output of createGetterAndSetter consists of getX and setX keys. However, it struggles to differentiate whether the values for those keys are getters or setters - only recognizing them as part of a union.

It may be worth noting that I drew inspiration from the createApi function in redux-toolkit, which has the ability to generate named hooks based on specified endpoints.

Answer №1

Consider this potentially useful typing:

type AccessorModifier<K extends string> =
  { [P in `get${Capitalize<K>}`]:
    <T extends { [Q in K]: any }>(model: T) => T[K]
  } &
  { [P in `set${Capitalize<K>}`]:
    <T extends { [Q in K]: any }>(model: T, value: T[K]) => T
  };
declare const accessorModifier: <K extends string>(key: K) => AccessorModifier<K>

Therefore, the function accessorModifier() accepts a key of type K and delivers an outcome of type AccessorModifier<K>. This typification utilizes template literal types to generate the method names based on K, while ensuring that the method types are generic for accurate typing with respect to the model parameter.

The potential implementation for accessorModifier could be:

const capitalize = (k: string) => k[0].toUpperCase() + k.substring(1);
const accessModifier = <K extends string>(k: K) => ({
  [`get${capitalize(k)}`]: (m: any) => m[k],
  [`set${capitalize(k)}`]: (m: any, v: any) => ({ ...m, [k]: v })
}) as AccessorModifier<K>;

Note the usage of a type assertion (as AccessorModifier<K>) and the application of the any type within the implementation. This is because defining the AccessorModifier<K> type may be relatively straightforward, but accurately implementing it can be challenging due to certain limitations within TypeScript's compiler functionalities. To ensure correctness, utilizing any along with as provides more certainty that no errors occur during execution than overcoming complex compilation issues.

To test the functionality, consider the following code snippet:

const data = {
  name: "John Doe",
  age: 30,
  active: true
}

const modifier = accessorModifier("name");
console.log(modifier.getName(data).toUpperCase()); // JOHN DOE

modifier.getName({id: 123}) // error! {id: 123} is invalid since it lacks the 'name' property

console.log(modifier.getName({name: "Jane Doe"})); // Jane Doe

const updatedData = modifier.setName(data, "Alice Smith");
console.log(updatedData.name); // Alice Smith

Upon inspection, the object modifier adheres to type AccessorModifier<"name">, possessing both getName() and setName() methods that behave correctly. The compiler correctly infers the return types and enforces type safety when interacting with these methods. Overall, this showcases the utility and robustness of such typed accessor modifiers.

Explore the code in TypeScript 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

Tips for successfully interacting with dynamic text areas using Protractor:

My current project involves e2e testing for an Angular 4 application with Protractor. Despite my efforts, I am struggling to locate a unique id, class, name or text identifier for a specific textarea within the application code. I need assistance in find ...

Upgrading from Angular 5 to 6: Embracing the RxJS Changes without the crutch of rxjs

Currently, I am facing the challenging task of migrating a project from Angular 5.2.11 to version 6.0.0. The main issue I'm encountering is with RxJS 6 (which is essential for Angular versions above 6). Here's an example of one of the errors that ...

List the hours using TypeScript

My data table is displaying records including a column for hours spent and a row showing the total sum of those hours. While the hours are being added correctly, the minutes display as NaN, such as 52:Nan. Can someone assist me in resolving this issue? co ...

"Encountered a problem while attempting to download the .xlsx file through http.get in an angular application interfacing

Attempting to download a .xlsx file using Angular 7 and web API in C#, encountering the following error: https://i.sstatic.net/7pwDl.png The code snippet from my service.ts is provided below: public exportExcelFile(matchedRows: string, reportInfoId: num ...

Using GraphQL to set default values in data within a useEffect hook can lead to never

Here's the code snippet that I'm working with: const [localState, setLocalState] = useState<StateType[]>([]); const { data = { attribute: [] }, loading } = useQuery<DataType>(QUERY, { variables: { id: client && client.id ...

There is no matching overload for this call in Angular. Error code: TS2769

Having trouble identifying the issue in this TypeScript code snippet. Error reported on line 5, ".subscribe((response: { Token: string }) => {". login() { this.httpClient .post('http://localhost:4000/signin', this.loginForm.value) ...

Please come back after signing up. The type 'Subscription' is lacking the specified attributes

Requesting response data from an Angular service: books: BookModel[] = []; constructor(private bookService: BookService) { } ngOnInit() { this.books = this.fetchBooks(); } fetchBooks(): BookModel[] { return this.bookService.getByCategoryId(1).s ...

Since transitioning my project from Angular 7.2 to Angular 8, I noticed a significant threefold increase in compile time. How can I go about resolving this issue

After upgrading my project to Angular 8, I noticed a significant increase in compile time without encountering any errors during the upgrade process. Is there a way to restore the previous compile time efficiency? **Update: A bug has been identified as th ...

Creating a button that displays the current day with Angular

I'm in the process of developing a timetable app that features buttons for the previous day, current day, and next day. How can I implement a button to specifically show the current day? HTML File <button type="button" (click)="previousDay()" ...

What's the alternative now that Observable `of` is no longer supported?

I have a situation where I possess an access token, and if it is present, then I will return it as an observable of type string: if (this.accessToken){ return of(this.accessToken); } However, I recently realized that the of method has been deprecated w ...

Is it possible to extract specific columns from the Convex database?

I am looking to retrieve all columns from a table using the following code snippet. Is there a more efficient way to achieve this? I couldn't find any information in the documentation. Does anyone have a workaround or solution? const documents = await ...

Error encountered while attempting to load SWC binary for win32/ia32 in a Next JS application

Upon installing a Next.js app using the command npx create-next-app@latest, I encountered an error while running the app. Can anyone explain why this error occurred and provide a solution? PS D:\New folder\my-app> npm run dev [email pr ...

Generating automatic generic types in Typescript without needing to explicitly declare the type

In the scenario where I have an interface containing two functions - one that returns a value, and another that uses the type of that value in the same interface - generics were initially used. However, every time a new object was created, the type had to ...

As a quirk of TypeScript, it does not allow for returning a Tuple directly and instead interprets it as an Array

I need assistance with adding type-safe return to a general function created by a previous developer. Here is the current syntax: export function to(promise:Promise<any>) { return promise .then(data => [null, data]) .catch(err => [ ...

Cannot execute npm packages installed globally on Windows 10 machine

After installing typescript and nodemon on my Windows 10 machine using the typical npm install -g [package-name] command, I encountered a problem. When attempting to run them through the terminal, an application selector window would open prompting me to c ...

Tips for utilizing import alongside require in Javascript/Typescript

In my file named index.ts, I have the following code snippet: const start = () => {...} Now, in another file called app.ts, the code is as follows: const dotenv = require('dotenv'); dotenv.config(); const express = require('express' ...

Spotlight a newly generated element produced by the*ngFor directive within Angular 2

In my application, I have a collection of words that are displayed or hidden using *ngFor based on their 'hidden' property. You can view the example on Plunker. The issue arises when the word list becomes extensive, making it challenging to ide ...

The functionality of the Angular directive ngIf is not meeting the desired outcome

We are currently working on transferring data from one component to another using the approach outlined below. We want to display an error message when there is no data available. <div *ngIf="showGlobalError"> <h6>The reporting project d ...

"Angluar4 is throwing an error: it's unable to read the property 'iname' of

This is the code snippet from item.ts file:- export interface item{ $key?:string; available?:boolean; countable?:boolean; iname?:string; price?:string; desc?:string; image?:string; } The items component item.componenet.ts looks like this:- import { Com ...

Unexpected alteration of property value when using methods like Array.from() or insertAdjacentElement

I'm encountering an issue where a property of my class undergoes an unintended transformation. import { Draggable, DragTarget } from '../Models/eventlisteners'; import { HeroValues } from '../Models/responseModels'; import { Uti ...