Create a union type by utilizing indices of an array type

For instance:

type BasicTheme = {
  name: 'basic';
  colors: [string, string];
};

type AdvancedTheme = {
  name: 'advanced';
  colors: [string, string, string, string];
};

type MainColor = ???; // 'main-1' | 'main-2';

type ExtraColor = ???; // 'extra-1' | 'extra-2' | 'extra-3' | 'extra-4';

The main query arises about the feasibility of utilizing the length property within the colors array to construct a mapped union type similar to the provided sample.

This simplified example serves the purpose of clarity, however, in its entirety, there exists a generic type known as Theme that accepts both name and length parameters, where length is utilized to generate a fixed tuple with a specific number of strings.

Answer №1

I have simplified @Aleksey L.'s solution and opted for a different approach:

type Keys<T> = keyof T & `${number}`;
type Keys1<T extends [...any]> = Exclude<Keys<[...T, any]>, '0'>

type Index_Test = Keys1<[string, string, string]> // '1' | '2' | '3'
type Name_Test = `Test-${Index_Test}`; // "Test-1" | "Test-2" | "Test-3"

Main variations include:

  • Avoidance of ad-hoc mapped types { [key in ...]: ... }
  • No use of recursive types (unlike @kaja3's solution).
  • Absence of conditional types X extends Y ? ... : ... (Exclude employs this internally, but it serves as a good abstraction)

Applied to the given example:

type DefaultDesign = {
  name: 'default';
  colors: [string, string, string];
};

type CustomDesign = {
  name: 'custom';
  colors: [string, string, string, string, string];
};

type WithTag<T extends {name: string, colors: string[]}> = `${T['name']}-${Keys1<T['colors']>}`;

type DefaultColoring = WithTag<DefaultDesign>; // 'default-1' | 'default-2' | 'default-3';
type CustomColoring = WithTag<CustomDesign>; // 'custom-1' | 'custom-2' | 'custom-3' | 'custom-4' | 'custom-5';

Interactive Example

Answer №2

To find a solution that is not evil, we can utilize a helper type that transforms a tuple like [string, string, string] into a union like 1 | 2 | 3. This transformation can be achieved recursively by leveraging the length property of the tuple.

type TupleToUnion<T extends any[]> =
    T extends [any, ...infer R] ? T['length'] | TupleToUnion<R> : never

type GenerateUnion<T extends {name: string, colors: string[]}> =
    `${T['name']}-${TupleToUnion<T['colors']>}`

// 'default-3' | 'default-2' | 'default-1'
type DefaultColor = GenerateUnion<DefaultTheme>

// 'custom-5' | 'custom-4' | 'custom-3' | 'custom-2' | 'custom-1'
type CustomColor = GenerateUnion<CustomTheme>

Playground Link

Answer №3

Behold the mischievous 😂 (with some clarifications in code annotations):

// Select only string representations of positive numbers
type Positive<N> = N extends `${number}` ? N extends "0" ? never : N : never

// Generate a union of possible array indexes (starting from 1)
type IndexFrom1<T extends Array<any>> = keyof { [K in keyof [null, ...T] as Positive<K>]: any }

type WithPrefix<T extends Array<any>, Prefix extends string, Value = IndexFrom1<T>> =
  Value extends string ? `${Prefix}-${Value}` : never

type N_Test = IndexFrom1<[string, string, string]> // "1" | "2" | "3"
type P_Test = WithPrefix<[string, string, string], 'Test'> // "Test-1" | "Test-2" | "Test-3"

Now let's apply the WithPrefix function to your scenarios:

type DefaultTheme = {
  name: 'default';
  colors: [string, string, string];
};

type CustomTheme = {
  name: 'custom';
  colors: [string, string, string, string, string];
};

type DefaultColor = WithPrefix<DefaultTheme["colors"], DefaultTheme["name"]>; // 'default-1' | 'default-2' | 'default-3';

type CustomColor = WithPrefix<CustomTheme["colors"], CustomTheme["name"]>; // 'custom-1' | 'custom-2' | 'custom-3' | 'custom-4' | 'custom-5';

Interactive Workspace


Initially, we create a union of tuple indexes by utilizing mapped typing. We introduce an additional element to it ([null, ...T]) and exclude the zero index (N extends "0") to commence from 1.

Subsequently, we employ a distributive conditional type with template literal type to generate the expected union.

Value extends string ? `${Prefix}-${Value}` : never

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

You are able to use a null type as an index in angular.ts(2538) error message occurred

onClick() { let obj = { fName: "ali", LName: "sarabi", age: "19", } let fieldName = prompt("field"); alert(obj[fieldName]); } I encountered an issue with the code above where alert(obj[fieldName] ...

What is the best method to adjust the width of the PrimeNG ConfirmDialog widget from a logic perspective

Currently utilizing "primeng": "^11.2.0" and implementing the ConfirmDialog code below this.confirm.confirm({ header: 'Announcement', message: this.userCompany.announcement.promptMsg, acceptLabel: this.userCompany.announcement ...

Dynamically load current components in Angular 2's final release

In my quest to dynamically load a component in the upcoming release version 2.0.0, I encountered some challenges. Previously, in RC5, I utilized the following code for loading: I created a directive responsible for loading the controls: import { Check ...

Tips for managing server data and dynamically binding values in Ionic 3

I am struggling with handling data retrieved from the server. I have a provider that fetches the data through HTTP, and I want to ensure the data is loaded before the page loads. However, there is a delay in reflecting the data on the page. Can someone pro ...

`Inconsistencies in console.log output with Angular Firestore``

I'm currently working on retrieving the id of selected data, but when I test it using console.log, it keeps outputting twice. The image below illustrates the console.log output. https://i.stack.imgur.com/IARng.png My goal is to fetch the id once and ...

A guide on renewing authentication tokens in the Nestjs framework

import { ExtractJwt, Strategy } from 'passport-jwt'; import { AuthService } from './auth.service'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common&apo ...

The push action in NavController is not displaying Google Maps as expected

Trying to display a map upon clicking a button is proving challenging for me. It appears that the function NavController.push() does not work as expected, while NavController.setRoot() does. Despite not encountering any errors, I am unable to identify the ...

Converting Data Types in Typescript

So I'm working with Data retrieved from a C# Rest Server. One of the values in the array is of type Date. When I try to perform calculations on it, like this: let difference = date1.getTime() - date2.getTime(); I encounter the following error messag ...

TS2304 error: 'Promise' is nowhere to be found

Hey everyone, I've exhausted all the solutions available on stackoverflow with no luck. So here's my question. tsconfig.json { "version":"2.13.0", "compilerOptions": { "target": "es5", "module": "commonjs", "sourceMap": true, ...

Angular messaging service error TS2769: There is no overload that fits this call

Currently, I am attempting to utilize a messenger service to send products to the cart component. Within the 'Product' class, there are various product attributes stored. Although I managed to successfully log the product to the console in the ca ...

When a function is transferred from a parent component to a child component's Input() property, losing context is a common issue

I have encountered an issue while passing a function from the parent component to the child component's Input() property. The problem arises when the parent's function is called within the child component, causing the this keyword to refer to th ...

Mapping object array values to the same key in Angular is a common task that can

Recently, I encountered an object that looks like this: const product = { name: 'watch', color: ['brown', 'white'] } Here's what I'm aiming for: I want to transform this object into the following format: name: ...

Issues arise when using Android BluetoothLeAdvertiser in Nativescript applications

I've been working on creating a Nativescript application that can send Bluetooth low energy advertisements. Since there are no existing Nativescript plugins for this functionality, I decided to develop a Java library (with plans to add a Swift library ...

Mapping an array of keys to an array of properties using Typescript

Is there a way to achieve the following: type A = { a: string; b: number; c: boolean }; type B = ["b", "a"]; type C = MapProps<A, B> ?? // [number, string] The solution I have currently is: type C = {[key in B[number]]: A[key]} ...

Caution: The `id` property did not match. Server: "fc-dom-171" Client: "fc-dom-2" while utilizing FullCalendar in a Next.js environment

Issue Background In my current project, I am utilizing FullCalendar v5.11.0, NextJS v12.0.7, React v17.0.2, and Typescript v4.3.5. To set up a basic calendar based on the FullCalendar documentation, I created a component called Calendar. Inside this comp ...

JS/Docker - The attribute 'user' is not recognized in the context of 'Session & Partial<SessionData>'

I'm attempting to integrate express-session into my Node.js application running within Docker. I've come across several discussions on the topic: Express Session: Property 'signin' does not exist on type 'Session & Partial<Se ...

Tips for setting up a proxy with an enum

I am facing an issue with setting up a Proxy for an enum. Specifically, I have an enum where I want to assign a value to this.status using a Proxy. However, despite my expectations, the output "I have been set" does not appear in the console. Can anyone ex ...

Error in AWS Cloud Development Kit: Cannot access properties of undefined while trying to read 'Parameters'

I am currently utilizing aws cdk 2.132.1 to implement a basic Lambda application. Within my project, there is one stack named AllStack.ts which acts as the parent stack for all other stacks (DynamoDB, SNS, SQS, StepFunction, etc.), here is an overview: im ...

What could be causing the "serviceName error: No provider found" message to appear?

Currently, I am working on sharing a value between two components in Angular. The setup involves a ProjectView component that renders a ProjectViewBudget component as a "child" (created within a dynamic tab component using ComponentFactoryResolver) with th ...

Enhance your map by incorporating an overlay with ngx-openlayers

Currently, I am trying to implement zoom-in and zoom-out buttons on an OpenLayers map. I attempted to use the overlay method but encountered an error. Here is the code snippet for reference: zoom_button = document.getElementById('zoom') zo ...