Locate the constructor for the class of the array element

I came up with a simple solution:

function customFunction<T>(data:any, type:new(...args:any[])=>T):T{
    //An illustrative example to point out the problem
    if(data instanceof Array){
        return new type();
    }
    return [new type()];
}

My goal is for it to handle objects and arrays like this:

let result:Result = customFunction<Result>(Result); //Works as expected
let results:Result[] = customFunction<Result[]>(Result); //Expected to return an array of Result objects

The above does not work because Result is not the constructor for an array of Result.

I have tried these alternatives:

customFunction<Result[]>(Result[])
customFunction<Result[]>(Array<Result>)
customFunction<Array<Result>>(Array<Result>)

Answer №1

Try using signature overloading to determine whether to return an array or a single instance based on the parameters passed:

type Constructor<T> = new () => T;

function customFunc<T>(input: [Constructor<T>]): T[]
function customFunc<T>(input: Constructor<T>): T
function customFunc<T>(input: Constructor<T> | [Constructor<T>]) {
    if(input instanceof Array){
        return [new input[0]];
    }
    return new input();
}

let arrayData = customFunc([Example]);
let output = customFunc(Example);

Answer №2

In your request, you mentioned:

let bar:Bar = foo<Bar>(Bar); //Works fine actually
let bars:Bar[] = foo<Bar[]>(Bar); //Should return a Bar[]

This approach will not work because types are erased at runtime. The JavaScript code for both will look like this:

let bar = foo(Bar);
let bars = foo(Bar);

Hence, there's no way to distinguish between them.

A possible solution suggested by @AlekseyL is to have two separate functions to handle the array and non-array cases. However, if you insist on using a single function, we need to find a way to determine what to do at runtime.

First, let's define the Constructor type to simplify things:

type Constructor<T> = {
  new(...args: any[]): T;
}

Next, let's create an ArrayOf type which holds the class constructor. We also need a type guard to identify if something is an ArrayOf at runtime, along with a function that developers can utilize to create an ArrayOf:

type ArrayOf<T> = {
  clazz: Constructor<T>;
}
function isArrayOf<T>(x: Constructor<T> | ArrayOf<T>): x is ArrayOf<T> {
  return 'clazz' in x;
}
function ArrayOf<T>(clazz: Constructor<T>): ArrayOf<T> {
  return { clazz };
}

Finally, let's implement the foo() function:

function foo<T>(clazz: Constructor<T>): T;
function foo<T>(arrayOfClazz: ArrayOf<T>): T[];
function foo<T>(x: Constructor<T> | ArrayOf<T>): T | T[] {
  if (isArrayOf(x)) {
    return [new x.clazz()];
  } else {
    return new x();
  }
}

This overloaded function can take either a constructor or an ArrayOf object and determines the appropriate action at runtime. Let's test it out:

class Bar {
  // ... 
}

const bar: Bar = foo(Bar);
const bars: Bar[] = foo(ArrayOf(Bar));

It should work as expected!


However, this method may seem complex compared to having two separate functions. Both approaches would require similar effort from developers:

  // original request
  let bar = foo<Bar>(Bar); 
  let bars = foo<Bar[]>(Bar); 

  // alternative solution
  let bar = foo(Bar);
  let bars = foo(ArrayOf(Bar));

  // using two functions
  let bar = foo(Bar);
  let bars = fooArray(Bar);

The two-function approach might be more user-friendly, but it's ultimately up to personal preference. In any case, I hope this information helps. Good luck!


Update

I noticed you added a new data parameter to the foo function, which indicates whether the input is an array or not. Based on this information, here is a straightforward solution:

function foo<T>(data: any[], clazz: Constructor<T>): T[];
function foo<T>(data: any, clazz: Constructor<T>): T;
function foo<T>(data: any, clazz: Constructor<T>): T | T[] {
  if (Array.isArray(data)) {
    return [new clazz()];
  } else {
    return new clazz();
  }
}

const bar: Bar = foo('bar', Bar);
const bars: Bar[] = foo(['bars'], Bar);

Wishing you success with this updated approach.

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

Dramatist: Unable to import session storage data from a JSON file into the browser environment

My task is to test an application built with React and NestJS. The session token can be found in the sessionStorage of my browser. To guide me through this process, I referred to the relevant part of the Playwright documentation: https://playwright.dev/ ...

A custom Typescript type for immutable values within an object

I am struggling with finding the right data type for my function, where I need to work with static types. I have experimented with Type, interface, class, Record, and others, but none seem to fit perfectly. GEOLOCATIONS is a constant record that maps cou ...

Converting <reference path/> directive to ESM import: A step-by-step guide

As I embark on developing a TypeScript application, I've reached the realization that I am not a fan of using the <reference path /> triple-slash directive. Instead, I prefer utilizing import 'something'. However, every time I attempt ...

Tips for using the arrow keys to navigate the cursor/caret within an input field

I am trying to create a function that allows the cursor/caret to move inside an input field character by character using the arrow keys (ArrowLeft, ArrowRight) on a keydown event. Current Approach: const handleKeyDown = (e: KeyboardEvent<HTMLInputEle ...

The loading spinner isn't appearing while the function is running

Having trouble displaying a loading spinner while a function is running. I've tried different methods, but the spinner just won't appear. Here's the HTML snippet: <div class="row pt-3" id="firstRow"> <div class="col"> <bu ...

Incorporating additional properties into a TypeScript interface for a stateless, functional component within a React Native application

When following the React Native documentation for consistent styling, a recommendation is made to create a <CustomText /> text component that encapsulates the native <Text /> component. Although this task seems simple enough, I'm facing d ...

Typescript check for type with Jest

Assume there is an interface defined as follows: export interface CMSData { id: number; url: string; htmlTag: string; importJSComponent: string; componentData: ComponentAttribute[]; } There is a method that returns an array of this obj ...

Navigating the thorny dilemma of catch-22 situations when dealing with parser

I'm struggling to incorporate @typescript-eslint/no-floating-promises into my ESLint guidelines. This necessitates the use of parserOptions. Below is my .eslintrc.js configuration: module.exports = { root: true, parser: '@typescript-eslint ...

Warning: React has detected that a non-boolean value of `true` was received for the attribute `my-optional-property`

source code import React from "react"; import { Button, ButtonProps } from "@material-ui/core"; interface MyButtonProps extends ButtonProps { "aria-label": string; "my-optional-property"?: boolean; } function MyCustomButton(props: MyButtonProps) { ...

Error in Angular 5: Attempting to access 'subscribe' property of undefined variable

I've been struggling for days trying to fix this error on Stack Overflow. Since I'm new to Angular, I decided to reach out to the community for help. The issue revolves around JWT authentication. ERROR TypeError: Cannot read property 'sub ...

Initial compilation of Angular 2 project with lazy-loaded submodules fails to resolve submodules

I'm working on an Angular 2 project (angular cli 1.3.2) that is structured with multiple modules and lazy loading. In my main module, I have the following code to load sub-modules within my router: { path: 'module2', loadChildren: & ...

Pass service API data across initial components within Angular 6

I am currently working on creating a navigation bar that includes categories, as well as a home component that also relies on those same categories. My goal is to avoid making multiple API calls and instead utilize a single variable for the categories thro ...

The ngShow directive is being assessed prematurely

In my development project, I have an Angular component and controller set up in the following way: export class MyController{ static $inject = [MyService.serviceId]; public elements: Array<string>; public errorReceived : boolean; ...

Angular and Jest combo has encountered an issue resolving all parameters for the AppComponent. What could be causing this error?

I am currently working within a Typescript Monorepo and I wish to integrate an Angular 8 frontend along with Jest testing into the Monorepo. However, I am facing some challenges. The tools I am using are: Angular CLI: 8.3.5 My Approach I plan to use ...

Using template literals with Optional chaining in Javascript does not yield the expected results

Trying to implement template literal with optional chaining. type Item = { itemId:number, price: number}; type ItemType = { A:Item, B:Item }; const data : ItemType = { A:{itemId:1, price:2}, B:{itemId:2, price:3} }; let key = `data?.${variable}?.ite ...

How can we limit the generic type T in TypeScript to ensure it is not undefined?

I have created a function called get(o, key), which is designed to work with any Object that meets the criteria of the { get: (key: K) => R } interface. Furthermore, I am interested in restricting the result variable R to not allow it to be undefined. ...

Encountering Errors while executing the yarn build or tsc commands

https://i.sstatic.net/JuueZ.pngWhenever I attempt to build a project or run the yarn tsc command, I encounter various types of errors. This seems to be due to them being installed in the incorrect location. But what could be causing this issue? Feel free ...

Subscribing to ngrx store triggers multiple emissions

Currently, I have an app with a ngrx store set up. I am experiencing an issue where, upon clicking a button, the function that fetches data from the store returns multiple copies of the data. Upon subsequent clicks, the number of returned copies grows expo ...

Is it possible for a class method in Typescript to act as a decorator for another method within the same

Can we implement a solution like this? class A { private mySecretNumber = 2; decorate (f: (x :number) => number) { return (x: number) => f(this.mySecretNumber * x); } @(this.decorate) method (x: number) { return x + 1; } } I h ...

What is the best way to handle typing arguments with different object types in TypeScript?

Currently, I have a function that generates input fields dynamically based on data received from the backend. To ensure proper typing, I've defined interfaces for each type of input field: interface TPField { // CRM id as a hash. id: string nam ...