如何为Lodash的set函数添加TypeScript类型安全性?
为Lodash的set函数添加TypeScript类型安全性
Lodash原生的set函数因为类型设计的局限性,无法对路径做严格的类型校验。我们可以自己实现一个类型安全的版本,通过TypeScript递归类型约束路径,同时保留自动创建嵌套对象的能力,且不会覆盖已有属性。
1. 定义合法路径的递归类型
首先需要一个递归类型,用来提取对象的所有合法嵌套路径,支持可选属性:
type Path<T> = T extends object ? { [K in keyof T]: K extends string | number ? `${K}` | `${K}.${Path<T[K]>}` : never; }[keyof T] : never;
这个类型会递归遍历对象的所有可访问属性,生成以点分隔的合法路径字符串,无效路径会被TypeScript直接拦截。
2. 实现类型安全的set函数
接下来实现核心函数,处理路径解析、嵌套对象自动补全,同时避免覆盖已有对象属性:
function safeSet<T extends object>(obj: T, path: Path<T>, value: unknown): void { const keys = path.split('.'); let current: any = obj; // 遍历路径的前置层级,补全缺失的嵌套对象 for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } const finalKey = keys[keys.length - 1]; // 仅当目标属性不存在或不是对象时赋值,避免覆盖已有对象 if (current[finalKey] == null || typeof current[finalKey] !== 'object') { current[finalKey] = value; } }
3. 使用示例
现在可以用这个函数,TypeScript会自动校验路径合法性:
interface User { id: number; profile?: { name: string; address?: { city: string; }; }; } const user: User = { id: 1 }; // 合法路径,无类型错误 safeSet(user, 'profile.name', 'Alice'); safeSet(user, 'profile.address.city', 'New York'); // 无效路径,TypeScript直接报错 safeSet(user, 'profile.age', 25); // 错误:类型'"profile.age"'不能赋值给类型'Path<User>' safeSet(user, 'invalidKey', 'test'); // 错误:类型'"invalidKey"'不能赋值给类型'Path<User>'
4. 扩展:支持数组索引路径
如果需要支持数组索引(比如'profile.addresses[0].city'),可以修改类型和解析逻辑:
// 更新路径类型,支持数组索引 type Path<T> = T extends (infer U)[] ? `[${number}]` | `[${number}].${Path<U>}` : T extends object ? { [K in keyof T]: K extends string | number ? `${K}` | `${K}.${Path<T[K]>}` : never; }[keyof T] : never; // 更新函数,处理数组索引解析 function safeSet<T extends object | any[]>(obj: T, path: Path<T>, value: unknown): void { const segments = path.split(/\.|\[|\]/).filter(Boolean); let current: any = obj; for (let i = 0; i < segments.length - 1; i++) { let key = segments[i]; // 处理数组索引 if (!isNaN(Number(key))) { key = Number(key); if (!Array.isArray(current[key])) { current[key] = []; } } else { if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } } current = current[key]; } let finalKey = segments[segments.length - 1]; if (!isNaN(Number(finalKey))) { finalKey = Number(finalKey); } if (current[finalKey] == null || typeof current[finalKey] !== 'object') { current[finalKey] = value; } }
数组路径使用示例:
interface User { addresses?: { city: string }[]; } const user: User = {}; safeSet(user, 'addresses[0].city', 'London'); // 合法 safeSet(user, 'addresses[1].zip', '12345'); // 合法 safeSet(user, 'addresses[0].invalid', 'test'); // TypeScript报错
这个实现保留了Lodash set的核心功能,同时通过TypeScript类型系统强制路径合法性,避免无效路径的问题,且默认不会覆盖已有对象属性(如果需要覆盖,可去掉对应判断逻辑)。
内容的提问来源于stack exchange,提问作者Evanss
相关产品推荐
相关产品推荐

