Tips for extracting certain keys from an interface using template string types?

I am dealing with a code snippet here that accepts a string array named possibleColumns for a specific database table. It then takes the incoming source and attempts to find a column containing {source}_id.

For example, if the possibleColumns includes

["id", "name", "first_id", "second_id"]
and the source is first, it looks for any columns named first_id and returns them.

The logic itself works correctly but the types returned are not accurate. Currently, it just returns any of the columns in that type as possible values. I aim to enhance this by using template literal types to specifically return values that contain <value>_id. Despite trying to utilize the Extract type as indicated in the comments, I have been unsuccessful so far.

/**
 * Takes a list of columns for a specific table and a source, and checks for any columns matching `${source}_id`.
 * @template T The desired output type
 * @param possibleColumns string array of columns of type T
 * @param source The source being queried
 * @returns a key of one of the columns of T or undefined if no match is found
 */
export function getProviderIdColumn<T>(possibleColumns: (keyof T)[], source: string) {
    // Attempting to extract only keys that end with _id
    // as Extract<keyof T extends `${infer _}_id` ? keyof T : never, T>
    const providerIdColumn = `${source}_id` as keyof T;
    if (possibleColumns.includes(providerIdColumn)) {
        return providerIdColumn;
    }

Current output

"id" | "name" | "first_id" | "second_id"

Desired output

"first_id" | "second_id"

I do acknowledge my limited knowledge of TypeScript terminology, so please overlook any inaccuracies in my explanation.

Minimal working example

export interface Model {
    id: number,
    name: string | null,
    third_id: string | null,
    source: string,
    first_id: string | null,
    second_id: string | null,
}

/**
 * Takes a list of columns for a specific table and a source, and checks for any columns matching `${source}_id`.
 * @template T The desired output type
 * @param possibleColumns string array of columns of type T
 * @param source The source being queried
 * @returns a key of one of the columns of T or undefined if no match is found
 */
export function getProviderIdColumn<T>(possibleColumns: (keyof T)[], source: string) {
    // Attempting to extract only keys that end with _id
    // as Extract<keyof T extends `${infer _}_id` ? keyof T : never, T>
    const providerIdColumn = `${source}_id` as keyof T;
    if (possibleColumns.includes(providerIdColumn)) {
        return providerIdColumn;
    }
    return undefined;

}

// A function here returns a string array of the keys in Model.
const columnInfo: (keyof Model)[] = ["id", "name", "first_id", "source", "second_id", "third_id"];

const source = "first";

// Values returned here are correct, but I aim to achieve the desired output.
const providerIdColumn = getProviderIdColumn<Model>(columnInfo, source);

Playground link

Answer №1

To achieve this, we can utilize a mapped type that helps in extracting the property names with the _id suffix:

/**
 * Obtain a union of literal types in `T` ending with `_id`.
 */
type IdKeys<T extends any[]> = keyof {
    [Key in T[number] as Key extends `${string}_id` ? Key : never]: null;
};

This mapped type can then be applied within the function logic:

/**
 * Retrieves columns from a specific table and source to identify matches for `${source}_id`.
 * @template ObjectType Desired output type
 * @param possibleColumns Array of columns of type T
 * @param source The conversion source
 * @returns A key from columns or undefined if no match is found
 */
export function getProviderIdColumn<ObjectType>(
    possibleColumns: (keyof ObjectType)[],
    source: string
): IdKeys<(keyof ObjectType)[]> | undefined {
    // Type assertion needed for `source`, which is of type `string`
    const providerIdColumn = `${source}_id` as IdKeys<(keyof ObjectType)[]>;
    if (possibleColumns.includes(providerIdColumn)) {
        return providerIdColumn;
    }
    return undefined;
}

Resulting in the following upon invocation:

const providerIdColumn = getProviderIdColumn<Model>(columnInfo, source);

The type of providerIdColumn becomes

"first_id" | "second_id" | "third_id" | undefined
.

Check out Playground example

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

Testing Angular applications using Karma

After utilizing the yo angular 1 generator, it generated a landing page and some tests. However, I am facing an issue when trying to compile the code in VS as I receive an error stating that "module('fountainFooter');" is undefined. /// <refe ...

Testing the throwing of errors when running Karma by utilizing sinon.spy on global functions like parseInt

I'm currently facing an issue with monitoring the usage of parseInt within my function. This is a Proof of Concept for integrating TypeScript into our company's workflow. I've tried following two different tutorials on testing Sinon, but no ...

Updating the status of the checkbox on a specific row using Typescript in AngularJS

My goal is to toggle the checkbox between checked and unchecked when clicking on any part of the row. Additionally, I want to change the color of the selected rows. Below is my current implementation: player.component.html: <!-- Displaying players in ...

Implementing the react-i18next withNamespaces feature in Ant Design forms

I'm a newcomer to i18next and TypeScript, and I'm trying to translate an antd form using withNamespaces. export default withNamespaces()(Form.create()(MyComponent)); Encountering the following error: Argument of type 'ComponentClass< ...

navigating through different pages on a react-native application

While building my app in react-native, I encountered some issues with setting up the navigation. I referred to the react-native documentation for guidance, specifically this navigation guide. The guide mentioned passing an object named "navigation" to your ...

Are you delving into the realm of reduce functions in order to grasp the intric

Currently following this particular tutorial where they utilize the reduce method to transform an Array<Student> into a { [key: string]: Array<string | number> }. The tutorial includes this expression that caught my attention. It's quite n ...

Is searching for duplicate entries in an array using a specific key?

Below is an array structure: [ { "Date": "2020-07", "data": [ { "id": "35ebd073-600c-4be4-a750-41c4be5ed24a", "Date": "2020-07-03T00:00:00.000Z", ...

The firebase-admin module encounters compatibility issues with middleware due to the OS Module

I'm facing a major issue with my API implementation. I am working on integrating an authentication and verification middleware, but the problem arises when using firebase-admin due to its dependencies on Edge Runtime modules that are incompatible with ...

What is the reason for IE displaying null when the model does not exist?

Why does IE 11 render 'null' if my model does not exist? For instance: <tr> <td [innerHTML]="model?.prop1 | my-pipe"></td> </tr> Imagine this scenario: When the page loads, a request is sent to the server and the res ...

Using regular expressions, replace all instances of " " with ' ' based on certain conditions

I am looking to replace quotes "" with single quotes '' within a string. string = `bike "car" bus "'airplane'" "bike" "'train'"` If a word is inside double quotes, it shoul ...

Prisma unexpectedly updates the main SQL Server database instead of the specified database in the connection string

I have recently transitioned from using SQLite to SQL Server in the t3 stack with Prisma. Despite having my models defined and setting up the database connection string, I am encountering an issue when trying to run migrations. Upon running the commands: ...

Field that only permits numerical input without triggering events for other characters

I've encountered some issues with the default behavior of the HTML number input and I'm looking to create a simple input that only allows numbers. To address this, I have developed a directive as shown below: import { Directive, ElementRef, Hos ...

The TSX file is encountering difficulty rendering an imported React Component

Upon attempting to import the Day component into the Week component (both .tsx files), an error is thrown: Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. ...

Unable to loop through the Array

let Users = [ { name: 'John', id: '1', jp: 'USA' }, { name: 'Jane', id: '2', jp: 'Japan' }, ]; export function DisplayUsers(usersList) { return ( <div> {usersList?.map((user ...

The issue of session type not updating in Next.js 14 with Next-auth 5 (or possibly version 4) is a common concern that needs to

Experimenting with new tools, I encountered an issue when trying to utilize the auth() function to access user data stored within it. TypeScript is indicating that the user does not exist in Session even though I have declared it. Here is my auth.ts file: ...

Having trouble making generics work with extends in Typescript

I am facing an issue when trying to limit input to an object, but unfortunately, it is not working correctly: displayModal<T extends {[key: string]: any}, U>(component: Type<AbstractDialogComponent<T, U>>, options?: ModalDialogOption ...

Compilation issues in node-modules arise following the Vue package and i18next updates

Recently, I decided to upgrade from the i18n package to the newer version called i18next in my project. However, this update led to numerous errors popping up during compilation. Fortunately, by adding 'skipLibCheck' to the compiler options in th ...

Having trouble deleting JavaScript object properties within a loop?

Struggling to comprehend the behavior of this particular piece of javascript code. const devices = searchResult.results.forEach(device => { const temp = Object.keys(device.fields); for(var property in temp) { if(device.fields.hasOwnPro ...

Connecting RxJS Observables with HTTP requests in Angular 2 using TypeScript

Currently on the journey of teaching myself Angular2 and TypeScript after enjoying 4 years of working with AngularJS 1.*. It's been challenging, but I know that breakthrough moment is just around the corner. In my practice app, I've created a ser ...

Creating a HTML element that functions as a text input using a div

I am encountering an issue with using a div as text input where the cursor flashes at the beginning of the string on a second attempt to edit the field. However, during the initial attempt, it does allow me to type from left to right. Another problem I am ...