TypeScript:实现带动态输出类型的对象属性提取及通用接口
动态类型安全的toJSON方法实现方案
需求与问题
我有多个类,每个类都具备toJSON方法用于将实例转换为JSON格式。现在需要为这些方法新增一个可选参数,使其能够仅返回类属性的子集,并且返回类型要根据开发者选择的属性动态变化,保证类型安全。
当前方案存在类型漏洞:当传入attributes: ['name']时,返回的truncatedUser仍能访问id/age属性,不会触发TS编译错误,不符合预期。
预期效果示例:
interface IUser { id: string; name: string; age: number; } const fullUser = user.toJSON(); // 类型为完整IUser const truncatedUser = user.toJSON({ attributes: ['name'] }); // 类型为Pick<IUser, 'name'> fullUser.id; // 正常可用 truncatedUser.id; // 触发TS编译错误
现有实现代码
// 使用泛型以适配IUser、IABC、IDEF等不同接口 interface IJSONExportOptions<Type> { /** 结果JSON中包含的对象属性列表 */ attributes?: (keyof Type)[]; } interface IUser { id: string; name: string; age: number; } class User { private id: string; private name: string; private age: number; constructor() { this.id = '1'; this.name = 'Jon Doe'; this.age = 20; } toJSON(opts?: IJSONExportOptions<IUser>) { const fullObj: IUser = { id: this.id, name: this.name, age: this.age, }; if (!opts?.attributes?.length) { return fullObj as Pick<IUser, keyof IUser>; } const truncatedUser: Partial<Pick<IUser, keyof IUser>> = {}; for (const key of opts?.attributes) { const val = fullObj[key]; truncatedUser[key] = val; } return truncatedUser as Pick<IUser, keyof IUser>; } } const user = new User() const fullUser = user.toJSON(); console.log(fullUser.age); const truncatedUser = user.toJSON({ attributes: ['name'] }); console.log(truncatedUser.age); // 此处未触发预期的TS编译错误
解决方案代码
// 调整泛型选项,让属性列表绑定具体的键类型 interface IJSONExportOptions<Type, K extends keyof Type = keyof Type> { attributes?: K[]; } interface IUser { id: string; name: string; age: number; } class User { private id: string; private name: string; private age: number; constructor() { this.id = '1'; this.name = 'Jon Doe'; this.age = 20; } // 给toJSON方法添加泛型K,绑定传入的属性键 toJSON<K extends keyof IUser = keyof IUser>(opts?: IJSONExportOptions<IUser, K>): K extends keyof IUser ? Pick<IUser, K> : IUser { const fullObj: IUser = { id: this.id, name: this.name, age: this.age, }; // 未传入属性列表时返回完整对象 if (!opts?.attributes?.length) { return fullObj as ReturnType<typeof this.toJSON>; } // 断言为Pick类型,确保类型匹配 const truncatedUser = {} as Pick<IUser, K>; for (const key of opts.attributes) { truncatedUser[key] = fullObj[key]; } return truncatedUser as ReturnType<typeof this.toJSON>; } } const user = new User() const fullUser = user.toJSON(); console.log(fullUser.age); // 正常,类型为IUser const truncatedUser = user.toJSON({ attributes: ['name'] }); console.log(truncatedUser.age); // 触发TS编译错误:类型“Pick<IUser, "name">”上不存在属性“age” // 支持多属性选择 const partialUser = user.toJSON({ attributes: ['id', 'age'] }); console.log(partialUser.name); // 同样触发编译错误
关键改动说明
- 泛型绑定属性键:给
IJSONExportOptions和toJSON方法添加泛型K,让attributes的类型从(keyof Type)[]变为具体的K[],TS能精准追踪选中的属性。 - 动态返回类型:通过条件类型
K extends keyof IUser ? Pick<IUser, K> : IUser,让返回类型根据传入的attributes自动切换:- 未传参数时,
K默认是keyof IUser,返回完整IUser类型 - 传入属性列表时,返回对应的
Pick<IUser, K>类型
- 未传参数时,
- 类型断言优化:将
truncatedUser直接断言为Pick<IUser, K>,避免之前Partial带来的类型模糊问题,确保返回对象只包含选中的属性。
内容的提问来源于stack exchange,提问作者Philipp
相关产品推荐
相关产品推荐

