给定数据库密钥成员,从实体中提取数据库密钥类型

Extract the db key type from an entity, given the db key members

给定一个实体:

const someEntity = {
  id: 'someId',
  createdAt: 123234223453,
  name: 'Dudemanson',
}

它是 db 关键成员:

const keyMembers = {
  hash: 'id',
  range: 'createdAt',
}

我想提取数据库密钥:

const extractedDbKey = {
  id: 'someId',
  createdAt: 123234223453,
}

这是我失败的尝试:

type KeyFields<Entity extends object> = { hash: keyof Entity, range?: keyof Entity };

function toKey<Entity extends object, TheKeyFields extends KeyFields<Entity>>
(entity: Entity, keyFields: TheKeyFields): Pick<Entity, TheKeyFields[keyof TheKeyFields]> {
  const ret = {} as Pick<Entity, TheKeyFields[keyof TheKeyFields]>;
  (Object.keys(keyFields) as Array<keyof TheKeyFields>)
    .map((k) => keyFields[k]).forEach(k => ret[k] = entity[k]);
  return ret;
}

请指点一下?

Here's a playground

如果您只对根据输入类型计算输出类型感兴趣,您将最终使用 Pick。首先让我们定义输入类型:

const someEntity = {
    id: 'someId',
    createdAt: 123234223453,
    name: 'Dudemanson',
}
type SomeEntityType = typeof someEntity;

const keyMembers = {
    hash: 'id',
    range: 'createdAt',
} as const; // <-- need that, or values will just be string
type KeyMembersType = typeof keyMembers;

请注意,您需要类似 const assertion to have the compiler remember that the property values of keyMembers are of string literal types 的东西,例如 "id""createdAt",而不是无用的 string

并且输出类型最终在对象类型 SomeEntityType 上使用 Pick,键来自 属性 values 24=],像这样:

type ExtractedDbKeyType =
    Pick<SomeEntityType, KeyMembersType[keyof KeyMembersType]>;
/* type ExtractedDbKeyType = {
    id: string;
    createdAt: number;
}*/

如果你想写一个像这样工作的函数签名,你可以这样做:

function extractKeys<T extends object, K extends Record<keyof K, keyof T>>(
    entity: T,
    keys: K
) {
    const ret = {} as Pick<T, K[keyof K]>;
    (Object.keys(keys) as Array<keyof K>).
        map((k) => keys[k]).forEach(k => ret[k] = entity[k]);

    // Without the following assertion you get an output type like 
    // 'Pick<{...},"..."|"..."">' instead of a more straightforward object type.
    return ret as any as (typeof ret extends infer O ? { [P in keyof O]: O[P] } : never);
}

实施只是一个例子;重要的是它采用 TK 类型的值(其中 K 受到限制,因此其值来自 keyof T)和 return 的值输入 Pick<T, K[keyof K]>。 return 类型有点问题;如果你只是 return Pick<T, K[keyof K]>,它往往会显示为 Pick<BlahBlah, "x"|"y"|"z">(至少在我的 IDE 中是这样)。所以最后我使用映射条件类型将对象类型扩展为更直接的对象类型。像这样:

const extractedDbKey = extractKeys(someEntity, keyMembers);

/* TYPE WITH ASSERTION:
const extractedDbKey: {
    id: string;
    createdAt: number;
}  */

/* TYPE WITHOUT ASSERTION:
const extractedDbKey: Pick<{
    id: string;
    createdAt: number;
    name: string;
}, "id" | "createdAt"> */

可以看出前者更好看。不过这是否重要取决于您。

哦,至少在你的例子中实现是合理的:

console.log(extractedDbKey) // {id: "someId", createdAt: 123234223453}

好的,希望对您有所帮助;祝你好运!

Playground link to code


编辑:如果您特别想强制执行 keyMembers 具有属性 hashrange,您可以修改 extractKeys():

function extractKeys<T extends object, K extends keyof T>(
    entity: T,
    keys: { hash: K, range: K }
) {
    const ret = {
        [keys.hash]: entity[keys.hash],
        [keys.range]: entity[keys.range]
    } as Pick<T, K>;
    return ret as any as (typeof ret extends infer O ? { [P in keyof O]: O[P] } : never);
}

Updated Playground link

干杯!