You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于接口构建类型安全嵌套结构的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的属性中
};

实现说明

  1. 用IsBase和IsBaseArray明确区分单个对象与数组场景,规避循环引用导致的类型推导失败
  2. 映射类型中仅保留Base或Base[]类型的属性,基础类型属性会被映射为never,无法在Expander中定义
  3. 数组类型直接映射为其元素类型的Expander,只需描述元素的嵌套结构,无需额外处理数组层级
  4. 所有可嵌套属性支持null标记结束,符合自定义规则

内容的提问来源于stack exchange,提问作者nkr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:04:51