自定义protoc-gen-go生成Go结构体,统一字段命名与JSON标签风格

news2025/6/9 13:06:27

背景

在日常的 Go 微服务开发中,Protocol Buffers(protobuf) 是广泛使用的数据交换格式。其配套工具 protoc-gen-go 会根据 .proto 文件生成 Go 结构体代码,但默认生成的字段名、JSON tag 命名风格往往不能满足所有团队或项目的代码规范需求。

比如,团队可能有以下规范或诉求:

  • Go 结构体字段名需要使用特定的 PascalCase 命名规则;
  • JSON tag 必须统一为 snake_case,以与前端规范对齐;
  • 字段名称如 iduser_id等在 Go 代码中必须转换为IDUserID,以保持一致性和清晰性。

遗憾的是,protoc-gen-go 并没有提供原生的机制来满足这些细致的定制需求。因此,我们选择自定义 protoc-gen-go 插件的生成逻辑,以实现结构体字段命名的精细控制。

实现步骤

1. 获取protobuf-go源码

首先克隆指定版本的protobuf-go仓库:

git clone github.com/protocolbuffers/protobuf-go@v1.36.6

2. 定义全局参数

cmd/protoc-gen-go/internal_gengo/main.go文件中定义全局变量和常量:

var (
    IsCustomField bool   // 标识是否使用自定义字段命名
    TagJSONStyle  string // JSON标签命名风格
)

const (
    SnakeCaseStyle = "snake_case"
    CamelCaseStyle = "camel_case"
)

3. 声明命令行参数

cmd/protoc-gen-go/main.go中添加参数解析逻辑:

// 定义option参数
isCustomField = flags.Bool("is_custom_field", false, 
    "struct field naming style setting, default is false, indicates no modification, "+
    "if true indicates custom hump style, for example, message field name suffix "+
    "is _id or Id, struct field name suffix of generated go code is ID")

tagJSONStyle = flags.String("tag_json_style", "", 
    "struct field tag json naming style setting, default is empty, indicates no modification, "+
    "if set to 'snake_case', indicates snake case style, if set to 'camelCase', indicates camel case style. "+
    "NOTE: this option overrides protobuf's json_name option")

// 判断参数是否合法
if *isCustomField {
    gengo.IsCustomField = true
}
if *tagJSONStyle != "" {
    if *tagJSONStyle != gengo.SnakeCaseStyle && *tagJSONStyle != gengo.CamelCaseStyle {
        return fmt.Errorf("protoc-gen-go: invalid tag_json_style value: %q, "+
            "must be 'camel_case' or'snake_case'", *tagJSONStyle)
    }
    gengo.TagJSONStyle = *tagJSONStyle
}

4. 添加命名转换工具

从nameFormat.go复制代码到cmd/protoc-gen-go/internal_gengo目录,并将函数名xstrings.ToCamelCase修改为xstrings.ToPascalCase

5. 修改字段生成逻辑

generateOneFile函数中添加自定义字段命名逻辑:

func generateOneFile(gen *protogen.Plugin, file *protogen.File, f *fileInfo, variant string) *protogen.GeneratedFile {
    // ......
    
    for _, message := range f.allMessages {
        // 添加的自定义字段名风格设置
        if IsCustomField {
            for _, field := range message.Fields {
                field.GoName = toCamel(field.GoName)
            }
        }
        
        genMessage(g, f, message)
    }
    
    // ......
}

6. 修改JSON标签生成逻辑

更新fieldJSONTagValue函数以支持自定义JSON标签风格:

func fieldJSONTagValue(field *protogen.Field) string {
    switch TagJSONStyle {
    case SnakeCaseStyle:
        return customToSnake(string(field.Desc.Name())) + ",omitempty"
    case CamelCaseStyle:
        return customToCamel(string(field.Desc.Name())) + ",omitempty"
    }
    return string(field.Desc.Name()) + ",omitempty" // default
}

使用示例

测试proto文件

使用以下user.proto文件进行测试:

syntax = "proto3";

package api.user.v1;

option go_package = "user/api/user/v1;v1";

service user {
  // Login 登录
  rpc Login(LoginRequest) returns (LoginReply) {}
}

message LoginRequest {
  string email = 1;
  string password = 2;
}

message LoginReply {
  uint64 user_id =1;
  uint64 communityId=2;
  repeated uint64 roleIDs =3;
  string token =4;
}

生成命令对比

  1. 默认生成方式(与原始protobuf-go行为一致):
protoc --go_out=. --go_opt=paths=source_relative user.proto
  1. 自定义字段命名
protoc --go_out=. --go_opt=paths=source_relative --go_opt=is_custom_field=true user.proto
  1. 自定义字段命名+蛇形JSON标签
protoc --go_out=. --go_opt=paths=source_relative --go_opt=is_custom_field=true --go_opt=tag_json_style=snake_case user.proto

效果对比

默认生成的代码

type LoginReply struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    UserId      uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
    CommunityId uint64 `protobuf:"varint,2,opt,name=communityId,json=communityId,proto3" json:"communityId,omitempty"`
    RoleIDs     []uint64 `protobuf:"varint,3,rep,packed,name=roleIDs,json=roleIDs,proto3" json:"roleIDs,omitempty"`
    Token       string `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
}

自定义字段命名后的代码

type LoginReply struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    UserID      uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
    CommunityID uint64 `protobuf:"varint,2,opt,name=communityId,json=communityId,proto3" json:"communityId,omitempty"`
    RoleIDs     []uint64 `protobuf:"varint,3,rep,packed,name=roleIDs,json=roleIDs,proto3" json:"roleIDs,omitempty"`
    Token       string `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
}

自定义字段命名+蛇形JSON标签后的代码

type LoginReply struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    UserID      uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
    CommunityID uint64 `protobuf:"varint,2,opt,name=communityId,json=communityId,proto3" json:"community_id,omitempty"`
    RoleIDs     []uint64 `protobuf:"varint,3,rep,packed,name=roleIDs,json=roleIDs,proto3" json:"role_ids,omitempty"`
    Token       string `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
}

总结

通过修改protoc-gen-go源码,我们实现了以下功能:

  1. 统一结构体字段命名风格(如将ID后缀统一大写)
  2. 控制JSON标签的命名风格(蛇形或驼峰)
  3. 通过命令行参数灵活控制生成行为

这种定制化特别适合需要严格遵循特定代码规范的团队,可以确保生成的代码风格一致,减少人工修改的工作量。

注意事项

  1. 此修改基于protobuf-go v1.36.6版本,其他版本可能需要相应调整
  2. 自定义JSON标签风格会覆盖protobuf原生的json_name选项
  3. 建议团队内部统一使用规范,避免风格不一致

希望本文对需要自定义protobuf代码生成的开发者有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2405391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLMs 系列科普文(11)

目前我们已经介绍了大语言模型训练的两个主要阶段。第一阶段被称为预训练阶段,主要是基于互联网文档进行训练。当你用互联网文档训练一个语言模型时,得到的就是所谓的 base 模型,它本质上就是一个互联网文档模拟器,我们发现这是个…

10万QPS高并发请求,如何防止重复下单

1. 前端拦截 首先因为是10万QPS的高并发请求,我们要保护好系统,那就是尽可能减少用户无效请求。 1.1 按钮置灰 很多用户抢票、抢购、抢红包等时候,为了提高抢中的概率,都是疯狂点击按钮。会触发多次请求,导致重复下…

Xilinx IP 解析之 Block Memory Generator v8.4 ——02-如何配置 IP(仅 Native 接口)

相关文章: Xilinx IP 解析之 Block Memory Generator v8.4 ——01-手册重点解读(仅Native RAM) – 徐晓康的博客 Xilinx IP 解析之 Block Memory Generator v8.4 ——02-如何配置 IP(仅 Native RAM) – 徐晓康的博客 V…

什么是高考?高考的意义是啥?

能见到这个文章的群体,应该都经历过高考,突然想起“什么是高考?意义何在?” 一、高考的定义与核心功能 **高考(普通高等学校招生全国统一考试)**是中国教育体系的核心选拔性考试,旨在为高校选拔…

Spring Cloud 多机部署与负载均衡实战详解

🧱 一、引言 为什么需要多机部署? 解决单节点性能瓶颈,提升系统可用性和吞吐量 在传统单机部署模式下,系统的所有服务或应用都运行在单一服务器上。这种模式在小型项目或低并发场景中可能足够,但随着业务规模扩大、用…

基于定制开发开源AI智能名片S2B2C商城小程序的首屏组件优化策略研究

摘要:在数字化转型背景下,用户对首屏交互效率的诉求日益提升。本文以"定制开发开源AI智能名片S2B2C商城小程序"为技术载体,结合用户行为数据与认知心理学原理,提出首屏组件动态布局模型。通过分析搜索栏、扫码入口、个人…

EasyRTC嵌入式音视频通信SDK音视频功能驱动视频业务多场景应用

一、方案背景​ 随着互联网技术快速发展,视频应用成为主流内容消费方式。用户需求已从高清流畅升级为实时互动,EasyRTC作为高性能实时音视频框架,凭借低延迟、跨平台等特性,有效满足市场对多元化视频服务的需求。 二、EasyRTC技术…

linux下gpio控制

linux下gpio控制 文章目录 linux下gpio控制1.中断命令控制/sys/class/gpio/export终端命令控制led 2.应用程序控制 3.驱动代码控制 1.中断命令控制 通用GPIO主要用于产生输出信号和捕捉输入信号。每组GPIO均可以配置为输出输入以及特定的复用功能。 当作为输入时,内…

Spring Boot 从Socket 到Netty网络编程(下):Netty基本开发与改进【心跳、粘包与拆包、闲置连接】

上一篇:《Spring Boot 从Socket 到Netty网络编程(上):SOCKET 基本开发(BIO)与改进(NIO)》 前言 前文中我们简单介绍了基于Socket的BIO(阻塞式)与NIO(非阻塞式&#xff0…

Orthanc:轻量级PACS服务器与DICOMweb支持的技术详解

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用…

量子计算导论课程设计 之 PennyLane环境搭建

文章目录 具体配置conda 虚拟环境配置Pennylane 正所谓,磨刀不误砍柴工,想要进行量子计算导论的课程设计,首先就是搭建好平台,推荐大家就是本地搭建,那么下面有三种选择 QiskitTensorFlow QuantumPennylane 具体配置…

GAN优化与改进:从条件生成到训练稳定性

摘要 本文聚焦生成对抗网络(GAN)的核心优化技术与改进模型。系统解析 条件生成对抗网络(CGAN) 的可控生成机制、深度卷积GAN(DCGAN) 的架构创新,揭示GAN训练崩溃的本质原因,并介绍W…

linux库(AI回答)

STL POSIX关系 DeepSeek-R1 回答完成 搜索全网22篇资料 STL(标准模板库)和 POSIX(可移植操作系统接口)是两种不同领域的技术标准,它们在 C/C 开发中各有侧重,但可以协同使用。以下是它们的关系和区别&…

CoordConv: CNN坐标感知特征适应

传统卷积 vs CoordConv 详细对比 传统卷积对空间位置不敏感,CoordConv通过显式添加坐标信息解决这个问题在特征图中嵌入(x, y)坐标和可选的径向距离r使模型能够感知空间位置关系 1. 传统卷积的"空间位置不敏感"问题 传统卷积的特点: 输入: …

opencv学习笔记2:卷积、均值滤波、中值滤波

目录 一、卷积概念 1.定义 2.数学原理 3.实例计算 (1) 输入与卷积核 (2)计算输出 g(2,2) 4.作用 二、针对图像噪声的滤波技术——均值滤波 1.均值滤波概念 (1)均值滤波作用 (2&#…

在 Android Studio 中使用 GitLab 添加图片到 README.md

1. 将图片文件添加到项目中 在项目根目录下创建一个 images 或 assets 文件夹 将你的图片文件(如 screenshot.png)复制到这个文件夹中 2. 跟提交项目一样,提交图片到 GitLab 在 Android Studio 的 Git 工具窗口中: 右键点击图片…

HarmonyOS:如何在启动框架中初始化HMRouter

应用启动时通常需要执行一系列初始化启动任务,如果将启动任务都放在应用主模块(即entry类型的Module)的UIAbility组件的onCreate生命周期中,那么只能在主线程中依次执行,不但影响应用的启动速度,而且当启动…

Web3 借贷与清算机制全解析:链上金融的运行逻辑

Web3 借贷与清算机制全解析:链上金融的运行逻辑 超额抵押借款 例如,借款人用ETH为抵押借入DAI;借款人的ETH的价值一定是要超过DAI的价值;借款人可以任意自由的使用自己借出的DAI 稳定币 第一步:借款人需要去提供一定…

【Vue3】(三)vue3中的pinia状态管理、组件通信

目录 一、vue3的pinia 二、【props】传参 三、【自定义事件】传参 四、【mitt】传参 五、【v-model】传参(平常基本不写) 六、【$attrs】传参 七、【$refs和$parent】传参 八、provide和inject 一、vue3的pinia 1、什么是pinia? pinia …

Jenkins自动化部署Maven项目

Jenkins自动化部署Maven项目 一、环境准备(Prerequisites) SpringBoot项目 确保项目根目录有标准Maven结构(pom.xml)且包含Dockerfile: # Dockerfile 示例 FROM openjdk:11-jre-slim VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["j…