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

如何将TypeScript类型/接口转换为含键与对应类型的对象?

实现TypeScript类型到运行时元数据的转换方案

核心思路

TypeScript类型仅存在于编译阶段,编译后会被完全擦除,所以无法直接在运行时获取类型信息。要实现需求,必须通过编译时工具提取类型AST并生成对应的运行时代码,或者采用「先定义运行时Schema再推导TypeScript类型」的反向模式。

方案一:使用TypeScript Compiler API手动解析AST

借助TypeScript自身的Compiler API读取类型定义文件,解析AST节点,递归遍历所有依赖类型,生成目标元数据对象。

步骤示例:

  1. 初始化TypeScript程序,加载目标文件
  2. 定位Account类型对应的AST节点
  3. 递归解析每个属性的类型:
    • 基础类型(string/number)直接映射为{type: 'xxx'}
    • 数组类型解析其元素类型,生成{type: 'array', items: ...}
    • 对象/接口类型递归解析其属性,生成{type: 'object', properties: ...}

代码示例(简化版):

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:43:40