Tips for creating a generic that can automatically determine the type based on a function that alters an object

The expected result on input:

const MOCK_DATA = {
  role: {
    option: 'Founder / CEO',
    text: '',
  },
  content: {
    option: 'Marketing videos',
    text: '',
  },
};

getSurveyData(MOCK_DATA)
// {
//   role: string;
//   content: string;
// }

The expected result on some other input:

const MOCK_DATA2 = {
  position: {
    option: ['Founder / CEO', 'Developer'],
    text: '',
  },
  termsConsent: true,
};

getSurveyData(MOCK_DATA2)
// {
//   position: string;
//   termsConsent: boolean;
// }

The approach I devised does not infer the return type accurately, so how can a generic function be created to correctly infer the return type based on the input value?

The top-level key in the input can be of type

string | object | boolean | number
.

Actual return type:

const surveyData: {
    [x: string]: string | boolean | number;
}

Code:

type FormValues = {
  [key: string]:
    | {
        option: string | string[];
        text: string;
      }
    | boolean
    | string
    | number
};

export const getSurveyData = (formValues: FormValues) => {

  return Object.entries(formValues).reduce(
    (
      prev: {
        [key: string]: string | boolean | number;
      },
      [key, data],
    ) => {
      let value: string | boolean | number = '';

      if (data instanceof Object) {

        if (Array.isArray(data.option)) {
          value = data.option.join(', ');

        } else {
          value = data.option;
        }
      }

      if (typeof data === 'boolean' || typeof data === 'string' || typeof data === 'number') {
        value = data;
      }

      return { ...prev, [key]: value };
    },
    {},
  );
};

Here's TS playground

Answer №1

Utilizing a mapped type for the return value is achievable. Transform getSurveyData into a generic function (as previously mentioned):

export const getSurveyData = <T extends FormValues>(formValues: T): SurveyData<T>

...and subsequently define SurveyData in a way that inspects each property to determine if the value of a key matches the object type with options and text, returning the type of text (always string) when applicable, or retaining the property's original type otherwise (using a conditional type):

type SurveyData<T extends FormValues> = {
    [Key in keyof T]: T[Key] extends { text: string } ? string : T[Key];
};

An assertion of type (as SurveyData<T>) will be necessitated on the return value inside the implementation logic (which may potentially be refactored to eliminate this need).

By implementing these changes, you can achieve the desired outcomes:

const result1 = getSurveyData(MOCK_DATA);
console.log(result1.content);
//                  ^? (property) content: string
console.log(result1.role);
//                  ^? (property) role: string

const result2 = getSurveyData(MOCK_DATA2);
console.log(result2.position);
//                  ^? (property) position: string
console.log(result2.termsConsent);
//                  ^? (property) termsConsent: boolean

Interactive Example Here

Answer №2

Explore the power of mapped types along with conditional types.

type SimpleTransform<T> = T extends Record<string, any> ? string : T;

function processData<T extends FormData>(data: T): {[key in keyof T]: SimpleTransform<T[key]>} {
    // Implementation goes here
}

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

Creating object properties dynamically based on specific conditions

Is there a more efficient way to create object properties that are dependent on certain conditions? For example, one can define a variable based on a condition like this: const foo = someCondition ? true : undefined However, what I am seeking is to achiev ...

Set the default value for a form control in a select dropdown using Angular

I've been struggling to figure out how to mark an option as selected in my select element, but I haven't had any luck. I've tried multiple solutions from the internet, but none of them seem to be working for me. Does anyone out there have ...

The default behavior of Angular-Keycloak does not include automatically attaching the bearer token to my http requests

I'm currently working on integrating keycloak-angular into my project, but I'm facing an issue with setting the bearer token as the default for my HTTP requests. "keycloak-angular": "9.1.0" "keycloak-js": "16.0 ...

The TypeScript error message "Type 'x' cannot be assigned to type 'IntrinsicAttributes & IntrinsicClassAttributes<Class>'" was displayed on the screen

I encountered an issue when trying to pass a class method to a child component in a new code. While it worked perfectly in another code, I am now receiving the following error message: view image here What steps should I take to resolve this error? Do I ...

Exploring the differences between MongoDB's ISODate and a string data

Currently deep in a backend build using MERN stack and Typescript. The issue arises when attempting to compare Dates stored in MongoDB as Date(ISODate(for example: "2022-09-14T16:00:00.000+00:00") with a string (for example: "2022-14-09&quo ...

Unable to send a function as props to a child component in TypeScript

Within my application, I have a parent component that holds a state and a setState function. This state and function are then passed down to a child component in order to retrieve values (such as input field data). When the function is triggered in the chi ...

Include additional information beyond just the user's name, profile picture, and identification number in the NextAuth session

In my Next.js project, I have successfully integrated next-auth and now have access to a JWT token and session object: export const { signIn, signOut, auth } = NextAuth({ ...authConfig, providers: [ CredentialsProvider({ async authorize(crede ...

Authentication with Angular 4 and Firebase 2

I'm having some difficulty learning how to authenticate users using Angular and Firebase. When I run the Angular app using ng serve in the terminal, I keep getting this ERROR message: ERROR in /Users/.../Desktop/angular/fireauth/node_modules/angul ...

The Child/Parent arguments in Typescript methods cannot be assigned

Why is this not working in TypeScript? class Parent { id: string = '' } class Child extends Parent{ name: string = '' } const fails: (created: Parent) => void = (created: Child) => { return }; const failsToo: ({ create ...

What is the best way to incorporate a class creation pattern in Typescript that allows one class to dynamically extend any other class based on certain conditions?

As I develop a package, the main base class acts as a proxy for other classes with members. This base class simply accepts a parameter in its constructor and serves as a funnel for passing on one class at a time when accessed by the user. The user can spe ...

Removing a row will always result in the deletion of the final row, as the index is returned as

In my Angular application, I have a Reactive Form with a feature that allows users to add or remove rows. Each row has its own delete button. However, there is an issue where clicking on the delete button for a specific item results in the last row being r ...

Pay attention to the input field once the hidden attribute is toggled off

In attempting to shift my attention to the input element following a click on the edit button, I designed the code below. The purpose of the edit is to change the hidden attribute to false. Here's what I attempted: editMyLink(i, currentState) { ...

EventEmitter asynchronous callback

Can you attach an asynchronous callback to an EventEmitter in TypeScript or JavaScript? someEmitter.on("anEvent", async () => console.log("hello")); If so, does using an async function guarantee that the function will run asynchronously? Also, what ar ...

Ways to retrieve object array values

How can I extract object array values from a data array? Here is the code I am using: Component FmtNews(mediasource) { let body = mediasource; console.log("Testing body:" +body) this.commonService.getTopNews(body) .subscribe((res) => { ...

Utilizing TypeScript with Express.js req.params: A Comprehensive Guide

Having an issue with my express.js controller where I am unable to use req.params The error message being displayed is 'Property 'id' is missing in type 'ParamsDictionary' but required in type 'IParam'.' I need a w ...

Error encountered: NextJs could not find the specified module, which includes Typescript and SCSS

I am in the process of migrating a Next.js application from .js to .ts and incorporating ScSS. The first error I encounter is during 'npm run dev'. However, when I try 'npm run build', different issues arise that do not seem related to ...

An error in typescript involving a "const" assertion and a string array

Currently, I am diving into the world of Typescript along with React. However, an error has emerged in my path that I can't seem to figure out. It's puzzling why this issue is occurring in the first place. Allow me to elaborate below. const color ...

Do not display large numbers within an HTML card

I have https://i.sstatic.net/DkowD.png this card here and displaying dynamic data inside it. The number is quite large, so I would like it to appear as 0.600000+. If a user hovers over the number, a tooltip should display the full number. How can I achieve ...

Node module for Nativescript angular project that enables multi select dropdown or picker functionality

Does anyone know of a Node NPM module for creating a multi-select dropdown in NativeScript Angular? I've searched through many plugins in the NativeScript marketplace, but haven't been able to find one that fits my needs. I need the plugin to wo ...

Tips for maintaining a marker at the bottom of the screen while zooming, similar to Google Maps

Is there a way to customize the zoom behavior in my project? I want to maintain the bottom position while resizing the other three directions, and also include pitch adjustments. https://i.sstatic.net/hQdg8.gif https://i.sstatic.net/m3xef.gif I aim for t ...