The union is disrupting the process of extracting values from a string record

Suppose you have a type Type that takes a record containing tuples with a union of strings and one string from that union (consider it as options and a default), and also takes a union of string builds where each string follows the pattern

${key of first type}:${one of the value in the first type union}

type BuildPart<T extends Record<string, [string, string]>> = '' | {
    [K in keyof T]: K extends string ? `${K & string}:${T[K][number]}` : never
}[keyof T]

type Type<All extends Record<string, [string, string]>, Part extends BuildPart<All> = ''> = {
    [K in keyof All]: K extends string ? Part extends `${K}:${infer V}` ? V : All[K][1] : never
}[keyof All]

The main objective is to extract values chosen, which can be either the default set originally or any alternative provided by Part.

type test0 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}>                   // 'c' | 'd'
type test1 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}, 'a:b'>            // 'b' | 'd'
type test2 = Type<{a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd']}, 'a:b' | 'b:e'>    // 'b' | 'c' | 'd' | 'e' but expected 'b' | 'e'

It functions correctly when Part is empty or contains only one value. However, if Part is a union, then all possible values are displayed.

Check out the playground here

Answer №1

The issue lies in the fact that

Part extends `${K}:${infer V}` ? V : All[K][1]

represents a concept known as a distributive conditional type, which individually processes all union members of Part. If any member of the Part union fails to match K, the default value for K (All[K][1]) is returned. For instance, if K is "a" and Part is

"a:b" | "b:e"
, it first evaluates
"a:b" extends `a:${infer V}` ? V : "c"
, resulting in "b". Subsequently, it evaluates
"b:e" extends `a:${infer V}` ? V : "c"
, leading to "c". This produces "b" | "c", an undesired outcome.


We cannot permit All[K][1] in the false branch. It should instead be never so that the final result is simply "b". The selection of "c" only occurs when the entire conditional type amounts to never. To address this, I propose creating a separate utility type:

type GetSuffix<K extends string, P extends string> =
    P extends `${K}:${infer V}` ? V : never;

For every K, we require GetSuffix<K, P> across the whole union P, unless it resolves to

never</code; in such case, <code>All[K][1]
should be used. We can further abstract the logic of "use this unless it's never, otherwise use that" into another utility type:

type OrDefault<T, D> = [T] extends [never] ? D : T;

(and we definitely want to avoid it being interpreted as a distributive type). This leads us to the refined version of Type:

type Type<O extends Record<string, [string, string]>, P extends BuildPart<O> = never> = {
    [K in string & keyof O]: OrDefault<GetSuffix<K, P>, O[K][1]>
}[string & keyof O]

This revision closely resembles your original implementation (but with adjustments regarding the check on whether K is a string). Let's put it to the test:

type test0 = Type<{ a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd'] }>                   
//       ^? type test0 = 'c' | 'd'
type test1 = Type<{ a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd'] }, 'a:b'>            
//       ^? type test1 = 'b' | 'd'
type test2 = Type<{ a: ['b' | 'c', 'c'], b: ['d' | 'e', 'd'] }, 'a:b' | 'b:e'>    
//       ^? type test2 = 'b' | 'e'

Everything looks solid.

Access the Playground to View Code

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

The child component is receiving undefined props, yet the console.log is displaying the actual values of the props

Struggling to implement a Carousel in my Gatsby-based app, I encountered an issue with passing props from the Parent to Child functional component. The error message "TypeError: props.slide is undefined" was displayed, but upon checking the console logs, i ...

Should I use Object.assign or define class properties?

Currently in the process of developing an angular application that interacts with the twitch API. The API returns data in various formats, some of which I need to parse and save into specific classes. My main concern is understanding the potential drawbac ...

Applying style to HTML elements using Typescript

My issue involves working with a combination of HTML and TypeScript. <ion-card class="ionCard" *ngFor="let item of libraries"> <ion-card-header> {{getLibraryName(item.locationName)}} </ion-card-header> ...

Creating a customized HTTP class for Bootstrap in Angular 2 RC 5

During my experience with Angular 2 RC 4, I encountered a situation where I needed to create a class called HttpLoading that extended the original Http class of Angular2. I managed to integrate this successfully into my project using the following bootstr ...

Switch the visibility of all local variables in *ngFor from an external loop

Imagine having an *ngFor loop where a local variable is defined like this: <div *ngFor="let item of items"> <p>{{item.name}}</p> <div *ngIf="item.colorVisible">{{item.color}}</div> <button (click)="item.colorVisible ...

Tips for simulating a getter parameter value from a fake service

Despite referring to this Stack Overflow post, it doesn't seem to be relevant to my situation. In my scenario, I have a service named AnimationService, which relies on another service called AnimationStateService. The AnimationStateService contains a ...

Azure functions encountered an issue: TypeError - it seems that the connection.query function is not recognized

Having a slight issue with my Azure function that is supposed to retrieve all data from a SQL table. The connection to the database is successful, but whenever I attempt to run the get request, it results in an error Exception: TypeError: connection.query ...

Creating a setup with Angular 2+ coupled with Webpack and a Nodejs

I have successfully configured Angular2+webpack along with NodeJs for the backend. Here is a basic setup overview: webpack.config.js: var webpack = require('webpack') var HtmlWebpackPlugin = require('html-webpack-plugin') var Extract ...

How to Implement the Play/Pause Button in an Angular Application

I'm working on adding a show/hide feature to the play and pause buttons for a list of tracks in Angular 7. I had some success with Angular animation initially, but encountered an issue where all buttons in my list would change state instead of just on ...

Is there a way for me to navigate from one child view to another by clicking on [routerLink]?

Currently, I am facing an issue with switching views on my Angular website. The problem arises when I attempt to navigate from one child view to another within the application. Clicking on a button with the routerlink successfully takes me to the new view, ...

Angular error: "name not found", element is not present in the DOM yet

In my current Angular5 project, I am implementing a small chat system. One issue I encountered is that when a user sends a message, a LI element is dynamically created: chat-component.html <li #ChatListItem *ngFor="let message of messages" class="list ...

Creating an Angular material modal that uses a component wrapper and takes a component as a parameter

Currently, I am in the process of developing a dialog service that will showcase a wrapper component whose parameter is a component to be displayed as the content of the wrapper. open(component: any | TemplateRef<any>, params, viewMode: ViewMode = V ...

Invoke a function of a child component that resides within the <ng-content> tag of its parent component

Check out the Plunkr to see what I'm working on. I have a dynamic tab control where each tab contains a component that extends from a 'Delay-load' component. The goal is for the user to click on a tab and then trigger the 'loadData&apo ...

The module named "jquery" has not been loaded in this context: _. Please use require() to load it

As I work on migrating my Javascript files to Typescript, I encountered an issue when trying to use the transpiled javascript file in an HTML page. The error message I received is as follows: https://requirejs.org/docs/errors.html#notloaded at makeError (r ...

Experiencing an issue with type error when transitioning syntax from using require to import

After transitioning from require in CommonJS syntax to import in ES Module syntax, I encountered an error. I decided to develop a todo-app using Node.js, TypeScript, and MySQL. To start off, I wrote the initial code snippets below: // db.ts export {}; co ...

The autoimport feature in VScode should consistently use absolute paths, unless the file being imported is located in the same

Objective: I want VScode to automatically import my dependencies using absolute paths, unless the file is located in the same directory. For example: Let's say we have ComponentA and ComponentB in the same directory, but a service in a different dire ...

Angular Mat AutoSuggest - Improving User Experience

I am encountering an issue where I can retrieve the list, but as soon as I input any of my data, I receive an error ERROR TypeError: Cannot read properties of null (reading 'valueChanges'). How can I resolve this? I have verified the name of my f ...

Removing the mousedown event from elements within a child component: tips and tricks

Two components are involved: DashboardView and DashboardOrderCard. My goal is to prevent the mousedown event from being emitted when either the date picker is clicked or an option is selected from the DashboardOrderCard. How can I accomplish this? Below is ...

Transferring data from the server to the client side in Next JS with the help of getInitialProps

I am currently developing an application using nextJS. Within server/index.ts, I have the following code: expressApp.get('/', (req: express.Request, res: express.Response) => { const parsedUrl = parse(req.url, true); const { query } = ...

Error message while attempting to update devextreme-datagrid: "Received HTTP failure response for an unknown URL: 0 Unknown Error"

Need help with updating the devextreme-datagrid. Can anyone assist? lineController.js router.put("/:id", (req, res) => { if (!ObjectId.isValid(req.params.id)) return res.status(400).send(`No record with given id : ${req.params.id}`); ...