TypeScript与.proto类型互转指南:Nest.js gRPC服务场景
Nest.js gRPC服务间UserSettings类型的Protobuf映射与转换方案
问题背景
假设有两个基于Nest.js的gRPC服务Service A和Service B,需通过RPC在两者间传递UserSettings类型对象。Service A中的TypeScript类型定义如下:
enum Language { AMERICAN_ENGLISH = "en/us", BRITISH_ENGLISH = "en/uk", ITALIAN = "it" } type UserSettings = { theme: "dark" | "white"; language: Language; device: string | number; }
由于Protobuf支持的特性少于TypeScript,请问如何正确将该丰富类型映射到.proto文件?是否需要通过映射函数在Service A中将UserSettings转为UserSettingsProto、在Service B中再转回,或是有更优方案?
一、Protobuf类型映射方案
1. 枚举类型映射
Protobuf的枚举仅支持整数作为枚举值,所以要把TypeScript的字符串值枚举转成整数枚举,同时用注释标注对应的字符串映射:
syntax = "proto3"; enum Language { // 对应TypeScript的 "en/us" AMERICAN_ENGLISH = 0; // 对应TypeScript的 "en/uk" BRITISH_ENGLISH = 1; // 对应TypeScript的 "it" ITALIAN = 2; }
2. 主题字段映射
TypeScript的"dark" | "white"字面量类型,在Protobuf里有两种处理方式:
- 方案一:用枚举(更严格,避免非法值传入)
enum Theme { DARK = 0; WHITE = 1; }
- 方案二:直接用
string类型,在业务逻辑层面校验值是否在允许范围内
3. Device字段映射
Protobuf不支持string | number这种联合类型,这里推荐两种严谨的处理方式:
- 方案一:统一用
string类型,发送方把数字转成字符串,接收方再根据业务需求转成数字 - 方案二:用
oneof字段,明确区分字符串和数字类型(更清晰,避免歧义)
message DeviceInfo { oneof device { string device_str = 1; int64 device_num = 2; } }
完整.proto文件示例
结合上述映射规则,最终的user_settings.proto内容如下:
syntax = "proto3"; enum Language { AMERICAN_ENGLISH = 0; BRITISH_ENGLISH = 1; ITALIAN = 2; } enum Theme { DARK = 0; WHITE = 1; } message DeviceInfo { oneof device { string device_str = 1; int64 device_num = 2; } } message UserSettingsProto { Theme theme = 1; Language language = 2; DeviceInfo device = 3; }
二、类型转换方案
1. 必须做类型转换
Protobuf生成的类型和原TypeScript业务类型结构完全不同,所以服务间调用时必须做类型转换:
- Service A发送请求前:把业务用的
UserSettings转成Protobuf生成的UserSettingsProto - Service B接收请求后:把
UserSettingsProto转回业务用的UserSettings
2. 手动转换函数示例
Service A:业务类型转Protobuf类型
function toUserSettingsProto(settings: UserSettings): UserSettingsProto { const deviceInfo: DeviceInfo = {}; if (typeof settings.device === 'string') { deviceInfo.deviceStr = settings.device; } else { deviceInfo.deviceNum = settings.device; } return { theme: settings.theme === 'dark' ? Theme.DARK : Theme.WHITE, language: Language[settings.language as keyof typeof Language], device: deviceInfo, }; }
Service B:Protobuf类型转业务类型
function toUserSettings(proto: UserSettingsProto): UserSettings { let device: string | number; if (proto.device?.deviceStr) { device = proto.device.deviceStr; } else if (proto.device?.deviceNum !== undefined) { device = proto.device.deviceNum; } else { throw new Error('无效的device值'); } return { theme: proto.theme === Theme.DARK ? 'dark' : 'white', language: Language[proto.language] as Language, device, }; }
3. 更优方案:用工具简化转换
可以借助类库或Nest.js拦截器自动处理转换,减少重复代码:
- 定义DTO类,用装饰器标注Protobuf字段的映射规则
- 自定义Nest.js拦截器,在请求发送/接收阶段自动完成类型转换,不用手动调用转换函数
三、注意事项
- Protobuf枚举默认值是第一个枚举项,要确保业务逻辑能处理默认值的情况
- 使用
oneof字段时,Protobuf会自动忽略多余的字段,转换时要保证只设置其中一个字段 - 类型转换阶段要加入参数校验,避免非法值流入业务逻辑
内容的提问来源于stack exchange,提问作者Amel Amcë Muminovic
相关产品推荐
相关产品推荐

