【问题标题】:How to get specific keys from interface using template literal types?如何使用模板文字类型从接口获取特定键?
【发布时间】:2022-10-24 16:49:19
【问题描述】:

我有以下代码为给定的数据库表接收possibleColumns 的字符串数组。然后它使用传入的source 并尝试将其与包含{source}_id 的列匹配。

例如possibleColumns 可以包含 ["id", "name", "first_id", "second_id"] 并且源是 first 然后检查任何名为 first_id 的列并返回它。

逻辑很好,但返回的类型不正确,目前它只返回可能的值作为该类型中的任何列,但我想使用模板文字类型只返回包含<value>_id 的任何值。正如在 cmets 中看到的那样,我正在尝试使用 Extract 类型但没有运气。

/**
 * 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;
    }

电流输出

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

期望的输出

"first_id" | "second_id"

我对打字稿的了解不是很好,所以请忽略任何滥用的术语。

最小的工作示例

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 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", "first_id", "source", "second_id", "third_id"];

const source = "first";

// Returned values here are fine but I want to get the desired output.
const providerIdColumn = getProviderIdColumn<Model>(columnInfo, source);

Playground link

【问题讨论】:

    标签: typescript


    【解决方案1】:

    我们可以通过使用 mapped type 来提取 _id 属性名称来做到这一点:

    /**
     * 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;
    };
    

    然后在函数中使用它:

    /**
     * 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;
    }
    

    结果是在您的示例调用中:

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

    ...providerIdColumn 的类型是 "boardex_id" | "duedil_id" | "zoom_info_id" | undefined

    Playground example

    【讨论】:

    • 非常感谢您回答这个问题,并就制作一个最小的可重现示例给我建议,已经坚持了好几天!
    • 这是一个有趣的!很高兴这有帮助。 :-)
    • @JoelAdams - 你希望电话看起来像什么? const providerIdColumn = getProviderIdColumn&lt;Model&gt;(columnInfo, source, disallow); 之类的东西?
    • 传入的请求看起来像 const providerIdColumn = getProviderIdColumn&lt;T, K extends T = any&gt;(columnInfo, source&gt; const providerIdColumn = `${source}_id` as K != undefined ? Omit&lt;IdKeys&lt;(keyof T)[]&gt;, K&gt; : IdKeys&lt;(keyof T)[]); 调用它像 const providerIdColumn = getProviderIdColumn&lt;Model, "company_id"&gt;(columnInfo, source)
    • @JoelAdams - 该调用没有为函数提供必要的运行时值。如果该函数在运行时没有该值,则该函数无法清除该值。我是不是误会了什么?如果仅在类型级别执行此操作,则可以使用Omit&lt;Model, "duedil_id"&gt;,但您必须为调用和columnInfo (example) 执行此操作。或者,您可以在使用类型保护like this 后缩小类型。
    猜你喜欢
    • 2022-10-24
    • 2021-10-17
    • 1970-01-01
    • 2016-01-28
    • 2020-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-13
    相关资源
    最近更新 更多