如何将TypeScript类型/接口转换为含键与对应类型的对象?
实现TypeScript类型到运行时元数据的转换方案
核心思路
TypeScript类型仅存在于编译阶段,编译后会被完全擦除,所以无法直接在运行时获取类型信息。要实现需求,必须通过编译时工具提取类型AST并生成对应的运行时代码,或者采用「先定义运行时Schema再推导TypeScript类型」的反向模式。
方案一:使用TypeScript Compiler API手动解析AST
借助TypeScript自身的Compiler API读取类型定义文件,解析AST节点,递归遍历所有依赖类型,生成目标元数据对象。
步骤示例:
- 初始化TypeScript程序,加载目标文件
- 定位
Account类型对应的AST节点 - 递归解析每个属性的类型:
- 基础类型(string/number)直接映射为
{type: 'xxx'} - 数组类型解析其元素类型,生成
{type: 'array', items: ...} - 对象/接口类型递归解析其属性,生成
{type: 'object', properties: ...}
- 基础类型(string/number)直接映射为
代码示例(简化版):
import * as ts from 'typescript'; function parseType(type: ts.Type, checker: ts.TypeChecker): any { // 处理基础类型 if (type.isStringLiteral() || type.getFlags() & ts.TypeFlags.String) { return { type: 'string' }; } if (type.isNumberLiteral() || type.getFlags() & ts.TypeFlags.Number) { return { type: 'number' }; } // 处理数组类型 if (type.getFlags() & ts.TypeFlags.Array) { const elementType = checker.getTypeArguments(type as ts.TypeReference)[0]; return { type: 'array', items: parseType(elementType, checker) }; } // 处理对象/接口类型 if (type.getFlags() & ts.TypeFlags.Object) { const properties = checker.getPropertiesOfType(type); const props: Record<string, any> = {}; for (const prop of properties) { const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration!); props[prop.name] = parseType(propType, checker); } return { type: 'object', properties: props }; } return { type: 'unknown' }; } // 初始化程序 const program = ts.createProgram(['./your-types-file.ts'], {}); const checker = program.getTypeChecker(); const sourceFile = program.getSourceFile('./your-types-file.ts')!; // 找到Account类型 let accountType: ts.Type | undefined; ts.forEachChild(sourceFile, node => { if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) { if (node.name.text === 'Account') { accountType = checker.getTypeAtLocation(node); } } }); if (accountType) { const metadata = parseType(accountType, checker); console.log(JSON.stringify(metadata, null, 2)); }
方案二:使用ts-morph简化AST解析
ts-morph是TypeScript Compiler API的封装库,能大幅简化AST操作,减少样板代码。
代码示例:
import { Project, TypeAliasDeclaration, InterfaceDeclaration } from 'ts-morph'; async function generateMetadata(typeName: string, filePath: string) { const project = new Project(); const sourceFile = project.addSourceFileAtPath(filePath); const typeDeclaration = sourceFile.getTypeAlias(typeName) || sourceFile.getInterface(typeName); if (!typeDeclaration) throw new Error(`Type ${typeName} not found`); function parseNode(node: any): any { if (node.isString()) return { type: 'string' }; if (node.isNumber()) return { type: 'number' }; if (node.isArray()) { const elementType = node.getElementType(); return { type: 'array', items: parseNode(elementType) }; } if (node.isObject() || node.isInterface()) { const properties: Record<string, any> = {}; const props = node.getProperties(); for (const prop of props) { const propType = prop.getType(); properties[prop.getName()] = parseNode(propType); } return { type: 'object', properties }; } return { type: 'unknown' }; } return parseNode(typeDeclaration.getType()); } // 使用示例 generateMetadata('Account', './your-types-file.ts').then(metadata => { console.log(JSON.stringify(metadata, null, 2)); });
方案三:反向定义——先写运行时Schema再推导TypeScript类型
如果可以调整开发流程,推荐先定义运行时可用的Schema(比如用zod库),再从Schema自动推导TypeScript类型,这样既能拿到运行时元数据,又能获得类型安全。
代码示例:
import { z } from 'zod'; // 定义运行时schema const StockSchema = z.object({ name: z.string(), value: z.number() }); const AccountSchema = z.object({ username: z.string(), portfolio: z.array(StockSchema) }); // 自动推导TypeScript类型 type Stock = z.infer<typeof StockSchema>; type Account = z.infer<typeof AccountSchema>; // 自定义转换为目标格式的元数据 function convertZodSchemaToMetadata(schema: z.ZodTypeAny): any { if (schema instanceof z.ZodString) return { type: 'string' }; if (schema instanceof z.ZodNumber) return { type: 'number' }; if (schema instanceof z.ZodArray) { return { type: 'array', items: convertZodSchemaToMetadata(schema.element) }; } if (schema instanceof z.ZodObject) { const properties: Record<string, any> = {}; for (const [key, value] of Object.entries(schema.shape)) { properties[key] = convertZodSchemaToMetadata(value); } return { type: 'object', properties }; } return { type: 'unknown' }; } const metadata = convertZodSchemaToMetadata(AccountSchema); console.log(JSON.stringify(metadata, null, 2));
关键注意事项
- 方案一和二属于编译时执行,生成的元数据可以导出为JSON文件或运行时模块,供应用程序在运行时直接使用
- 若要处理依赖库中的类型,需确保TypeScript能正确加载这些库的类型定义(如
.d.ts文件),初始化Program时需包含对应的文件路径 - 对于联合类型、交叉类型、泛型等复杂类型,需要在解析函数中添加对应的处理逻辑,根据需求调整元数据的生成规则
内容的提问来源于stack exchange,提问作者Ayyli
相关产品推荐
相关产品推荐

