检索字符串值以给定字符串开头的对象子集

Retrieve subset of object where string values start with given string

给定一个对象

const MyObject = {
    a: 'a',
    b1: 'b1',
    b2: 'b2',
} as const;

我想为一个只包含以字母 "b" 开头的字符串值的对象创建一个类型。我如何根据字符串值的内容过滤对象类型?

这是我要从 typeof MyObject:

构造的类型
type OnlyBs = {
    b1: "b1";
    b2: "b2";
}

为了获得所需的解决方案,我使用了 template literal typesinfer 关键字:

type OnlyBs = {
    [K in keyof typeof MyObject]: typeof MyObject[K] extends `b${infer Rest}` ? `b${Rest}` : never;
};

然而,这给了我类型

type OnlyBs = {
    readonly a: never;
    readonly b1: "b1";
    readonly b2: "b2";
}

这接近我想要的,但不是我想要的类型。因此,在这里将它与对象 MyObject2 一起使用会给我类型错误:

const MyObject2: OnlyBs = {
    b1: 'b1',
    b2: 'b2',
};

"Property 'a' is missing in type '{ b1: "b1"; b2: "b2"; }' but required in type 'OnlyBs'.(2741)"

问题是 TypeScript 要求我添加 a 属性,尽管它的类型是 never。我想不出另一种方法来实现我的目标。我觉得我很接近,但我无法从生成的对象类型中删除整个 属性(包括它的键)。

顺便说一句: 如果这样做更容易,那么约束对象键(而不是 属性 值)的解决方案也适用于我,因为在这个特定的例子 属性 值等于 属性 键。

TypeScript Playground 我的代码示例

尝试在 OnlyBs 中 return K 而不是 b${Rest}。这样,只有 Bs returns 个允许的密钥。然后使用Pick获得想要的道具。

考虑这个例子:

const MyObject = {
    a: 'a',
    b1: 'b1',
    b2: 'b2',
} as const;

type AllowedKeys<Obj> = {
    [K in keyof Obj]: Obj[K] extends `b${infer _}` ? K : never; // <-- use K instead of `b{infer Rest}`
}[keyof Obj]; // use extra keyof Obj

{
    // "b1" | "b2" <-- allowed keys
    type _ = AllowedKeys<typeof MyObject>

}

type OnlyBs<Obj> = Pick<Obj, AllowedKeys<Obj>>

{
    // readonly b1: "b1";
    // readonly b2: "b2";
    type _ = OnlyBs<typeof MyObject>
}

const MyObject1: OnlyBs<typeof MyObject> = {
    b1: 'b1',
    b2: 'b2',
}; // ok

const MyObject2: OnlyBs<typeof MyObject> = {
    a: null as any, // expected error
    b1: 'b1',
    b2: 'b2',
};

Playground

如果你想在一种实用程序类型中制作它,请考虑:

type AllowedKeys<Obj> = Pick<Obj, {
    [K in keyof Obj]: Obj[K] extends `b${infer _}` ? K : never;
}[keyof Obj]>; 

Why does [keyof Obj] at the end of the mapped type AllowedKeys not also retrieve "a"? What's this concept called? Without [keyof Obj] and a call of keyof later, "a" would be there.

让我们删除 [keyof Obj]:

const MyObject = {
    a: 'a',
    b1: 'b1',
    b2: 'b2',
} as const;

type AllowedKeys<Obj> = {
    [K in keyof Obj]: Obj[K] extends `b${infer _}` ? K : never; // <-- use K instead of `b{infer Rest}`
};

{
    // {
    //     readonly a: never;
    //     readonly b1: "b1";
    //     readonly b2: "b2";
    // }
    type _ = AllowedKeys<typeof MyObject>
}

您可能已经注意到,我们正在获取一个对象,如果值存在,则它是允许的键。如果不允许密钥,则为 never。这就是 anever 的原因。现在。让我们尝试在我们的结果中使用方括号符号。它适用于类型的方式与它适用于普通 js 中的运行时值的方式相同:

{
    // {
    //     readonly a: never;
    //     readonly b1: "b1";
    //     readonly b2: "b2";
    // }
    type a = AllowedKeys<typeof MyObject>['a'] // never
    type b = AllowedKeys<typeof MyObject>['b1'] // b1, because keys and allowed values are the same
    type ab = AllowedKeys<typeof MyObject>['a' | 'b1'] // still "b1" because any union with never does not make any sense
}

如您所见,ab 键入 returns b1 而不是 b1 | never。为什么? 你会找到答案的。因此使用 AllowedKeys<typeof MyObject>[keyof typeof MyObject] 重新调整 b1 | b2 因为 TS 编译器删除了 never.

还有一点,return联合类型不是键的联合,它是对象值的联合。

这里你有一个实用程序类型来获取所有对象值的联合:type Values<T>=T[keyof T]

在 属性 键和 属性 值是相同字符串的情况下约束对象的 (而不是值)的解决方案。

这将仅选择键以 "b" 字母开头的属性(MyObject 如问题中所定义):

type OnlyBKeys = {
    [K in keyof typeof MyObject as K extends `b${infer _}` ? K : never]: typeof MyObject[K];
};

或任意前置字符串的通用变体:

type KeyStartsWith<Object extends object, Prepend extends string> = {
    [K in keyof Object as K extends `${Prepend}${infer _}` ? K : never]: Object[K];
};

这可以用作:

const MyObject3: KeyStartsWith<typeof MyObject, 'b'> = {
    b1: 'b1',
    b2: 'b2',
};

TS Playground of solution