如何修正gRPC Proto生成的TS接口,准确识别可选字段?
gRPC Proto3生成TypeScript代码的可选属性问题
在ts-node项目中,从带有optional标记的gRPC Proto3文件生成TypeScript代码时,所有生成的TS接口属性均被标记为可选,还额外生成了带下划线前缀的对应可选属性字段。目前需要在TS代码中判断字段是否可选(例如跳过undefined检查),但当前生成的代码无法满足该需求,需修正或调整代码生成方式。
示例Proto定义
message GetStatusResponse { OperationMode mode = 1; optional string transactionId = 2; SystemState state = 3; string systemName = 4; }
当前生成的TypeScript代码
// Original file: proto/autofuel.proto import type { OperationMode as _autofuel_control_OperationMode, OperationMode__Output as _autofuel_control_OperationMode__Output } from '../../autofuel/control/OperationMode'; import type { SystemState as _autofuel_control_SystemState, SystemState__Output as _autofuel_control_SystemState__Output } from '../../autofuel/control/SystemState'; export interface GetStatusResponse { 'mode'?: (_autofuel_control_OperationMode); 'transactionId'?: (string); 'state'?: (_autofuel_control_SystemState); 'systemName'?: (string); '_transactionId'?: "transactionId"; } export interface GetStatusResponse__Output { 'mode'?: (_autofuel_control_OperationMode__Output); 'transactionId'?: (string); 'state'?: (_autofuel_control_SystemState__Output); 'systemName'?: (string); }
期望生成的TypeScript代码
export interface GetStatusResponse { mode: _autofuel_control_OperationMode; transactionId?: string; state: _autofuel_control_SystemState; systemName: string; }
使用的生成命令
proto-loader-gen-types --grpcLib=@grpc/grpc-js --outDir=proto/generated/ proto/*.proto
package.json配置
{ "name": "machine-ui-gateway", "version": "0.0.1", "main": "main.ts", "license": "MIT", "dependencies": { "@grpc/grpc-js": "^1.8.0", "@grpc/proto-loader": "^0.7.4", "async-mqtt": "^2.6.3", "dotenv": "^16.0.3", "express": "^4.18.2" }, "devDependencies": { "@types/express": "^4.17.14", "nodemon": "^2.0.20", "ts-node": "^10.9.1", "typescript": "^4.9.4" }, "scripts": { "build": "tsc", "start": "nodemon main.ts", "proto:gen": "proto-loader-gen-types --grpcLib=@grpc/grpc-js --outDir=proto/generated/ proto/*.proto" } }
解决方法
方案一:切换到ts-proto生成工具
@grpc/proto-loader的类型生成逻辑偏向于兼容gRPC运行时(允许字段缺失,用默认值填充),因此会把所有字段标记为可选。如果需要严格对应Proto3的optional关键字生成TypeScript类型,建议使用ts-proto工具,它会精准区分必填和可选字段。
操作步骤
- 安装依赖:
npm install -D ts-proto
- 修改
package.json中的生成命令:
"scripts": { // ... 其他脚本 "proto:gen": "protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=proto/generated --ts_proto_opt=outputServices=grpc-js,env=node,protoImport=grpc-js proto/*.proto" }
- 执行生成命令:
npm run proto:gen
生成的TypeScript接口会将Proto中标记optional的字段设为TS可选属性(带?),未标记的字段设为必填属性,完全匹配预期需求。
方案二:基于现有生成代码做类型转换
如果不想更换生成工具,可以通过TypeScript工具类型,利用生成代码中带下划线的标记字段,区分真正的可选字段,构造严格类型:
import type { GetStatusResponse } from './proto/generated/autofuel'; // 提取Proto中标记为optional的字段名 type ProtoOptionalFieldNames<T> = { [K in keyof T]: K extends `_${infer RealKey}` ? RealKey : never }[keyof T]; // 构造严格类型:必填字段为非可选,可选字段保留可选 type StrictGetStatusResponse = Omit<GetStatusResponse, ProtoOptionalFieldNames<GetStatusResponse> | `_${string}`> & { [K in ProtoOptionalFieldNames<GetStatusResponse>]?: Exclude<GetStatusResponse[K], undefined> }; // 转换为必填字段无undefined的类型 type RequiredStrictGetStatusResponse = { [K in keyof Omit<StrictGetStatusResponse, ProtoOptionalFieldNames<GetStatusResponse>>]-?: Exclude<StrictGetStatusResponse[K], undefined> } & Pick<StrictGetStatusResponse, ProtoOptionalFieldNames<GetStatusResponse>>;
使用RequiredStrictGetStatusResponse即可得到符合预期的类型:mode、state、systemName为必填,transactionId为可选。
内容的提问来源于stack exchange,提问作者Esben von Buchwald
相关产品推荐
相关产品推荐

