Why am I getting `Generic<T[]>` instead of `Generic<T>[]` when using generics and arrays together?

I'm working with a simple function that can handle either a single object of type T or an array of objects of type T[]. It processes the input and returns results corresponding to the type provided. For instance, if an array is passed in, an array of results will be returned; if a single item is passed in, a single result will be returned.

The transpiled JavaScript functions as expected, but I am running into issues with the type system. It keeps insisting on nesting the array inside the generic instead of resolving it as desired. I'm not sure why this is happening or how to make sure it resolves correctly.

Example Function

  • Playground Example
type OneOrMany<T> = T | T[]
type Item = Record<string, any>
type CapitalizedProps<T extends Item> = {
  [K in keyof T as Capitalize<K & string>]: T[K]
}

function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T>
function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[]
function toCapitalizedProps<T extends Item>(
  itemOrItems: OneOrMany<T>,
): OneOrMany<CapitalizedProps<T>> {
  if (Array.isArray(itemOrItems)) {
    return itemOrItems.map((item) =>
      toCapitalizedProps(item),
    ) as CapitalizedProps<T>[]
  }

  const result = { ...itemOrItems }

  for (const key in result) {
    result[(key[0].toUpperCase() + key.slice(1)) as keyof T] = result[key]
    delete result[key]
  }

  return result as unknown as CapitalizedProps<T>
}

Answer №1

Function overloads rely on the initial match.

function capitalizeProps<T extends Item>(item: T): CapitalizedProps<T>
function capitalizeProps<T extends Item>(items: T[]): CapitalizedProps<T>[]

Interestingly, an array satisfies the Item constraint of Record<string, any>, as arrays possess string properties with values that can be assigned to any.

const example: Record<string, any> = [] as string[] // acceptable

Therefore, when passing an array, the first overload is applied, resulting in the inference of T as an array type.


If you switch the order, the overload that requires an array will take precedence and activate if provided with an array. Otherwise, it will move on to the next one.

function capitalizeProps<T extends Item>(items: T[]): CapitalizedProps<T>[]
function capitalizeProps<T extends Item>(item: T): CapitalizedProps<T>

View playground

Answer №2

Overrides are processed based on their defined order.

Considering that T doesn't have any constraints preventing it from matching

{ name: string;  age: number; }[]
, the function toCapitalizedProps will return CapitalizedProps<T>, where T represents
{ name: string;  age: number; }[]
.

If you reverse the order, it should resolve your problem:

function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[]
function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T>
function toCapitalizedProps<T extends Item>() {
...

}

Interactive Demo

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

Dynamically apply classes in Angular using ngClass

Help needed with setting a class dynamically. Any guidance is appreciated. Below is the class in my SCSS file: .form-validation.invalid { border: 2px solid red } In my ts file, there's a variable named isEmailValid. When this variable is set to ...

Issue encountered when using await in Tensorflow.js sample code with TypeScript

After going through the official tensorflow.js documentation, I attempted to test this example using typescript with tensorflow.js While trying to execute the code snippet provided in the tensorflow.js documentation, I encountered an error related to the ...

What is the best way to create a function that can securely search for a URL containing parameters while ensuring type safety?

I am working on a NextJS/React application. We have a large object containing internal URLs, each with a createPath function that looks similar to this: const URLS = { dashboard: { createPath: ({ accountNumber }: { accountNumber: string }) => ...

In order to successfully run this generator XXX, you need to have a minimum of version 4.0.0-rc.0 of yeoman-environment. However, the current version available

When attempting to run a generator using the latest version of yeoman-generator (7.1.0), yo discord An error message pops up saying: This generator (discord:app) requires yeoman-environment version 4.0.0-rc.0 or higher, but the current version is 3.19.3. ...

Is there a way to determine if a browser's Storage object is localStorage or sessionStorage in order to effectively handle static and dynamic secret keys within a client?

I have developed a customizable storage service where an example is getExpirableStorage(getSecureStorage(getLocalStorage() | getSessionStorage())) in typescript/javascript. When implementing getSecureStorage, I used a static cipher key to encrypt every ke ...

The error at core.js:4002 is a NullInjectorError with a StaticInjectorError in AppModule when trying to inject FilterService into Table

While exploring PrimeNg Table control in my application - as a beginner in PrimeNg & Angular, I encountered an error No provider for FilterService! shown below: core.js:4002 ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppMo ...

Can a default value be assigned to a generic argument in Typescript?

I'm currently creating versatile methods for use across various frontend applications. The goal is to invoke the function .postAsync<CustomModel>('www.mysite.com',..., CustomModel); and receive a CustomModel object as the response. I ...

What are some ways to optimize the performance of a Select Box?

I am attempting to show a lengthy list of countries in an ion-select. Currently, there are 249 countries that I need to load. Unfortunately, the rendering performance is quite slow on my phone. <ion-list margin-top margin-bottom> <ion-item> ...

Nextjs introduces an innovative "OnThisDay" functionality, leveraging getServerSideProps and Date.now() to provide real-time information

I am currently working on adding an "OnThisDay" feature in my Nextjs project, inspired by Wikipedia's style of displaying events that happened on a specific date. To achieve this, I have implemented a function structured like the following code snippe ...

Is there a way to retrieve the object property within the subscribe function in order to display the HTML content?

Is there a way for me to update the HTML using the properties obtained within .subscribe? I am aware that .subscribe is asynchronous and therefore returns an undefined value initially, but how can I ensure it waits until the value is resolved? Currently, I ...

What is the method for displaying script commands within package.json files?

With a multitude of repositories, each one unique in its setup, I find myself constantly referencing the package.json file to double-check the scripts. "scripts": { "start": "npm run dev" "build:dev": "N ...

Stop tsc from creating declaration file for javascript component

I am currently maintaining an outdated react library and I am working on developing new components with added typing. This will provide me with autocompletion and type checking benefits when using it. However, due to time constraints, I am unable to revamp ...

How to navigate to the bottom of a webpage with Angular 4 using TypeScript's onClick event

Within my component, I have a template that looks like the following. When this div is clicked, the intention is to scroll to the bottom of the page. `<section><div onclick='scrollDown()'>Goto Reports</div></section><d ...

Updating props in a recursive Vue 3 component proves to be a challenging task

I am facing an issue with two recursive components. The first component acts as a wrapper for the elements, while the second component represents the individual element. Wrapper Component <template> <div class="filter-tree"> &l ...

Stuck on loading screen with Angular 2 Testing App

Currently working on creating a test app for Angular 2, but encountering an issue where my application is continuously stuck on the "Loading..." screen. Below are the various files involved: app.component.ts: import {Component} from '@angular/core& ...

Error in Angular: Http Provider Not Found

NPM Version: 8.1.4 Encountered Issue: Error: Uncaught (in promise): Error: Error in ./SignupComponent class SignupComponent_Host - inline template:0:0 caused by: No provider for Http! Error: No provider for Http! The error message usually indicates the a ...

Please ensure the subscription has completed before proceeding with the loop

I am currently working on an Angular application that retrieves data from an API and uses one of its parameters from a looped array. The issue I'm facing is that the data is pushed in a random order due to the continuous looping without waiting for th ...

The function of type 'PromiseConstructor' is not executable. Should 'new' be added? React TypeScript

.then causing issues in TypeScript. interface Props { type: string; user: object; setUserAuth: Promise<any>; } const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); if (type === "signup" ...

Obtain a comprehensive list of React state fields

Is there a way to retrieve a list of state fields in a React component? I am looking for a way to access and work with the fields stored inside a React.Component state dynamically. In the code snippet below, there is a method called getStateFieldList(), w ...

Enhance the API response for Angular service purposes

I am currently working on modifying the response returned by an API request. At the moment, I receive the response as: [ { name: "Afghanistan" }, { name: "Åland Islands" } ] My goal is to adjust it to: [ { name: "A ...