Develop a TypeScript function that accepts a collection of input fields and FormData as parameters and outputs a secure object containing name and value attributes

Looking to create a TypeScript function with 2 parameters,

  1. a list of fields containing a name and optional type
  2. FormData, then return an object with field name as key and field value as value

How it should operate:

const formData = new FormData();
formData.append('name', 'John Doe');
formData.append('age', '25');

const fields = { name: {}, age: { type: 'number' } };
const data = extractFormValues(fields, formData);
// {age: 25, name: "John Doe;"}
// types being {age: number, name: string}

I managed to get it working by using an object instead of an array

// const fields = [{ name: 'name' }, { name: 'age', type: 'number' }];
const fields = { name: {}, age: { type: 'number' } };

Presenting the current solution utilizing an object rather than an array

type IField = {
    name: string;
    type?: string;
};

type IFields = Record<string, IField>;

const extractFormValues = <TFields extends IFields>(fields: TFields, formData: FormData) => {
    const keys = Object.keys(fields);
    const data = {} as {
        [Key in keyof TFields]: TFields[Key]['type'] extends 'number' ? number : string;
    };
    for (const key of keys) {
        const value = formData.get(key) as string;
        if (fields[key]?.type === 'number') data[key] = Number(value);
        else data[key] = value;
    }
    return data;
};

const fields = { name: { name: 'name' }, age: { name: 'age', type: 'number' } };

const formData = new FormData();
formData.append('name', 'JohnDoe');
formData.append('age', '25');

const data = extractFormValues(fields, formData);

Desiring the functionality when passing an array like this.

const fields = [{ name: 'name' }, { name: 'age', type: 'number' }]

This is how I would like it, while maintaining type safety

type IField = {
   name: string;
   type?: string;
};

type IFields = IField[];

const extractFormValues = (fields, formData: FormData) => {
   const data = {};
   for (const field of fields) {
      const value = formData.get(field.name) as string;
      if (field.type === 'number') data[field.name] = Number(value);
      else data[field.name] = value;
   }
   return data;
};

To execute it, use the following:

const fields = [{ name: 'name' }, { name: 'age', type: 'number' }];

const formData = new FormData();
formData.append('name', 'John Doe');
formData.append('age', '25');
const data = extractFormValues(fields, formData); // {name:'John Doe',age:25}
//    ^? needs to be {name:string,age:number}

Answer №1

To potentially make this process work, it's necessary to modify how you define the fields. The current version in your code looks like this:

const fields = [{ name: 'name' }, { name: 'age', type: 'number' }]
//    ^? const fields: ({name: string; type?: undefined}|{name: string; type: string})[]

In these elements, the name property is of type string, and the type can be either string or undefined. While this inference is reasonable because the compiler doesn't know how fields will be used later, it doesn't suit your specific requirements. What you need are literal types for properties like "name", "age", and "number".

The most efficient way to achieve this is by using a const assertion. By doing this, you inform the compiler that there won't be any changes and hence it should infer the most precise type:

const fields = [{ name: 'name' }, { name: 'age', type: 'number' }] as const;
/* const fields: readonly [{
    readonly name: "name";
}, {
    readonly name: "age";
    readonly type: "number";
}] */

This declaration contains all the relevant information and indicates that fields is a readonly tuple type. Since readonly arrays are broader than regular arrays, updating IFields to accept them will eliminate any potential issues:

type IFields = readonly IField[];

Now, with a strong call signature for extractFormValues(), we're good to go.


One method to implement this is as follows:

declare const extractFormValues: <const TFields extends IFields>(
   fields: TFields, formData: FormData
) => { [Key in TFields[number]["name"]]:
      Extract<TFields[number], { name: Key; }>["type"] extends "number" ? number : string;
   }

This function has been declared in TypeScript simply to specify its existence and the correct type (see When do you use declare in TypeScript?). In the actual code, implementation should follow, and the appropriate means must be taken to assign the call signature.

The functionality involves iterating over the name properties of array elements, finding the corresponding type based on the name, and performing checks similar to the previous version but now against "number".


Let's put it into action:

const formData = new FormData();
const data = extractFormValues(fields, formData);
//    ^? const data: { name: string; age: number; }

It seems to be working correctly. The type of data is exactly what we desired - {name: string, age: number}.

Playground link to code

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

What materials are required in order to receive messages and information through my Contact page?

Currently, I am pondering the optimal method for gathering information from my Contact page. I have already created a form; however, I'm unsure how to send the gathered data to myself since I am relatively new to Web development. Angular is the framew ...

Guide on breaking down a primary element into two smaller components

I'm currently working on an Angular application where I need to toggle between two buttons (btn1, btn2) within a toolbar. These buttons are part of a component called "design" which is a subcomponent of the main App component. The toolbar contains an ...

Tips for storing an unmatched result in an array with a Regexp

Is it possible to extract the unmatched results from a Regexp and store them in an array (essentially reversing the match)? The following code partially addresses this issue using the replace method: str = 'Lorem ipsum dolor is amet <a id="2" css ...

Import statement cannot be used except within a module

I am currently facing an issue with running the production version of my code. I have Node 20.10 and TypeScript 5 installed, but for some reason, I am unable to run the built version. Here are the contents of my package.json and tsconfig.json files: { & ...

Looking for help with importing modules/namespaces in TypeScript?

I am currently using TypeScript 2.1.5 in conjunction with Visual Studio 2015. The project has been set up to utilize the "ES 2015" module system and ECMAScript 6. My current challenge involves implementing the Angular Local Storage module, as outlined by ...

Encoding and decoding data in Base64 using native TypeScript types

When working in Node.js, I can utilize the Buffer class with both toString variations and the base64Slice method. In JavaScript within a browser, the btoa function is available for encoding. Despite these native options, I am struggling to find a built-i ...

Angular Dynamic CSS Library by BPNM

I'm currently working with the Bpmn library to create a diagram. However, I've encountered an issue where I need to hide the palette in the diagram view dynamically. I attempted to achieve this by using @ViewChild in my TypeScript file to target ...

I am encountering TypeScript errors during the build process due to importing extracted .graphql files which are causing unresolved types

Within my project, I have a file called @/graphql/mutations.graphql which contains the following code: mutation addCharacterToUser($userId: ID!, $characterId: ID!) { addCharacterToUser(userId: $userId, characterId: $characterId) { characterIds ...

The ts-node encountered an issue with the file extension in this message: "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension

When using ts-node, I encountered the following error: $ ts-node index.ts TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /home/projects/node-hddds8/index.ts I attempted to remove "type": "module& ...

react-vis: Utilizing multiple elements to showcase a complex chart

I have a project with multiple interfaces, and one of them is dedicated to displaying statistics. Currently, I am utilizing the react-vis library for this purpose. The issue arises when I attempt to display values similar to those shown in the provided i ...

Is it possible to utilize both $uibModal and $uibModalInstance within the same controller to create a modal popup in an Angular project incorporating TypeScript?

Being new to Angular with Typescript, I encountered an issue while trying to implement a modal popup in Angular. The problem arises when I have a dropdown menu that triggers the opening of a modal popup with two buttons, "Yes" and "No". To handle this, I h ...

What is the best way to generate a simulated API request in rxjs that triggers an error after a delay of 1 second?

Explore the code playground here => https://stackblitz.com/edit/playground-rxjs-f8xjsh Hello there, I'm attempting to replicate a failed API Call scenario where the error value should be emitted at the subscribe() { Error: } section. import { from ...

What is the best way to prioritize the display of custom login buttons based on the last button used for login?

I have implemented 4 different login methods for my app, each with its own associated component. I am looking to rearrange the buttons based on the last used login method. I already have a function to determine the last login method. let lastSignedInMetho ...

Error: No injection provider found for function(){}!

After countless hours of setting up a custom AOT in Angular 7 project without CLI and debugging, I have encountered the following error: Uncaught Error: StaticInjectorError(Platform: core)[function(){}]: NullInjectorError: No provider for function(){}! ...

Error in React.tsx Material UI Tab - Avoid using curly braces "{}" as a data type

Currently, I am working on a React TS project and facing an issue while trying to import the Material UI tabs for scrollable functionality. The specific Tabs/Scrollable Tabs example from Material-UI documentation is what I'm referring to: https://mate ...

Is it necessary to use StyleProp<style> in React-Native, or can I simply write the styles without it?

My usual writing style is as follows: interface _props{ style?: TextStyle, } However, I'm considering adding StyleProp. What do you think? interface _props{ style?: StyleProp<TextStyle>, } If so, what are the reasons for doing so? ...

Element in the iterator is lacking a "key" prop

import React from 'react' import { motion } from "framer-motion" type Props = {} export default function Projects({}: Props) { const projects = [1,2]; return ( <motion.div initial={{ opacity: 0 }} whileInVie ...

Arranging a list of objects with a designated starting value to remain at the forefront

Consider the array and variable shown below: array = ['complete','in_progress','planned']; value = 'planned'; The goal is to always sort the array starting with the 'value' variable, resulting in: array ...

Thoroughly verifying API responses in RTK Query with Zod schema

I am seeking to verify the API response I receive from a REST API by utilizing a Zod schema. As an illustration, I possess this user schema along with the corresponding API import { z } from 'zod'; import { createApi, fetchBaseQuery } from ' ...

Tips for implementing the select2 feature in asp.net core:

Currently, I am developing a project in ASP.NET Core and utilizing TypeScript. I am interested in integrating Select2 into my project. Can someone provide guidance on how to incorporate Select2 in ASP.NET Core? Additionally, is there a specific package t ...