基于接口构建类型安全嵌套结构的TypeScript实现问题
问题需求
需要实现泛型Expander,让开发者定义的对象具备类型安全性,避免属性拼写错误,核心规则:
- 接口间可通过属性相互引用
- 部分属性为特定类型的一维数组
null用于标记嵌套的最后一层(自定义规则)- 支持任意深度嵌套,仅允许上层属性类型的有效属性;
number/string等基础类型属性不能出现在Expander结构中
基础接口与使用示例
interface Base { id: string; } interface Foo extends Base { name: string; subFoo: Foo; bars: Bar[]; } interface Bar extends Base { description: string; foo: Foo; } // 预期的Expander使用效果 const expander: Expander<Bar> = { foo: null, // ✅ 正确:标记嵌套结束 foo: { bars: { foo: null } }, // ✅ 正确:合法嵌套引用 foo: { baz: null }, // ❌ 应报错:baz不是Foo的属性 baz: null, // ❌ 应报错:baz不是Bar的属性 };
当前尝试的代码
type BaseOrArray = Base | Base[]; type Expander<B extends BaseOrArray> = { [P in keyof B]: B[P] extends BaseOrArray ? Expander<B[P]> | null : never; }[keyof B];
遇到的错误
Type of property 'bars' circularly references itself in mapped type '{ [P in keyof Foo]: Foo[P] extends BaseOrArray ? Expander<Foo[P]> | null : never; }'.
(翻译:映射类型中属性'bars'的类型循环引用了自身)
解决方案
问题源于循环引用的类型推导冲突,需要通过辅助类型区分单个对象与数组场景,同时过滤基础类型属性,调整后的泛型实现如下:
interface Base { id: string; } // 辅助类型:判断是否为Base的子类型 type IsBase<T> = T extends Base ? true : false; // 辅助类型:判断是否为Base数组 type IsBaseArray<T> = T extends Base[] ? true : false; // 核心Expander泛型 type Expander<T> = // 单个Base对象:仅保留可嵌套的属性(Base/Base数组类型) IsBase<T> extends true ? { [K in keyof T]?: T[K] extends Base ? Expander<T[K]> | null : T[K] extends Base[] ? Expander<T[K][number]> | null : never; } : // Base数组:直接映射为元素类型的Expander IsBaseArray<T> extends true ? Expander<T[number]> : never;
验证效果
const expander1: Expander<Bar> = { foo: null // ✅ 正确 }; const expander2: Expander<Bar> = { foo: { bars: { foo: null // ✅ 正确嵌套 } } }; const expander3: Expander<Bar> = { foo: { baz: null } // ❌ 报错:baz不存在于Foo的属性中 }; const expander4: Expander<Bar> = { baz: null // ❌ 报错:baz不存在于Bar的属性中 };
实现说明
- 用
IsBase和IsBaseArray明确区分单个对象与数组场景,规避循环引用导致的类型推导失败 - 映射类型中仅保留
Base或Base[]类型的属性,基础类型属性会被映射为never,无法在Expander中定义 - 数组类型直接映射为其元素类型的
Expander,只需描述元素的嵌套结构,无需额外处理数组层级 - 所有可嵌套属性支持
null标记结束,符合自定义规则
内容的提问来源于stack exchange,提问作者nkr
相关产品推荐
相关产品推荐

