如何在TypeScript中强制字典与字符串数组的键严格匹配?
实现字典键与数组元素的完全匹配约束
需求说明
需要实现一个函数f,接收Record<string, string>类型的字典和字符串数组,要求:
- 数组中的值必须是字典的键
- 二者完全一致,即以下两种情况在编译时必须报错:
- 字典存在数组未包含的额外键
- 数组存在字典未定义的额外元素
合法调用示例:
f({ a: "foo", b: "bar" }, ["a", "b"]);
需要编译报错的调用:
// 字典存在额外键c f({ a: "foo", b: "bar", c: "baz" }, ["a", "b"]); // 数组存在额外元素c f({ a: "foo", b: "bar" }, ["a", "b", "c"]);
已有尝试
校验数组额外元素的方案
该方案通过泛型约束数组元素必须是字典键的子集,能拦截数组多元素的情况:
function f< D extends Record<string, string>, A extends Array<keyof D> >(d: D, a: A): void { console.log(d, a); } // 数组中的"c"不属于字典键,编译报错 f({ a: "foo", b: "bar" }, ["a", "b", "c"]);
校验字典额外键的方案
通过UnionToIntersection类型将数组元素转为字典的精确键类型,拦截字典多键的情况(注意数组需要用as const断言):
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; function f<A extends readonly string[]>( d: UnionToIntersection<{ [K in A[number]]: string }>, a: A ): void { console.log(d, a); } // 字典中的`c: "baz"`不在数组中,编译报错 f({ a: "foo", b: "bar", c: "baz" }, ["a", "b"] as const);
合并后的完整解决方案
将两种约束结合,同时校验数组元素不能超出字典键,且字典键不能超出数组元素:
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; function f< K extends string, A extends readonly K[], D extends Record<K, string> >( d: D & UnionToIntersection<{ [Key in A[number]]: string }>, a: A ): void { console.log(d, a); } // 合法调用,无报错 f({ a: "foo", b: "bar" }, ["a", "b"]); // 情况1:字典存在额外键c,编译报错 f({ a: "foo", b: "bar", c: "baz" }, ["a", "b"]); // 情况2:数组存在额外元素c,编译报错 f({ a: "foo", b: "bar" }, ["a", "b", "c"]);
方案说明
- 泛型
A extends readonly K[]结合D extends Record<K, string>,确保数组元素只能是字典D的键,拦截数组多元素的情况。 - 参数
d的类型D & UnionToIntersection<{ [Key in A[number]]: string }>通过交叉类型实现双向约束:UnionToIntersection<{ [Key in A[number]]: string }>生成仅包含数组元素作为键的精确字典类型- 与
D交叉后,D不能包含该精确类型之外的键,从而拦截字典多键的情况
- 无需手动添加
as const断言,TypeScript会自动推导数组的精确字面量类型。
内容的提问来源于stack exchange,提问作者damix911
相关产品推荐
相关产品推荐

