如何在TypeScript中创建可自动窄化类型的通用JSON对象接口?
解决JSON对象嵌套属性访问的TypeScript类型问题
问题根源
你原来定义的JsonLikeObject接口中,每个属性的类型是string | number | boolean | JsonLikeObject | JsonLikeObject[]的联合类型。当访问嵌套属性(如myStuff.x.y)时,TypeScript无法确定myStuff.x到底是对象还是基本类型(string/number/boolean),因此会报错——因为基本类型没有y属性。
解决方案
我们需要定义完整的合法JSON类型体系,并结合类型守卫实现自动类型窄化,同时完全禁止Symbol等非JSON合法类型。
1. 定义合法的JSON类型
首先定义覆盖所有JSON规范的类型,包含null(JSON原生支持null),并通过递归结构支持嵌套对象和数组:
// 所有合法的JSON值类型 type JsonValue = string | number | boolean | null | JsonObject | JsonArray; // JSON对象:键为字符串,值为任意JSON值 interface JsonObject { [key: string]: JsonValue; } // JSON数组:元素为任意JSON值 interface JsonArray extends Array<JsonValue> {}
这个类型体系自动排除了Symbol、Function等非JSON类型,完全符合JSON规范。
2. 使用类型守卫实现自动窄化
由于通用JSON对象的结构是动态的,TypeScript无法静态推断嵌套属性的类型,因此需要类型守卫来明确当前值的类型,从而允许合法的嵌套访问:
// 判断值是否为JSON对象的类型守卫 function isJsonObject(value: JsonValue): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); } // 判断值是否为JSON数组的类型守卫 function isJsonArray(value: JsonValue): value is JsonArray { return Array.isArray(value); } // 示例用法 const apiData: JsonObject = { profile: { name: "Bob", score: 95, tags: ["frontend", { category: "framework", name: "React" }] } }; // 安全访问嵌套对象属性 if (isJsonObject(apiData.profile)) { console.log(apiData.profile.name); // 类型:string console.log(apiData.profile.score); // 类型:number // 安全访问数组元素 if (isJsonArray(apiData.profile.tags)) { const secondTag = apiData.profile.tags[1]; if (isJsonObject(secondTag)) { console.log(secondTag.name); // 类型:string } } }
在类型守卫的代码块内部,TypeScript会自动将值的类型窄化为对应的JsonObject或JsonArray,此时嵌套属性访问完全合法。
3. 已知结构的JSON场景
如果你的JSON结构是提前已知的,可以使用satisfies关键字(TypeScript 4.9+)结合as const来保留精确的字面量类型,无需类型守卫即可直接访问嵌套属性:
const fixedData = { user: { id: 101, active: true }, items: [{ title: "Book", price: 29.9 }] } as const satisfies JsonObject; // 直接访问嵌套属性,TypeScript自动推断类型 console.log(fixedData.user.id); // 类型:101 console.log(fixedData.items[0].price); // 类型:29.9
内容的提问来源于stack exchange,提问作者NoNameProvided
相关产品推荐
相关产品推荐

