Exploring the Ways to Determine Array Type in Typescript Generics

I'm working with a method that looks like this:

public select(fieldName: keyof TType)

In this scenario, TType can potentially be an array type. If fieldName is called with a type of User[], I want to access the properties of User instead of the default properties of Array.

Is there a way to achieve this functionality?

On top of that, is it possible to limit TType to only accept array types?

Answer №1

If you want to create a function that can unwrap an array type up to one level deep and then use conditional types with the result, you can achieve it like this:

// Unwrapping up to one level
type Unarray<T> = T extends Array<infer U> ? U : T;

// Example usage in a class
declare class Container<T> {
  constructor(item: T);
  public selectItem(fieldName: keyof Unarray<T>): void;
}
// Example interface
interface Person {
  name: string,
  age: number
}

declare const p1: Person;
declare const p2: Person;
const container = new Container(p1);
container.selectItem("name"); // works fine
const anotherContainer = new Container([p1, p2]);
anotherContainer.selectItem("age"); // works fine
anotherContainer.selectItem("push"); // error

This should fulfill your typing requirements. However, for actual implementation, you may need additional adjustments, as implementing conditional types usually involves some type assertions or overloads to satisfy the compiler. But based on your question, it seems you are interested in the typings rather than the implementation details.


In response to your additional query, yes, you can constrain T to only accept array types by specifying it like so:

// Example class
declare class Box<T extends Array<any>> {
  constructor(items: T);
  public selectItem(fieldName: keyof (T[number])): void;
}
// Example interface
interface Person {
  name: string,
  age: number
}

declare const person1: Person;
declare const person2: Person;
const box1 = new Box(person1); // error
const box2 = new Box([person1, person2]);
box2.selectItem("age"); // works

In this case, I simplified the approach by removing conditional types altogether for clarity and simplicity of the code.

Answer №2

To extract the boxed type, you will require a little helper:

type Unboxed<T> =
    T extends (infer U)[]
        ? U
        : T;

Your method can be structured in this manner:

interface User {
    id: symbol;
    name: string;
}

class Foo {
    select(fieldName: keyof Unboxed<User[]>) {
        console.log(fieldName) // "id" | "name"
    }
}

In response to your additional query, it is indeed feasible, although it may seem somewhat unconventional.

class Foo {
    select<T extends any[]>(fieldName: keyof Unboxed<T>) {
        console.log(fieldName)
    }
}

new Foo()
  .select<Window[]>('addEventListener')

The purpose of type parameters is to define the arguments residing within the method or the generic type of the class. Perhaps you intended to implement the following approach:

class Foo<T extends any[]> {
    select(fieldName: keyof Unboxed<T>) {
        console.log(fieldName)
    }
}

new Foo<Window[]>()
  .select('addEventListener')

Answer №3

By utilizing array indexing with number, one can deduce the type parameter T from an array defined as type U = T[], expressed as U[number].

type Foo = {"foo": string};
type Bar = {"bar": number};
type FooArray = Foo[];
type BarArray = Bar[];

FooArray[number]; // {"foo": string}
BarArray[number]; // {"bar": number}

Check out this TypeScript Playground link for a live demonstration of the concept.

Answer №4

I have personally found success in using TType[0] as the type in my own experience. For example:

public select(fieldName: keyof TType[0])

Even though it may not look great, it actually gets the job done!

Answer №5

When considering the use of the keyof operator, it's important to understand that it will result in an array because fieldName will consist of only the keys in the TType type.

If your goal is to have an array of User, you should define TType accordingly:

type TType = User[]
...
public select(fieldName: TType) {
   ...
}

If you want TType to encompass any array, it would be defined as: type TType = any[]

However, with the latter definition, TypeScript may not accurately infer the inner type (e.g., User) which might not align with your intentions.

Alternatively, if TType could represent various types of arrays: type TType = User[] | Whatever[] | ...

For a more detailed response, please provide further clarification on your requirements.
In the meantime, I hope this explanation proves helpful ;)
Seb

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

Implement Material-UI's built-in validation for form submission

I'm in the process of setting up a form with validation: import React from 'react'; import { useForm } from "react-hook-form"; import axios, {AxiosResponse} from "axios"; import {Box, Button, Container, Grid, Typography} ...

How can one use TypeScript to return a subclass instance within a static function of a base class?

Below is the code snippet: class BaseElement { public static create<T extends typeof BaseElement>(this: T ): InstanceType<T> { this.createHelper(); const r = new this(); return r; } public static createHelpe ...

The module does not contain 'toPromise' as an exported member in rxjs version 5.5.2

Encountering an error when using toPromise Prior method: import 'rxjs/add/operator/toPromise'; Updated approach: import { toPromise } from 'rxjs/operators'; The new way is causing the following issues: [ts] Module '"d:/.../ ...

Why does TypeScript require a generic type parameter when arguments have already been provided?

When I attempted to use the argument p to infer type P, TypeScript still prompted me to provide type P. Why is that? const numberStringConverter = <T extends string | number,P extends {x: any}>(p: P): T => { if(typeof p.x === 'string') ...

Locate the nearest upcoming date and time to today's date in the JSON response

I am currently working with an API that provides a response containing the `start_time` field in JSON format. My goal is to extract the ID from the JSON object whose next date time is closest to the current date and time, excluding any dates from the past. ...

What are the TypeScript type definitions for the "package.json" configuration file?

What is the most efficient method for typing the content of the "package.json" file in TypeScript? import { promises as fs } from 'fs'; export function loadManifest(): Promise<any> { const manifestPath = `${PROJECT_DIR}/package.json`; ...

Converting a JSON array into a TypeScript array

Looking to convert a JSON array into a TypeScript variable array. The JSON data retrieved from http://www.example.com/select.php: { "User":[ {"Name":"Luca M","ID":"1"}, {"Name":"Tim S","ID":"2"}, {"Name":"Lucas W","ID":"3"} ...

Issue with TypeScript: Declaring type for objects in an array using .map

I'm having trouble assigning types to the Item object that is being returned from unitedStates.map((item: Item) => {}. Despite my efforts, I am unable to correctly define the types. Although I have specified the unitedStates array of objects as un ...

What is the proper classification for me to choose?

Apologies for the lack of a suitable title, this question is quite unique. I am interested in creating a function called setItem that will assign a key in an object to a specific value: const setItem = <T, U extends keyof T>(obj: T) => (key: U, ...

Tips for utilizing programmatic object keys as TypeScript type?

Consider the object shown below: const obj = { foo: "bar", hello: "world", } and this function for processing objects: const process = (obj) => { const processedObj = {} for (const key in obj) { processedObj[`--${key}`] ...

Cannot locate module using absolute paths in React Native with Typescript

I recently initiated a new project and am currently in the process of setting up an absolute path by referencing this informative article: https://medium.com/geekculture/making-life-easier-with-... Despite closely following the steps outlined, I'm en ...

What is the process of converting the Object type returned from an Observable to an array of objects in Angular?

When utilizing the GET method to retrieve information, I have encountered a problem. Here is the code snippet: constructor(private http: HttpClient) { } items: Item[]; stuff: any[]; ngOnInit() { const url = ...; this.http.get(url) .subscribe(nex ...

What is the process for implementing custom color props with Material-UI v5 in a React TypeScript project?

Looking to enhance the MUI Button component by adding custom color props values? I tried following a guide at , but encountered errors when trying to implement it in a custom component. The custom properties created in createPalette.d.ts did not work as ex ...

Ionic - What is the correct way to import ViewController? - Uncaught (in promise): Error: ViewController provider not found

I have a Popover in my app and I want it to behave differently based on the selected item. I followed the instructions in the Ionic documentation to achieve this. Error: Uncaught (in promise): Error: No provider for ViewController! When I tried adding ...

Encountered an error when incorporating nguniversal/express-engine into an Angular project: "Unable to locate the BrowserModule import in /src/app/app.module.ts"

One of the initial questions Purpose The main aim is to integrate SSR into my Angular project using ng add @nguniversal/express-engine --clientProject [name] (to enable dynamic prerendering of meta tags). Expected Outcome I anticipated the command to run ...

Modify information in formArray

Let's take a look at this setup I have: Model: export class MapDetailModel{ id: number; lat: number; lon: number; alt: number; long: number; angle: number; distance?: number; pendenza?: number; } Html: <div clas ...

Tips for preventing duplication of the interface in Typescript React

Embarking on my first Typescript app, I am determined to maintain a structured approach by keeping styles and components in separate files. With an ambitious project scope of numerous components, I intend to utilize props for calling classes, ensuring each ...

Instructions for including a class are ineffective

I am trying to dynamically add a class to a div based on two conditions. To achieve this, I have created a custom directive as shown below: import { Directive, HostBinding, Input } from '@angular/core'; @Directive({ selector: '[confirmdia ...

Updating DynamoDB objects seamlessly in Lambda functions without any conflicts

I am currently working with example Objects that follow this interface structure: interface Car{ id: Number; name: String; tires: Wheel[] } interface Wheel{ id: Number; name: String; radius: Number; } My goal is to store these Car Objects in DynamoDB and ...

React Typescript: The element is implicitly assigned an 'any' type as the type does not have an index signature

While attempting to locate a key of an object using an item from an array, I encountered an error... An Element implicitly has an 'any' type because type lacks an index signature I've replicated the issue in this sandbox https://codesandbo ...