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

如何修正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工具,它会精准区分必填和可选字段。

操作步骤

  1. 安装依赖:
npm install -D ts-proto
  1. 修改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"
}
  1. 执行生成命令:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 03:35:44