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

TypeScript中动态访问const对象字面量的实现方案——基于ts-proto生成代码的编码需求

解决ts-proto动态编码通用方法的问题

我明白你遇到的困境——ts-proto自动生成的代码没法修改,想写一个通用的encode函数来处理不同的类型,但动态导入和类型获取的问题一直卡着。下面给你几个可行的方案:

异步版本的通用encode函数(推荐)

首先要明确:ES模块的动态导入import()是异步操作,所以你的encode函数必须是异步的(返回Promise)。我们可以通过动态导入模块后,获取其中与类型名同名的编码对象(就是ts-proto生成的那个包含encode和fromJSON的const):

// 先定义一个通用类型,描述ts-proto生成的类型结构
type TsProtoType<T> = {
  encode(message: T): Writer;
  fromJSON(object: any): T;
};

async function encode(typeName: string, object: any): Promise<Writer> {
  // 动态导入对应模块
  const module = await import(`/path/to/${typeName}`);
  // 获取模块中导出的编码工具对象(和类型名同名)
  const typeCodec = module[typeName] as TsProtoType<any>;
  
  // 先把原始对象转成目标类型,再执行编码
  const typedMessage = typeCodec.fromJSON(object);
  return typeCodec.encode(typedMessage);
}

// 使用方式(需要在async函数中调用)
async function runEncoding() {
  const writer1 = await encode("BasicMessage", { id: 1, name: "Fake" });
  const writer2 = await encode("BasicMessagePlus", { id: 1, name: "Fake", email: "fake@fake.com" });
  
  // 这里可以处理生成的Writer对象
}
runEncoding();

为什么之前的方案行不通?

  • 同步import的误区:你原来的代码里直接写import(...)没有处理异步,import()返回的是Promise,必须用await或者.then()才能拿到模块内容,同步写法根本获取不到模块导出的对象。
  • eval/globalThis的问题:ts-proto生成的模块是ES模块,作用域是隔离的,导出的内容不会自动挂载到globalThis;而eval只能访问当前作用域的变量,无法直接获取模块导出的内容,除非你手动把每个类型挂载到全局(但你没法修改自动生成的代码,所以这条路走不通)。

优化:添加类型安全(可选)

如果想让函数有更好的类型提示,避免传错字段,可以创建一个类型映射表,把类型名和对应的TS类型关联起来:

// 先导入生成的类型(只导入类型,不会打包代码)
import type { BasicMessage } from '/path/to/BasicMessage';
import type { BasicMessagePlus } from '/path/to/BasicMessagePlus';

// 定义类型名到类型的映射
type TypeMap = {
  BasicMessage: BasicMessage;
  BasicMessagePlus: BasicMessagePlus;
};

// 优化后的encode函数,带类型校验
async function encode<T extends keyof TypeMap>(
  typeName: T,
  object: Partial<TypeMap[T]>
): Promise<Writer> {
  const module = await import(`/path/to/${typeName}`);
  const typeCodec = module[typeName] as TsProtoType<TypeMap[T]>;
  
  const typedMessage = typeCodec.fromJSON(object);
  return typeCodec.encode(typedMessage);
}

// 现在调用时,object会有对应的类型提示,传错字段会报错!
await encode("BasicMessage", { id: 1, name: "Fake" }); // 正确
await encode("BasicMessage", { id: 1, email: "wrong" }); // TypeScript会报错

同步版本的方案(如果必须同步调用)

如果你的业务场景必须同步返回Writer,可以提前预加载所有需要的模块,把编码对象缓存起来:

type TsProtoType<T> = {
  encode(message: T): Writer;
  fromJSON(object: any): T;
};

// 缓存已加载的编码对象
const typeCache = new Map<string, TsProtoType<any>>();

// 初始化缓存(建议在应用启动时执行)
async function initTypeCache() {
  // 并行加载所有需要的模块
  const [basicModule, plusModule] = await Promise.all([
    import('/path/to/BasicMessage'),
    import('/path/to/BasicMessagePlus')
  ]);
  
  typeCache.set('BasicMessage', basicModule.BasicMessage);
  typeCache.set('BasicMessagePlus', plusModule.BasicMessagePlus);
}

// 同步编码函数(必须确保缓存已初始化)
function encodeSync(typeName: string, object: any): Writer {
  const typeCodec = typeCache.get(typeName);
  if (!typeCodec) {
    throw new Error(`类型 ${typeName} 未预加载到缓存中`);
  }
  
  const typedMessage = typeCodec.fromJSON(object);
  return typeCodec.encode(typedMessage);
}

// 使用方式
async function run() {
  await initTypeCache(); // 先初始化缓存
  const writer1 = encodeSync("BasicMessage", { id: 1, name: "Fake" });
  const writer2 = encodeSync("BasicMessagePlus", { id: 1, name: "Fake", email: "fake@fake.com" });
}
run();

这个方案的缺点是需要提前知道所有要用到的类型名,但胜在可以同步调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 22:38:11