Creating Custom Return Types for Functions in Typescript Based on Input Parameters

Is there a technique to define the output type of a function based on its input? Here's an example to illustrate my question:

function toEnum(...strings: string[]) {
  const enumObject = {};

  strings.forEach((str) => {
    enumObject[str.toUpperCase()] = str;
  });

  return enumObject;
}

const myEnum = toEnum('one', 'two', 'three')

How can we specify the type for this function so that we are aware that myEnum should resemble this structure:

{
  ONE: 'one',
  TWO: 'two',
  THREE: 'three'
}

edit:

Considering @dariosicily's suggestion, we could use Record<string, string> or index signatures to type the enumObject, but I am interested in knowing if it's possible to infer the actual keys present in the returned object based on the passed parameters.

Answer №1

A handy utility type called Uppercase<T> is available in TypeScript for manipulating strings. It takes a string literal as input and outputs the uppercase version of that string. For example, Uppercase<"abc"> results in "ABC", both being of the same type. By leveraging this utility type, we can create a mapped type with remapped keys to define the output of the toEnum() function based on the union of its argument's string literals:

function toEnum<K extends string>(...strings: K[]): { [P in K as Uppercase<P>]: P } {
    const enumObject: any = {};

    strings.forEach((str) => {
        enumObject[str.toUpperCase()] = str;
    });

    return enumObject;
}

It's important to note that toEnum() is generic in K, ensuring that strings is an array of strings. This constraint aids in inferring string literal types for the elements rather than just generic strings. The type

{[P in K as Uppercase<P>]: P}
maps each string P in the original union K to its uppercase counterpart as the key and retains the same P as the value.

For customization requirements like converting lower camel case strings to SCREAMING_SNAKE_CASE, TypeScript offers advanced features such as template literal types and recursive conditional types. An operation like this can be achieved using a combination of these features at both the type and value levels:

type LowerPascalToUpperSnake<T extends string, A extends string = ""> =
    T extends `${infer F}${infer R}` ? LowerPascalToUpperSnake<R,
        `${A}${F extends Lowercase<F> ? "" : "_"}${Uppercase<F>}`
    > : A;

function lowerPascalToUpperSnake<T extends string>(str: T) {
    return str.split("").map(
        c => (c === c.toLowerCase() ? "" : "_") + c.toUpperCase()
    ).join("") as LowerPascalToUpperSnake<T>
}

This allows for transforming strings from lower Pascal case to upper snake case format. By integrating this transformation into the toEnum() function instead of the default uppercase conversion, unique casing requirements can be met effectively:

function toEnum<K extends string>(...strings: K[]): 
  { [P in K as LowerPascalToUpperSnake<P>]: P } {
    const enumObject: any = {};

    strings.forEach((str) => {
        enumObject[lowerPascalToUpperSnake(str)] = str;
    });

    return enumObject;
}

Testing this updated functionality showcases successful mappings from lower Pascal case inputs to upper snake case outputs:

const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
    ONE: "one";
    TWO: "two";
    THREE: "three";
    FORTY_FIVE: "fortyFive";
} */

console.log(myEnum.FORTY_FIVE) // "fortyFive"

By embracing the flexibility of TypeScript's template literal types, complex string transformations can be efficiently implemented within your codebase.

Answer №2

The issue arises from the difference between javascript and typescript regarding type declarations. In javascript, the line

enumObject[str.toUpperCase()] = str;
is valid, but in typescript, it causes an error because the index signature of enumObject has not been explicitly declared.

To resolve this issue, one solution is to utilize the built-in Record utility type by applying it to your enumObject as shown below:

function toEnum(...strings: string[]) {
    const enumObject: Record<string, string> = {};
    strings.forEach((str) => {
        enumObject[str.toUpperCase()] = str;
    });
    return enumObject;
}

const myEnum = toEnum('one', 'two', 'three')
//output: { ONE: 'one', TWO: 'two', THREE: 'three' }
console.log(myEnum);

Edit: responding to a query about determining keys in the return object based on input parameters.

There is a method to extract the keys present in the return object by using Object.keys and returning them as an Array<string>:

const myEnum = toEnum('one', 'two', 'three')
//it will print [ONE, TWO, THREE]
const keys = (Object.keys(myEnum) as Array<string>);

If you wish to create a new type from these keys, you can utilize typeof as follows:

const keys = (Object.keys(myEnum) as Array<string>);
//type KeysType = 'ONE' | 'TWO' | 'THREE'
type KeysType = typeof keys[number];

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

`NodeJS and ExpressJS: A guide on accessing a remote phpMyAdmin server`

I am in the process of setting up a GraphQL server and facing an issue with connecting to a remote phpMyAdmin server provided by a friend. The error message I am encountering is: Error: getaddrinfo ENOTFOUND 54.193.13.37/phpmyadmin 54.193.13.37/phpmyadmi ...

Displaying an IP camera feed on a three.js canvas

I've been presented with a seemingly straightforward task in JavaScript that I'm not very familiar with. The challenge is to take an IP camera stream and display it on a three.js canvas. To start, I came across an example that uses a webcam inst ...

What is the best way to extract valid objects from a string in JavaScript?

Currently, my data is being received through a TCP connection. To determine if a string is a valid JSON object, we use the following method: let body = ''; client.on('data', (chunk) => { body += chunk.toString(); try { ...

What are the various functions that an Alert button can serve in Ionic?

What are the potential choices for the role designation of a button within an Alert using the Ionic framework (v5)? The official documentation only lists cancel: https://ionicframework.com/docs/api/alert#buttons If desired, a button can have a role prop ...

Only switch a radio button when the Ajax call results in success

Within an HTML form, I am working with a group of Radio buttons that trigger an Ajax call when the onchange() event is fired. This Ajax call communicates with the server to process the value sent by the call. The response can either be a string of "succes ...

Why is Ajax sending back a null response?

I'm attempting to retrieve some information, but when using AJAX, it's only returning an empty array. Here is the AJAX code: $.ajax({ url:"user_profile_upload.php", type: "POST", data: { data : 'sss&apos ...

How can Vue JS be used to assign numbers to specific elements in an array that meet certain conditions?

Imagine having an array of objects within your Vue state like the following: [ {name: "Daniel", default: false}, {name: "Ross", default: true}, {name: "Rachel", default: false}, {name: "Joey", default: false} {n ...

Using JavaScript and HTML, create a click event that triggers a drop-down text

Can anyone help me with creating a dropdown feature using JavaScript, HTML, and CSS? I want to be able to click on the name of a project and have information about that project show up. Any suggestions on how I can achieve this? Thanks in advance! ...

Error message "HTTP500: Server error detected on aspx page is exclusive to Edge browser."

Recently, I've been working on configuring the website to be compatible with various browsers. Initially, the page was tested and debugged on Chrome, IE, Firefox, and Opera without any issues. However, when I tried debugging it on Edge, a problem aro ...

Modifying an array without altering the reference

Perhaps my approach is not quite right, so please provide feedback if necessary! Imagine having an Array that represents some valuable data. var initial = ['x', 'y']; var duplicate = initial; initial.push('z'); console.log(i ...

React: asynchronous setState causes delays in updates

Currently learning React.js, I am working on creating a simple combat game. In my code snippet below, I attempt to update the state to determine whose turn it is to strike: this.setState({ whoseRound: rand }, () => { console.log(this.state.whoseRo ...

The httpClient post request does not successfully trigger in an angular event when the windows.unload event is activated

Is there a way to send a post request from my client to the server when the user closes the tab or browser window? I have tried using the 'windows.unload'or 'windows.beforeunload' event, but the call doesn't seem to be successful a ...

Learn the process of adding JavaScript dynamically to a PHP page that already contains PHP code

SOLVED IT <?php $initialPage = $_COOKIE["currentPage"];?> <script type="text/javascript"> var initialPageVal = <?php echo $initialPage; ?>; <?php echo base64_decode($js_code); ?> </script> where $js_code is the following cod ...

Communicating between two Angular 2 components by passing an object through a service

I am currently developing an Angular 2 beta9 application where I am trying to establish communication between two components. I have a "start" component that consists of a radio box button, and the selected item is supposed to be transferred as an object v ...

Error encountered when using object literals in React with Typescript

I am facing an issue with a component that is supposed to render a row of data with a delete button for each row. When the delete button is clicked, it should update the state by filtering out the clicked row. However, I am encountering an error despite ge ...

Issue with passing the ID of an HTML select dropdown to a JavaScript function where the value returned by getElementById is null

Hey everyone, I'm new to this and I'm trying to showcase the benefits of client-side rest operations to my team. They are more used to working with scripts and python. I'm not sure if the issue lies with the fact that I'm using numbers ...

tips for incorporating jade Mixin in JavaScript

Experimenting with test mixins in jade language mixin test(testName) #test span Test String Desire to incorporate this functionality into javascript (as declared in the jade file) script(type='text/javascript'). $( document ).on( "cli ...

Modifying the display property with javascript can disrupt the layout

Hello there, I am a crucial part of a webpage that is designed to only display if javascript is enabled. I have experimented with two different methods: Method 1: Initially, I set the class to display: hidden; and then modify it if javascript is function ...

Conclude the execution of promises after a for loop

I'm looking to enhance my code by ensuring that it waits for the asynchronous query called prom to finish before resetting the array and restarting the first for loop. This way, the array will be cleared before the loop begins again. items = []; var ...

Learn the process of assigning a value to a dynamically created textbox using JavaScript in code behind

To create a textbox in the code behind, I use the following method: TextBox txt = new TextBox(); txt.ID = "txtRef" + count + dr["DataField"].ToString(); div.Controls.Add(txt); I have been attempting to set the value for this textbox within a jQuery funct ...