Creating a union type from an array that is not a literal (using `Object.keys` and `Array.map`)

Can a union be derived from a non-literal array?

I attempted the following:

const tokens = {
  "--prefix-apple": "apple",
  "--prefix-orange": "orange"
};

const tokenNames = Object.keys(tokens).map(token => token.replace("--prefix-", ""));

type TokenNamesWithoutPrefix = typeof tokenNames[number] 

Nevertheless, it outputs type string instead of what I want

"apple" | "orange"

Answer №1

To perform this operation successfully, it is necessary to implement it at the type level since the existing built-in functions only return string types. Therefore, generic types must be used to handle string literal types.

Let's begin with:

type StripPrefix<T extends string, Prefix extends string> =
  T extends `${Prefix}${infer Result}`
    ? Result
    : T

type Test = StripPrefix<'--prefix-foo-bar', '--prefix-'>
//   ^? 'foo-bar'

This type verifies if a string literal type begins with a specified prefix and then deduces the following string literal type.

If no match is found, the type remains as T, unaffected.


Now, we require a function to carry out this operation and enforce these types.

function removePrefix<
  T extends string,
  Prefix extends string
>(token: T, prefix: Prefix): StripPrefix<T, Prefix> {
  return token.replace(prefix, "") as StripPrefix<T, Prefix>
}

In this function, the starting token is indicated by type T, while the prefix to remove is represented by type Prefix.

A type assertion with as is needed on the return value here. This adjustment is essential because the replace method on strings always results in the type string. By knowing the input types, enforcement using our new StripPrefix type can be ensured.


Another issue arises when Object.keys consistently provides string[] and not the specific keys. This discrepancy occurs due to potential additional keys within the actual object beyond the type's knowledge.

const obj1 = { a: 123, b: 456 }
const obj2: { a: number } = obj1 // fine
const keys = Object.keys(obj2) // ['a', 'b']

In this code snippet, although obj2 contains two keys, the type information identifies just one key.

If certainty exists that such a scenario won't transpire or won't impact the outcome significantly, an extra type assertion can accurately dictate the array of keys:

const myObjectKeys = Object.keys(tokens) as (keyof typeof tokens)[]

Subsequently, applying the preceding function to the mapped values should deliver the anticipated result.

const tokenNames = tokenKeys.map(token => removePrefix(token, "--prefix-"));
//    ^? ('apple' | 'orange')[]

View Typescript playground

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

Replace a portion of text with a RxJS countdown timer

I am currently working on integrating a countdown timer using rxjs in my angular 12 project. Here is what I have in my typescript file: let timeLeft$ = interval(1000).pipe( map(x => this.calcTimeDiff(orderCutOffTime)), shareReplay(1) ); The calcTim ...

Storing data from a collection of interface objects in a string array

Take a look at the following code snippet: import React, {FC} from 'react'; import {useFetchErrors} from "../Api/Api"; import {useLocation} from "react-router-dom"; interface ExecutionTableProps { project_id: number } const ...

Using href with IconButtonProps is not supported

I'm facing a challenge in creating a wrapper for the IconButton. I aim to pass components or href props, but unfortunately, I am unable to achieve this by passing the IconButtonProps. Is there a way to accomplish this? function CustomIconButton(props ...

"Utilizing TypeScript with React: Creating a window onClick event type

My linter is not happy with the any type for window.onClick. What should be the correct type? import React, { useContext, useState } from 'react'; import { Link } from 'react-router-dom'; import { Global } from '../globalState&apo ...

Access the $event object from an Angular template selector

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script> <input type="file" #myFile multiple /> <button (click)="onDelete(myFile.event)">DeleteFiles</button> My code snippet is experienci ...

Is ngForIn a valid directive in Angular 4?

While attempting to loop over the properties of an object using *ngFor with in, I encountered a challenge. Here is a sample code snippet: @Controller({ selector: 'sample-controller', template: ` <ul> <li *ngFor="let i in o ...

The technique for concealing particular div elements is contingent upon the specific values within an array

My TypeScript code is returning an array in this format: allFlowerTypes (3) ['Rose', 'Bluebell' , 'Daisy'] I want to dynamically show or hide the following HTML content based on the array values above: <ul> <li> ...

Using Angular 4 to delete selected rows based on user input in typescript

I am facing a challenge with a table that contains rows and checkboxes. There is one main checkbox in the header along with multiple checkboxes for each row. I am now searching for a function that can delete rows from the table when a delete button is clic ...

Developing a dynamic modal using Angular and embedding Google Maps within an iframe

I'm currently working on implementing a modal in my Angular application that, when opened, displays Google Maps within an iframe. The problem I'm facing is that the iframe isn't loading and I'm receiving this error in the browser conso ...

The 'required' validator in Mongoose seems to be malfunctioning

I've been attempting to validate the request body against a Mongoose model that has 'required' validators, but I haven't been successful in achieving the desired outcome so far. My setup involves using Next.js API routes connected to Mo ...

What is the best approach for managing and obtaining accurate JSON responses when working with PHP API and AngularJS 2 services?

Encountering a backend issue with MySQL, wherein one query is producing a specific dataset: {"candidat":[{"ID":1,"nom":"Danny","prenom":"Hariot","parti":"Quamba","departement":"Ukraine","commune":"Chapayeve"},{"ID":2,"nom":"Shari","prenom":"Adamkiewicz"," ...

The 'current' in react typescript is not found within the type 'never'

Currently, I am working with react and typescript in my project. To fetch the height of a specific div tag, I decided to utilize useRef method. However, when trying to access 'current' property, TypeScript throws an error. Property 'current& ...

Encountering a problem when utilizing window.ethereum in Next Js paired with ether JS

Experiencing some difficulties while utilizing the window.ethereum in the latest version of NextJs. Everything was functioning smoothly with NextJs 12, but after upgrading to NextJs 13, this error started popping up. Are there any alternative solutions ava ...

Creating a shared function using TypeScript

Having a vue3 component that displays a list of items and includes a function to delete an item raises a concern about checking parameters and specifying the array for the filter operation. The goal is to create a universal function using typescript. <t ...

Troubles with Jest tests are encountered when using ts-jest in an ES2020/ESNEXT TypeScript project

Currently, I am working on a VueJS project that utilizes ViteJS for transpilation, which is functioning properly. However, when Jest testing is involved alongside ts-jest, the following Jest configuration is used: jest.config.ts import { resolve } from &q ...

Angular 2 has its own version of $q.when called RxJs

Back in the AngularJS 1.* days, I used to have this code snippet to refresh the auth-token: ... if (!refreshTokenInProgress) { refreshTokenInProgress = AuthService.refreshToken(); } $q.when(refreshTokenInProgress, function () { refreshTokenInProgre ...

Changing the font family for a single element in Next.js

One unique aspect of my project is its global font, however there is one element that randomly pulls font families from a hosted URL. For example: https://*****.com/file/fonts/Parnian.ttf My page operates as a client-side rendered application (CSR). So, ...

Event triggered by an Angular counter

Typescript: countdown; counter = 10; tick = 1000; this.countdown = Observable.timer(0, this.tick) .take(this.counter) .map(() => --this.counter) Also in HTML: <div> <h1>Time Remaining</h1> <h2>{{countdow ...

The Cypress-TinyMCE package consistently returns undefined for the editor instance when using TypeScript

My current project involves building a React JS application with TypeScript, where I utilize the TinyMCE editor within a form. To further enhance my development process, I am incorporating integration tests using Cypress in TypeScript. However, I have enco ...

Monitor database changes using TypeORM

Within my database, there is a table named Songs. One of my applications is responsible for adding new songs to this table. I also have a second application that serves as an API for the database and utilizes typeorm. I am curious if there is a ...