Ways to expand a generic tuple in TypeScript

I am attempting to develop a function that enhances objects within an array and then returns them. I want the returned type for each item to retain the literals from the generics used. Is there a method to achieve this goal?

type Identified<Id extends string = string> = { id: Id };
type Extended<Id extends string> = Identified<Id> & { test: "test" };

let one: Identified<"one"> = {id:"one"};
let two: Identified<"two"> = {id:"two"};
let three: Identified<"three"> = {id:"three"};

function extendList<A extends Identified>(arr: A[]) {
  return arr.map((item) => ({ ...item, test: "test" }));
}

let extendedList = extendList([one,two,three]); // literal is lost
let oneExtended = extendedList[0]; // this should be {id:"one", test:"test"} (not {id:string...})

Playground Link

Answer №1

Here's an alternative method:

const modifyList<T extends number[]>(
  list: [...{ [I in keyof T]: Altered<T[I]> }]
) {
  return list.map((item) => ({ ...item, changed: true })) as
    { [I in keyof T]: Modified<T[I]> };
}

The concept behind modifyList() is to introduce a generic approach with the paramenter T, which represents a tuple composed of numerical literal types corresponding to the indexes of the elements in the input list. So if you invoke modifyList([1, 2, 3]), T will be [1, 2, 3].

Subsequently, the type of the incoming list is transformed into a mapped tuple where each element T[I] (the Ith item within the T tuple) is converted to Identified<T[I]>, while the outcome consists of a mapped tuple that converts each element T[I] to Modified<T[I]>.

It's worth noting that the type of list doesn't exclusively adhere to the mapped format

{ [I in keyof T]: Identified<T[I]> }
, but instead has been encased in a variadic tuple represented by
[...{ [I in keyof T]: Identified<T[I]> }]
; this particular setup aids the compiler in favoring inferred tuple types over disordered arrays. If not structured in such a way, modifyList([1, 2, 3]) might lead to an inference of
("1" | "2" | "3")[]
for T, which may deviate from your intentions.

Similarly, it was necessary to utilize a type assertion to notify the compiler that

list.map(item => ({...item, changed: true}))
matches the intended output type. The compiler cannot autonomously deduce or verify this aspect. Essentially, the compiler lacks the capability to discern that list.map(...) corresponds precisely to your description. Refer to this relevant answer for further insights on this constraint.

To ensure that it operates as expected:

let one: Identified<1> = { index: 1 };
let two: Identified<2> = { index: 2 };
let three: Identified<3> = { index: 3 };
let modifiedList = modifyList([one, two, three]);
// let modifiedList: [Modified<1>, Modified<2>, Modified<3>]
let oneModified = modifiedList[0]; 
// let oneModified: Modified<1>

All seems well!

Access playground link for code

Answer №2

Give this a shot

interface IdentifiedEntity<Id extends string = string> {
  id: Id;
}

type ExtendedEntity<Id extends string> = IdentifiedEntity<Id> & { test: string };

const entityOne: IdentifiedEntity<"one"> = { id: "one" };
const entityTwo: IdentifiedEntity<"two"> = { id: "two" };
const entityThree: IdentifiedEntity<"three"> = { id: "three" };

function extendEntityList<T extends string>(arr: IdentifiedEntity<T>[]): ExtendedEntity<T>[] {
  return arr.map((item) => ({ ...item, test: "test" }));
}

const extendedEntityList = extendEntityList([entityOne, entityTwo, entityThree]);
const oneExtendedEntity = extendedEntityList[0];

Answer №3

If you ever find yourself in need of deriving a tuple type from a generic tuple, take a look at the ExtensibleList implementation below:

type Identified<Id extends string = string> = { id: Id };
type ExtensibleList<List, Extension> = {
  [I in keyof List]: List[I] & Extension;
};

let one: Identified<"one"> = {id:"one"};
let two: Identified<"two"> = {id:"two"};
let three: Identified<"three"> = {id:"three"};
const list = [one,two,three] as const;

type ExtendedIdList = ExtensibleList<typeof list,{test:"test"}>

let extendedList:ExtendedIdList = [{id:"one", test:"test"}, {id:"two", test:"test"}, {id:"three",test:"test"}];

Code Playground Link

this approach is necessary because according to TypeScript documentation, arrays are essentially objects with numeric keys and support for mapped tuples was introduced in version TS 3.1+

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

Exploring an object to retrieve values based on keys using javascript

Currently, I have an array of objects set up like this: https://i.sstatic.net/l6d3z.png My goal is to loop through the array and extract users based on a specific roomCode So, I've attempted two different approaches: for(let i=0; i <data. ...

Encountered a problem with regular expressions in Angular 2 - a Module parse error due to an octal literal in strict mode

Greetings, I have encountered an issue with a regular expression in my environment.ts file. export const environment = { passwordPolicy: "^(?!.*(.)\1\1)(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}.*$" }; Unfortunately, whe ...

Modifying the @input value in the child component does not seem to reflect the changes in the parent component

parent component class export class Parent { display: boolean = false; constructor() { } displayChildComponent() { this.display = true; } } parent component template <child [isVisible]="display"></child> child component ...

Retrieve information from subscriber and store it in a local variable

Angular 6 Service getProjectEpics(id: string): Observable<any> { return this.http.get<any>(this.baseUrl + 'getEpics/' + id); } Component Angular 6 projectEpics=[]; getProjectEpics(id: string) { this.epicService.getProjectEpics(this ...

Creating efficient React components with TypeScript and leveraging generic props

I’ve been working on understanding and resolving my issue with React components' generic props. I came across a code example in an article about using TypeScript with functional React components and generic props. Unfortunately, when trying to repli ...

When running `aws-cdk yarn synth -o /tmp/artifacts`, an error is thrown stating "ENOENT: no such file or directory, open '/tmp/artifacts/manifest.json'"

Starting a new aws-cdk project with the structure outlined below src └── cdk ├── config ├── index.ts ├── pipeline.ts └── stacks node_modules cdk.json package.json The package.json file looks like this: " ...

Compile time extraction of constant string from type field

I am currently facing an issue with a field in my type that contains a constant string literal. My goal is to be able to reference both the type and field by name so that I can utilize this string literal throughout my code. Here is an example: export type ...

Error in NextJS: The name 'NextApplicationPage' cannot be found

const { Component, pageProps}: { Component: NextApplicationPage; pageProps: any } = props After implementing the code above with 'Component' type set to NextApplicationPage, an error message pops up stating, The name 'NextApplicationPage&ap ...

What is the significance of `/// <reference types="react-scripts" />` in programming? Are there any other XML-like syntaxes that are commonly used in *.d.ts

When working with normal *.d.ts files (which are definition files for TypeScript), we typically use declare *** export interface *** However, there is also this syntax: /// <reference types="react-scripts" /> This syntax is generated by create- ...

SolidJS does not support reactivity for arrays of objects

I've been scratching my head trying to figure out why this code isn't working as expected. I'm simply updating an object and expecting it to be refreshed in the DOM, but for some reason, that's not happening. The console log confirms th ...

What are the steps to utilizing mattooltip effectively?

Can someone help me figure out how to successfully implement mattooltip in this code? It's not working as expected. <div class="btn-edit-nounderline" matTooltipClass="custom-tooltip" (click)="edit(row.widgetAccess)" ...

TSLint Alert: Excessive white space detected before 'from' keyword (import-spacing)

I'm currently using WebStorm and working to maintain a specific code style: https://i.sstatic.net/r1n7n.png However, I encounter an issue where TSLint highlights my spacing and provides the following hint: "Too many spaces before 'from' ...

Can someone explain how to define "a type of object that must not be empty" in typescript?

I'm working with a function that can operate on any type T, but with the constraint that if T is an object, it cannot potentially be empty. Here's what I've tried: declare function myFunction<T>(param: T extends Record<string, neve ...

What is preventing this from being a function?

It appears that the authenticationProvider is missing for some reason. @autoinject() export class ProviderManager implements AuthenticationManager { constructor( private container: Container ){ } public authenticate( creds: Credentials ): Promis ...

What is the best approach to converting an array of strings into a TypeScript type map with keys corresponding to specific types?

The code provided below is currently working without any type errors: type Events = { SOME_EVENT: number; OTHER_EVENT: string } interface EventEmitter<EventTypes> { on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => voi ...

Error message: 'import not located' when using custom types in Vue 3 with Vite/Vitesse

I'm currently working on a Vue 3 project using Vitesse (which is based on Vite). I created a custom type called ProductData in my src/types.ts file. However, when I try to use this type in one of my pages, the page fails to load and I see multiple con ...

Restricting array elements through union types in TypeScript

Imagine a scenario where we have an event type defined as follows: interface Event { type: 'a' | 'b' | 'c'; value: string; } interface App { elements: Event[]; } Now, consider the following code snippet: const app: App ...

The type checkbox cannot be converted to type number

Currently, the TodoApp is utilizing the Inquirer.js module for questioning purposes. However, an issue has arisen with the error message stating type checkbox is not assignable to type number. function promptComplete(): void{ console.clear(); inq ...

Utilizing the Pub/Sub architecture to integrate the kafka-node library within Node Js

Utilizing the kafka-node module in my NodeJs Microservise project, I am aiming to implement a Pub/Sub (publisher and subscriber) design pattern within the Functional programming paradigm. producer.js const client = new kafka.KafkaClient({ kafkaHost: ...

How to store an imported JSON file in a variable using TypeScript

I am facing a challenge with a JSON file that stores crucial data in the following format { "login": { "email": "Email", "firstName": "First name", "lastName": "Last name", ...