Develop a function that returns a specific type that has been mapped

I'm currently working on a method that loops through an object and replaces key-value pairs where the value matches { _id: Types.ObjectId } with a new key and maps the value to the string representation of _id. For example:

{
  "name": "test",
  "image": {
    "_id": "63162902546ac59fb830ccae",
    "url": "https://..."
  }
}

This object would be transformed into:

{
  "name": "test",
  "imageId": "63162902546ac59fb830ccae"
}

In addition, I want to specify keys that should not be modified. If I pass in image as a parameter, the object will remain unchanged.

I have created a mapped type like this:

type TransformedResponse<T, R extends keyof T> = {
  [K in keyof T as K extends R
    ? K
    : T[K] extends { _id: Types.ObjectId }
    ? `${string & K}Id`
    : K]: K extends R
    ? T[K]
    : T[K] extends { _id: Types.ObjectId }
    ? string
    : T[K];
}

This type seems to produce the expected results:

interface Example {
  name: string;
  image: {
    _id: Types.ObjectId;
    url: string;
  };
}
type T1 = TransformedResponse<Example, 'name'>; // { name: string; imageId: string; }
type T2 = TransformedResponse<Example, 'image'>; // { name: string; image: { _id: Types.ObjectId; url: string; } }

The function for transforming the response looks like this:

export function ResponseTransformer<T>(
  obj: T,
  relations: (keyof T)[]
): TransformedResponse<T, keyof T> {
  const transformedObj = {};
  Object.keys(obj).forEach((key) => {
    if (obj[key] && obj[key]._id && !relations.includes(key as keyof T)) {
      transformedObj[`${key}Id`] = obj[key].toString();
    } else {
      transformedObj[key] = obj[key];
    }
  });
  return transformedObj as TransformedResponse<T, keyof T>;
}

When calling this function like this:

const example = {
  name: 'test',
  image: {
    _id: '63162902546ac59fb830ccae',
    url: 'test',
  },
};
const t1 = ResponseTransformer(example, ['name']); 

The returned type is not T1 as expected but a generic response (

TransformedResponse<T, keyof T>
). This causes issues with type checking where the exact type T1 is required as the return type.

How can I make the function return the specific type?

Also, how can I make the generic argument R optional in the type?

Answer №1

Make sure that the parameter relations is defined as its own generic type, rather than just being keyof T. This ensures that it captures the actual values passed to the function, not just keys.

export function ResponseTransformer<T, K extends keyof T>(
  obj: T,
  relations: K[]
): Expand<TransformedResponse<T, K>> {
  return {} as any
}

type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;

TypeScript may have difficulty displaying the correct return type at times. Including the Expand helper resolves this issue.

Furthermore, remember to pass an actual ObjectId to the function, and not just a string.

const example = {
  name: 'test',
  image: {
    _id: '63162902546ac59fb830ccae' as unknown as Types.ObjectId,
    url: 'test',
  },
};
const t1 = ResponseTransformer(example, ['name']); 
// const t1: {
//     name: string;
//     imageId: string;
// }

Interactive Playground

Answer №2

To focus on specific relationships, first narrow down the type of relationships and then utilize the determined tuple in the return type as shown below:

export function ResponseTransformer<T, R>(
  obj: T,
  relations: [R] extends [[]] ? [] : { [K in keyof R]: Extract<R[K], keyof T> }
): TransformedResponse<T, R extends (keyof T)[] ? R[number] : keyof T> {

Playground


For more information on the narrowing process, you can refer to this link.

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

Angular - Enabling the next screen button only after completing multiple selections

Currently, I'm working on a screen where users can select multiple options from a table. The requirement is that they must select at least 3 options before they can proceed. However, I am facing difficulties in implementing this functionality and unsu ...

Directly within Angular, map the JSON data to an object for easy assignment

I came across this information on https://angular.io/guide/http. According to the source, in order to access properties defined in an interface, it is necessary to convert the plain object obtained from JSON into the required response type. To access pr ...

"Error: The method setValue is not found in the AbstractControl type" when trying to clear form in Angular 2

Here is the template code: <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" novalidate="novalidate"> <textarea [ngClass]="{ 'error': comment }" [formControl]="form.controls['comment']" ...

Exploring Cypress: Leveraging the Power of Variable Assignment

Recently, I encountered an issue while working with a variable in a Cypress each loop. Despite incrementing the variable within the loop, it resets to zero once outside of it. Can someone shed light on why this happens and suggest a solution? Thank you. p ...

Distinguishing the switch function from the React switch operator (jsx, tsx)

We are in the process of converting our portfolio from JavaScript to TypeScript, utilizing NextJS as the frontend framework and Strapi as the backend. To enable dynamic content, we have implemented a dynamiczone field within the post model that is accesse ...

Is there a way to view the type signature of the resulting intersection type (type C = A & B) in IDE hints, rather than just seeing the components?

When analyzing types defined by intersection in Typescript, I notice that the hint remains identical to the original definition: https://i.stack.imgur.com/mjvI8.png However, what I actually want is to visualize the resulting shape, similar to this: http ...

Having trouble utilizing a JavaScript file within TypeScript

I am currently exploring the integration of Three.js into an Angular application. Following the documentation, I imported Three.js using the following line: import * as THREE from 'three'; In addition, I installed the types for Three.js with th ...

How to convert an attribute of an object within a list to a string separated by commas in TypeScript and Angular

I am working with an array of person objects in Angular 5 and displaying them in HTML using ngFor. The Person objects also contain an array of Role objects. persons:Array<Person>=[]; Each Role object is structured like this: export class Role{ ...

Exclude<Typography, 'color'> is not functioning correctly

Take a look at this sample code snippet: import { Typography, TypographyProps } from '@material-ui/core'; import { palette, PaletteProps } from '@material-ui/system'; import styled from '@emotion/styled'; type TextProps = Omi ...

What is the best way to individually update elements in an array in Ionic v5?

As a newcomer to Ionic and TypeScript, I would appreciate your kindness in helping me with a problem I am facing. I have an array of objects that get updated when adding an 'exercise', where you can specify the number of sets and reps. The issue ...

Lack of MaterialUI Table props causing issues in Storybook

Currently, I am utilizing MaterialUI with some modifications to create a personalized library. My tool of choice for documentation is Storybook, using Typescript. An issue I have encountered is that the storybook table props are not consistently auto-gene ...

What impact do passing children have on the occurrence of Typescript errors?

Recently, I came across a peculiar situation where the Typescript compiler appeared to be confused by passing the children prop to a component, leading to unsafe behavior. I am looking to create a component that can only accept the subtitle (text) and sub ...

Tips for chaining API calls in Angular using rxjs?

How can I efficiently nest API calls in Angular using RxJS? getProducts(): Observable<any> { return this.getProductIDs().pipe( map((response) => response.products.map((data) => (item: any) => flatMap(() => th ...

The MaxDuration feature for a 5-minute time limit is malfunctioning on the Serverless Pro Plan, resulting in a 504 ERROR on

I am currently using Next.js@latest with App Directory My application is hosted on Vercel I'm experiencing a 504 error from Vercel and I'm on the pro plan. My serverless functions are set to run for up to 5 minutes, but usually, they only take ...

Ensuring a child element fills the height of its parent container in React Material-UI

Currently, I am in the process of constructing a React Dashboard using MUI. The layout consists of an AppBar, a drawer, and a content area contained within a box (Please correct me if this approach is incorrect)... https://i.stack.imgur.com/jeJBO.png Unf ...

Tips for creating a vue-cli application that can be customized post-build:

I have a functioning vue-cli application that I can easily build. However, I now need to create a single deployable build that can be used on multiple servers. The challenge is that depending on the server, I will need to adjust some basic variables such a ...

Personalized Carousel using Ng-Bootstrap, showcasing image and description data fields

I have been working on customizing an Angular Bootstrap Carousel and have managed to successfully change the layout. I now have two columns - with the image on the right and text along with custom arrows on the left. My goal is twofold: First, I am lookin ...

Using TypeScript with React and Redux to create actions that return promises

Within my React application, I prefer to abstract the Redux implementation from the View logic by encapsulating it in its own package, which I refer to as the SDK package. From this SDK package, I export a set of React Hooks so that any client can easily u ...

Limit potential property values based on the existing keys within the object

My structure looks like this: export interface AppConfig { encryptionKey: string; db: TypeOrmModuleOptions; } export interface BrandsConfig { /** * Brand name */ [key: string]: AppConfig; } export interface IConfig { brands: BrandsConfig ...

Implementing mandatory object keys in TypeScript

Suppose you have these type definitions: type Panel = 'store' | 'logs' You aim to construct an object containing key => ReactChild pairs, where the keys are restricted to the values in Panel. const components = { store: StoreC ...