如何用TypeScript类型谓词将任意值对象转为字符串值对象?
问题描述
我有一个对象:
let obj1: A; /* type A = { property1: any; property2: any; } */
我明确知道这个对象的所有值都是字符串,但不想用强制类型转换的方式:
// 我不想这么做 const obj2 = obj1 as Record<keyof typeof obj1, string>
于是尝试用TypeScript的类型谓词来实现类型推断,写了下面的函数:
function getIsCorrectType<T extends Record<string, any>>( obj: T ): obj is Record<keyof T, string>{ return true; // 假设我已手动检查每个值都是字符串 }
但运行时触发了错误:
A type predicate's type must be assignable to its parameter's type. Type 'Record<keyof T, string>' is not assignable to type 'T'. 'Record<keyof T, string>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Record<string, any>'. Type 'string' is not assignable to type 'T[P]'.
我觉得这不合理,string明明可以赋值给any类型的T[P],请问问题出在哪?有没有其他解决方案?
问题根源
TypeScript对类型谓词有硬性要求:断言的类型必须是参数类型的子类型。虽然你给T加了Record<string, any>的约束,但T可以是这个约束的任意子类型,比如:
type FixedType = { property1: "hello"; property2: number };
如果T被实例化为FixedType,那么Record<keyof T, string>会变成{ property1: string; property2: string },但FixedType的property2是number,string显然无法赋值给number——TypeScript要考虑所有可能的T子类型,而不只是你当前用到的A类型,所以才会报错。
替代解决方案
方案1:调整泛型约束与断言类型
修改泛型约束,让T的属性类型是string的父类型(比如unknown),同时用交叉类型确保断言类型是T的子类型:
function getIsCorrectType<T extends Record<string, unknown>>( obj: T ): obj is T & Record<keyof T, string> { // 实际检查逻辑:遍历所有属性确认是字符串 return Object.values(obj).every(val => typeof val === 'string'); } // 使用示例 if (getIsCorrectType(obj1)) { // 这里obj1类型被收缩为{ property1: string; property2: string } console.log(obj1.property1.toUpperCase()); // 类型安全 }
T & Record<keyof T, string>的意思是“T的所有属性都必须是字符串”,既满足了类型谓词的子类型要求,又能正确收缩类型。
方案2:针对特定类型写谓词
如果你的场景只需要处理A类型的对象,可以直接写更具体的类型谓词,避免泛型带来的复杂问题:
function isAWithStrings(obj: A): obj is { property1: string; property2: string } { return Object.values(obj).every(val => typeof val === 'string'); } // 使用示例 if (isAWithStrings(obj1)) { obj1.property1; // 类型为string }
方案3:使用断言函数
如果你确定检查逻辑绝对可靠,可以用断言函数直接将对象断言为目标类型,无需返回布尔值:
function assertIsStringRecord<T extends Record<string, any>>(obj: T): asserts obj is Record<keyof T, string> { if (!Object.values(obj).every(val => typeof val === 'string')) { throw new Error("对象属性不全是字符串"); } } // 使用示例 assertIsStringRecord(obj1); // 执行后obj1的类型自动变为Record<keyof A, string> obj1.property1; // 类型为string
内容的提问来源于stack exchange,提问作者sayandcode
相关产品推荐
相关产品推荐

