TypeScript中如何校验字符串属性路径对指定类型是否合法?
TypeScript 对象属性路径校验实现
下面分别提供*编译时类型安全(支持IDE智能补全、实时报错)*和纯运行时校验两种方案,完全匹配你需要的点属性访问、数组下标访问、字符串键名方括号访问的路径格式。
编译时类型安全方案
这套方案基于TypeScript类型系统实现,不需要额外依赖,IDE会在你输入路径时逐层给出属性提示,非法路径会在编写代码时直接标红,同时会自动校验传入值的类型是否和路径目标属性匹配。
// 解析路径字符串为逐段访问的元组 type ParsePath<S extends string> = S extends `${infer Head}.${infer Tail}` ? [...ParsePath<Head>, ...ParsePath<Tail>] : S extends `${infer Key}[${infer Index}]` ? ParsePath<Key> extends [...infer ParentPath] ? Index extends `'${infer RealKey}'` | `"${infer RealKey}"` ? [...ParentPath, RealKey] : [...ParentPath, number] : never : S extends "" ? [] : [S]; // 根据路径元组推导目标位置的值类型 type ValueAtPath<T, Path extends unknown[]> = Path extends [infer First, ...infer Rest] ? First extends keyof T ? ValueAtPath<T[First], Rest> : First extends number ? T extends Array<infer Item> ? ValueAtPath<Item, Rest> : never : never : T; // 校验路径是否为目标类型的合法访问路径 type ValidPath<T, S extends string> = ParsePath<S> extends infer P ? P extends unknown[] ? ValueAtPath<T, P> extends never ? never : S : never : never; function setValue<T, P extends string>( object: T, path: P & ValidPath<T, P>, value: ValueAtPath<T, ParsePath<P>> ) { const segments: (string | number)[] = []; // 匹配所有路径格式:.key、[num]、['key']、["key"] const pathRegex = /([^.[\]]+)|\[(\d+)\]|\['([^']+)'\]|\["([^"]+)"\]/g; let match: RegExpExecArray | null; while ((match = pathRegex.exec(path)) !== null) { if (match[1]) segments.push(match[1]); else if (match[2]) segments.push(Number(match[2])); else segments.push(match[3] || match[4]); } // 逐层遍历赋值,中间节点不存在时自动创建 let current: any = object; for (let i = 0; i < segments.length - 1; i++) { const seg = segments[i]; if (current[seg] == null) { current[seg] = typeof segments[i + 1] === 'number' ? [] : {}; } current = current[seg]; } current[segments.at(-1)!] = value; }
效果验证
测试基于你定义的TestObject类型:
type TestObject = { arr: Array<{x: number, y: Array<{z: string}>}>; obj: {[key: string]: number}; } const o: TestObject = { arr: [], obj: {} }; setValue<TestObject>(o, "arr[0].x", 1); // 校验通过 setValue<TestObject>(o, "arr[0].y[2].z", "hello world"); // 校验通过 setValue<TestObject>(o, "obj['x']", 1); // 校验通过
非法路径会直接触发编译错误:
setValue<TestObject>(o, "a", 1); // 类型报错,"a"不属于TestObject的合法访问路径
注:由于编译阶段无法获知数组实际长度,所有合法数字下标都会通过校验,符合数组动态长度的实际使用场景。
纯运行时校验方案
如果不需要编译时检查,只需要在代码运行时拦截非法路径,可以使用以下JS实现,无类型依赖:
function setValue(object, path, value) { const segments = []; const pathRegex = /([^.[\]]+)|\[(\d+)\]|\['([^']+)'\]|\["([^"]+)"\]/g; let match; while ((match = pathRegex.exec(path)) !== null) { if (match[1]) segments.push(match[1]); else if (match[2]) segments.push(Number(match[2])); else segments.push(match[3] || match[4]); } let current = object; for (let i = 0; i < segments.length - 1; i++) { const seg = segments[i]; if (current == null || !(seg in current)) { throw new Error(`非法路径:属性${String(seg)}不存在`); } current = current[seg]; } const lastSeg = segments.at(-1); if (current == null) { throw new Error(`非法路径:目标节点为空`); } current[lastSeg] = value; }
该实现会在运行时逐段校验路径是否存在,遇到非法访问直接抛出错误,如果需要和编译时版本对齐支持自动创建中间节点,把对应校验逻辑替换为自动创建数组/对象的逻辑即可。
内容的提问来源于stack exchange,提问作者Scott Schafer
相关产品推荐
相关产品推荐

