Can TypeScript automatically deduce object keys from function argument values?

Consider this function for parsing strings.

  
  const { a, b } = parseString({
    string, 
    mapping: [{
      param: 'a',
      ...
    },{
      param: 'b',
      ...
    }]

Is there a way to restrict TypeScript to accept only 'a' and 'b' keys?

This is the current type definition I am using.

interface ParseStringArgs {
    string: string;
    mapping: {
        start: string | null;
        end: string | null
        param: string;
    }[];
}

I tried to define the key value for param.

export function parseString({ string, mapping }) {

...

    type ParamKey = typeof mapping[number]['param'];
    const result: Record<ParamKey, string> = {};

    ... some logic to populate result

    return result

The output of parseString currently is Record<string, string>

Can the return type be changed to Record<'a' | 'b', string>?

Answer №1

If you desire a function's return type to be determined by the types of its parameters in a flexible manner, then you must make the function generic. This requires explicitly declaring type parameters on the call signature as they are not automatically inferred for you (although there is an open feature request at microsoft/TypeScript#17428 for such functionality).

One approach to achieve this is:

function parseString<const P extends ParseStringArgs>({ string, mapping }: P) {
  type ParamKey = P["mapping"][number]['param'];
  const result = {} as Record<ParamKey, string>;
  return result
}

In this code snippet, a type parameter P is constrained to ParseStringArgs, and the function parameter is set to type P. Note that it's defined as a const type parameter to encourage the compiler to infer literal types for inputs, ensuring specificity when defining object properties.

The ParamKey type is derived from P through repeated indexed access. Avoid using typeof on mapping to prevent generic widening by the compiler.

Additionally, assert the result initializer as Record<ParamKey, string> since {} does not inherently match this type.


Let's now test the function:

const { a, b, z } = parseString({
  // -------> ~ error on z as expected
  string: "xyz",
  mapping: [{
    param: 'a',
  }, {
    param: 'b',
  }]
});

The test confirms that the return type of the function corresponds to {a: string, b: string}, prompting an error when attempting to destructure assign z, as intended.

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

The attribute 'y' is not found within the data type 'number'

Currently working on a project using Vue.js, Quasar, and TypeScript. However, encountering errors that state the following: Property 'y' does not exist on type 'number | { x: number[]; y: number[]; }'. Property 'y' does not ...

Unable to stop at breakpoints using Visual Studio Code while starting with nodemon

VSCode Version: 1.10.2 OS Version: Windows 7 Profesionnal, SP1 Node version: 6.10.0 Hey there. I'm attempting to debug TypeScript or JavaScript code on the server-side using Visual Studio Code with nodemon. I've set up a new configuration in la ...

Automatically select a value in MUI AutoComplete and retrieve the corresponding object

I recently set up a list using the MUI (v4) Select component. I've received a feature request to make this list searchable due to its extensive length. Unfortunately, it appears that the only option within MUI library for this functionality is the Au ...

Prisma allows for establishing one-to-many relationships with itself, enabling complex data connections

I am in the process of developing a simple app similar to Tinder using Prisma. In this app, users can swipe left or right to like or dislike other users. I want to be able to retrieve matches (users who also like me) and candidates (all users except myself ...

Show a table row based on a specific condition in Angular

I'm having this issue with the tr tag in my code snippet below: <tr *ngFor="let data of list| paginate:{itemsPerPage: 10, currentPage:p} ; let i=index " *ngIf="data.status=='1'" > <td> </td> <td> ...

Angular - Issue with deleting data using HTTPClientModule

I've encountered a strange issue with my Angular app where the delete request is not functioning as expected without any visible errors. Here's the code snippet from my service: import { Injectable } from '@angular/core'; import { HttpC ...

What is the process of molding items?

Here is an object that I have: { "id": "Test", "firstname": "Test", "lastname": "Test", "age": 83 } However, I only want to return the object with this value: { "id&quo ...

Using Material-UI with TypeScript

Attempting to integrate TypeScript/React with Material UI has been quite the challenge for me so far. Here is my index.tsx file: declare function require(p: string): any; var injectTapEventPlugin = require("react-tap-event-plugin"); injectTapEventPlugin( ...

Guide to assigning object values according to properties/keys

I'm currently diving into Typescript and exploring how to dynamically set object types based on specific keys (using template literals). Check out the code snippet below: interface Circle { radius: number; } interface Square { length: number; } ...

The utilization of `ngSwitch` in Angular for managing and displaying

I am brand new to Angular and I'm attempting to implement Form Validation within a SwitchCase scenario. In the SwitchCase 0, there is a form that I want to submit while simultaneously transitioning the view to SwitchCase 1. The Form Validation is fun ...

Retrieve the attribute from a TypeScript union data type

Here is the structure that I am working with: export interface VendorState extends PaginationViewModel { vendors: CategoryVendorCommand[] | CategoryVendorCommand; } This is my model: export interface CategoryVendorCommand { id: string; name: str ...

Encountering an error in Angular 4: 'Cannot find property on specified component type'

I recently embarked on the journey of learning Angular 4 and TypeScript, but I've encountered my first obstacle. My current challenge involves creating a simple date and time component. Despite having a seemingly correct Javascript code, I believe I ...

Indicate the location of tsconfig.json file when setting up Cypress

Having trouble integrating Cypress with Typescript? I've encountered an issue where Cypress is unable to locate the tsconfig.json file I created for it. My preference is to organize my project with a custom directory structure, keeping configuration f ...

Determine the time difference between the beginning and ending times using TypeScript

Is there a way to calculate the difference between the start time and end time using the date pipe in Angular? this.startTime=this.datePipe.transform(new Date(), 'hh:mm'); this.endTime=this.datePipe.transform(new Date(), 'hh:mm'); The ...

Using ng-if to evaluate multiple numerical values

I have multiple questionnaires, and I'm looking to use *ngIf to control which ones are displayed in a specific div. Currently, I'm handling this by specifying each individual question number: <div *ngIf = "questionNo!= '00' &&a ...

Executing multiple service calls in Angular2

Is there a way to optimize the number of requests made to a service? I need to retrieve data from my database in batches of 1000 entries each time. Currently, I have a loop set up like this: while (!done) { ... } This approach results in unnecessary re ...

Transforming jQuery library functions into TypeScript syntax

I have developed a directive using TypeScript. Here is an example of the code: 'use strict'; module App.Directives { interface IPageModal extends ng.IDirective { } interface IPageModalScope extends ng.IScope { //modal: any ...

What is the best approach to enhance a class definition that lacks types from DefinitelyTyped?

Recently, I came across the need to utilize the setNetworkConditions method from the Driver instance in selenium-webdriver. This method can be found in the source code here. Surprisingly, when checking DefinitelyTyped for TypeScript types, I discovered th ...

Utilizing the String Enum for mapping an interface with an index

Using Typescript, I aim to make use of my string enumeration: export const enum MutationKeys { registerUser = 'registration/REGISTER', registerUserCompleted = 'registration/REGISTER_COMPLETED' } This allows the string values t ...

Troubleshooting issue of data-binding failure with dynamic component loader in Angular2-universal-started

I am currently utilizing the angular2-universal-starter project. While attempting to pass an object to a child component using @Input, I encountered some issues with it not functioning correctly. I have implemented a dynamic component loader to load the ...