I have the following code that takes in a string array of possibleColumns
for a given database table. It then uses the incoming source
and tries to match it to a column that contain {source}_id
.
e.g. the possibleColumns
could contain ["id", "name", "duedil_id", "salesforce_id"]
and the source is duedil
it then checks for any columns called duedil_id
and returns it.
The logic is fine but the returned types are not correct, currently it just returns the possible value as any of the columns in that type but I want to use template literal types to just return any values that contain <value>_id
. As seen in the comments I'm trying to use the Extract
type but no luck.
/**
* Takes a list of columns for a specific table and a source and checks for any columns that
* match `${source}_id`.
* @template T The desired output type
* @param possibleColumns string array of columns of type T
* @param source The source to convert in question
* @returns a key of one of the columns of T or undefined if not matched
*/
export function getProviderIdColumn<T>(possibleColumns: (keyof T)[], source: string) {
// Attempt to extract only keys that contain _id at the end
// 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" | "duedil_id" | "salesforce_id"
Desired output
"duedil_id" | "salesforce_id"
My knowledge of typescript isn't great so please disregard any misused terminology.
Minimal working example
export interface Model {
id: number,
name: string | null,
boardex_id: string | null,
source: string,
duedil_id: string | null,
zoom_info_id: string | null,
}
/**
* Takes a list of columns for a specific table and a source and checks for any columns that
* match `${source}_id`.
* @template T The desired output type
* @param possibleColumns string array of columns of type T
* @param source The source to convert in question
* @returns a key of one of the columns of T or undefined if not matched
*/
export function getProviderIdColumn<T>(possibleColumns: (keyof T)[], source: string) {
// Attempt to extract only keys that contain _id at the end
// 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", "boardex_id", "source", "duedil_id", "zoom_info_id"];
const source = "boardex";
// Returned values here are fine but I want to get the desired output.
const providerIdColumn = getProviderIdColumn<Model>(columnInfo, source);
CodePudding user response:
We can do that by using this mapped type to extract the _id
property names:
/**
* Gets a union of the literal types in `T` that end with `_id`.
*/
type IdKeys<T extends any[]> = keyof {
[Key in T[number] as Key extends `${string}_id` ? Key : never]: null;
};
and then use that in the function:
/**
* Takes a list of columns for a specific table and a source and checks for any columns that
* match `${source}_id`.
* @template ObjectType The desired output type
* @param possibleColumns string array of columns of type T
* @param source The source to convert in question
* @returns a key of one of the columns of T or undefined if not matched
*/
export function getProviderIdColumn<ObjectType>(
possibleColumns: (keyof ObjectType)[],
source: string
): IdKeys<(keyof ObjectType)[]> | undefined {
// Sadly, we have to have a type assertion, since `source` is type `string`
const providerIdColumn = `${source}_id` as IdKeys<(keyof ObjectType)[]>;
if (possibleColumns.includes(providerIdColumn)) {
return providerIdColumn;
}
return undefined;
}
The result is that in your sample call:
const providerIdColumn = getProviderIdColumn<Model>(columnInfo, source);
...providerIdColumn
's type is "boardex_id" | "duedil_id" | "zoom_info_id" | undefined
.