Tips for typing a subset of an array containing string literals in TypeScript

Is it possible to have a function called createFields that takes a generic Object type, such as User, and extracts a subset of keys that can be inferred with a string literal array, similar to the selectable property in Fields? If so, how can this be achieved using TypeScript? Thank you.

Below is the code snippet
interface User {
  id: number;
  username: string;
  password: string;
  age: number;
}

interface Fields<T extends object = {}> {
  selectable: Array<keyof T>;
  sortable: Array<keyof T>;
}

function createFields<T extends object = {}>({ selectable, sortable }: Fields<T>) {
  return {
    // How can I retrieve the type of selectable as ("id" | "username" | "age")[]
    select(fields: typeof selectable): string {
      return fields.join(',')
    }
  }
}
const UserFields: Fields<User> = {
  selectable: ['id', 'username', 'age'],
  sortable: ['username', 'age']
}

const fields = createFields(UserFields)
// current output
// => fields.select = (fields: ("id" | "username" | "password" | "age")[]): string;

// expected output 
// => fields.select = (fields: ("id" | "username" | "age")[]): string;

Answer №1

The issue lies in the lack of generality in Fields<T>. The properties selectable and sortable are defined as Array<keyof T>, which makes it difficult to identify the specific subset of keyof T being used. To address this, we can modify createFields() to accept a generic type F constrained to Fields<T>.

An obstacle faced when implementing this solution is TypeScript's inability to perform partial type argument inference, requiring both T and F to be specified or inferred together. This can be circumvented using currying:

const createFieldsFor = <T extends object>() =>
    <F extends Fields<T>>({ selectable, sortable }: F) => ({
        select(fields: readonly F["selectable"][number][]): string {
            return fields.join(',');
        }
    });

If you have no preference for a specific T, leaving it unspecified is a valid option:

const createFields = <F extends Fields<any>>({ selectable, sortable }: F) => ({
    select(fields: readonly F["selectable"][number][]): string {
        return fields.join(',');
    }
});

Note that the argument to fields is defined as

readonly F["selectable"][number][]
, allowing any array or readonly array of that type rather than enforcing a fixed order/length.


To maintain specificity in UserFields, preventing widening to Fields<T> and avoiding conversion of selectable and sortable to string[], consider defining UserFields with const assertion:

const UserFields = {
    selectable: ['id', 'username', 'age'],
    sortable: ['username', 'age']
} as const;

By utilizing a const assertion, arrays within UserFields are kept narrow and marked as readonly. To accommodate regular and read-only arrays, update the Fields<T> interface accordingly:

interface Fields<T extends object> {
    selectable: ReadonlyArray<keyof T>;
    sortable: ReadonlyArray<keyof T>;
}

The choice between a curried or non-curried approach should align with the specific use case. It is crucial to ensure that createFields() supports the type of keys expected in the selectable and sortable properties. Customizing the implementation based on individual requirements is recommended. Best of luck!

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

Swiping in Angular2 gets a new twist with Swiper typings

Having trouble importing typings for Swiper into my Angular 2 project. After installing Swiper and its typings using npm, I tried including Swiper in my component like this: import { Swiper } from 'swiper'; However, Atom displays an error: ...

An insightful guide on effectively binding form controls in Angular using reactive forms, exploring the nuances of formControlName and ngModel

Here is the code snippet: list.component.html <form nz-form [formGroup]="taskFormGroup" (submit)="saveFormData()"> <div nz-row *ngFor="let remark of checklist> <div nz-col nzXXl="12" *ngFor="let task of remark.tasks" styl ...

What is the best way to display suggested words from a given list of options?

Looking for a way to provide suggestions to users based on a list of words they enter in TypeScript while maintaining performance. How can this be achieved efficiently? ...

What could be the reason for my npm package installed globally to not be able to utilize ts-node?

Recently, I've been working on developing a CLI tool for my personal use. This tool essentially parses the standard output generated by hcitool, which provides information about nearby bluetooth devices. If you're interested in checking out the ...

Convert TypeScript model to JSON while excluding properties with null values

When working with an Angular 4 App and a typescript model, I have defined a Person class as follows: export class Person{ fname:string, lname?:string } The 'lname' property in the model is optional. To populate the model in a component, I u ...

Managing nested request bodies in NestJS for POST operations

A client submits the following data to a REST endpoint: { "name":"Harry potter", "address":{ "street":"ABC Street", "pincode":"123", "geo":{ &q ...

The "path" parameter must be a string data type in order to proceed. The value received is currently undefined

My current project is utilizing Angular 8 When I attempt to run 'ng build --prod', my project encounters errors. ERROR in The "path" argument must be of type string. Received type undefined The issue arose after adding "enableIvy": true to the ...

Issue with Authentication - Sequencing of Observables and Promises in Angular/REST APIs

I'm currently utilizing Angular 7 and have recently started working on a new Angular application project at my agency. One of my colleagues has already set up the backend (Restful), so I began by focusing on implementing the Authentication Feature. T ...

Utilizing Generics in TypeScript to Expand Abstract Classes

How can I define the property eventList in the class ImplTestClass to be an array with all possible values of AllowedEvents, while extending the class TextClass that accepts T? I'm stuck on this one. If anyone can provide guidance on how to achieve t ...

Converting JSON to TypeScript with Angular Casting

I'm facing an issue detailed below. API: "Data": [ { "Id": 90110, "Name": "John", "Surname": "Doe", "Email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="472d282f2923282207202a262e2b ...

loop through an intricate JSON schema using Angular 5

I've been trying to figure out how to iterate a complex JSON in Angular 5. I came across the pipe concept, but it doesn't seem to work with JSON data like the one below. I'm trying to create an expandable table using this type of data, but I ...

Utilize the fetch function within a React functional component

I have been experimenting with different methods to fetch data only once before rendering, but I am encountering some challenges: It is not possible to call dispatch in componentDidMount as there is a restriction that it can only be done in Functional c ...

Exploring TypeScript: Ensuring Compatibility of Types

Given two sets of TypeScript type definitions in string format: Set A: { a: string b: number } Set B: { a: string } Is there a way to programmatically determine if these two sets are compatible? In other words, can we assign variables defi ...

Error occurs when attempting to test both boolean and number data within an ngIf statement

In the scenario where I am working with a template that includes a boolean called readOnly and an array known as arrayOfStuff: <span *ngIf="!readOnly && arrayOfStuff && arrayOfStuff.length">Hey</span> When running eitherng bui ...

The numerical value of zero in React Native TypeScript is being interpreted as NaN

When attempting to map an array in React Native (Android) and use its values or keys as data for return, I encountered an issue where the value 0 is read as NaN. This problem also arose when utilizing a typescript enum. The versions I am using are: typesc ...

`Browser Extension Compatibility``

I am currently working on developing a TypeScript extension that is compatible with all major browsers. I have come across the package https://www.npmjs.com/package/web-ext-types which I have integrated into my package.json file. While coding in TypeScrip ...

Tips for extracting specific JSON response data from an array in TypeScript

I have an array named ReservationResponse, which represents a successful response retrieved from an API call. The code snippet below demonstrates how it is fetched: const ReservationResponse = await this.service.getReservation(this.username.value); The st ...

I am looking to extract solely the numerical values

Programming Tools ・ react ・ typescript ・ yarn I am trying to extract only numbers using the match method But I keep encountering an error Error Message: TypeError: Cannot read property 'match' of undefined const age="19 years ...

Guide to Generating a Compilation Error with Method Decorators in Typescript

Currently, I am developing a library named expresskit which allows the use of decorators to define routes, params, and other functionalities for express. While refactoring the codebase, I am considering implementing restrictions on the types of responses a ...

Resolving TS2304 error using Webpack 2 and Angular 2

I have been closely following the angular documentation regarding webpack 2 integration with angular 2. My code can be found on GitHub here, and it is configured using the webpack.dev.js setup. When attempting to run the development build using npm start ...