首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ProtoBuf 生成 Go 代码去掉 JSON tag omitempty

ProtoBuf 生成 Go 代码去掉 JSON tag omitempty

作者头像
恋喵大鲤鱼
发布2022-06-02 13:55:11
4.2K0
发布2022-06-02 13:55:11
举报
文章被收录于专栏:C/C++基础C/C++基础

文章目录

1.背景

我们经常使用 PB(ProtoBuf)作为数据的交换协议,用于数据的序列化与反序列化。对于 PB 生成的 Go strutc,将其序列化为 JSON 时,比如对于数字类型,默认值为零,将不会出现在 JSON 串中。

为什么会这样呢?因为 PB 默认生成 的 Go struct 会带上 JSON tag omitempty,有时我们希望缺省值为零值的字段也能够出现在 JSON 串,我们需要将 struct 中的 JSON tag omitempty 去掉,那么该如何将其去掉呢?

下面将以 PB 的最新版本 proto3,来简单演示:

  • PB 文件的定义
  • protoc 和 protoc-gen-go 的安装
  • 编译 PB 生成 Golang 代码
  • 为 PB 字段自定义 JSON tag

看官莫急,且听我娓娓道来。

2.定义 proto 文件

按照官网给的 proto 示例文件 addressbook.proto ,其定义如下。

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";

message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}

其中syntax = "proto3"表明我们使用版本是 proto3。

其中import "google/protobuf/timestamp.proto"表明我们依赖 timestamp.proto。该文件可以在我们下载 protoc 的安装包中获取到,官方已经为我们打包好了。

其中package tutorial指明当前 pb 文件所属的包,以防止不同项目的 pb 文件发生冲突。

其中option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb"用来指明生成的 Go 文件所属的包的导入路径。路径最后一段包名。我们的示例将使用包名“tutorialpb”。当然我们也可以指定其他包命,在路径后添加个分号后写上我们想要的包命。比如我们以 addressbook 作为包名:

option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb;addressbook";

3.安装 protoc 和 protoc-gen-go

我这里是在 Windows 10 环境下编译 proto 文件。按照官方给的指引,我们需要提前下载安装 protoc 和 protoc-gen-go。

protoc 是 proto 文件的编译器(protocol buffer compiler),用于将 proto 文件翻译成特定语言的类(结构)以及生成相应序列化与反序列化方法。安装详见 download the package。我这里直接下载 Windows 平台的 protoc-21.1-win64.zip,其内容如下:

- bin
	- protoc.exe
- include
- readme.txt

需要将 protoc.exe 拷贝到 PATH 中的任意目录中,以保证在命令行执行它时能够找到它。我这里将其放到 GOROOT/bin 目录下。

protoc-gen-go 是用于生成 Go 代码的插件,供 protoc 使用。安装方式如下:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

编译器插件 protoc-gen-go.exe 将被安装在 GOBIN 中,默认为 GOPATH/bin。它必须位于 PATH 中,以便 protoc 能够找到它。

4. 编译 proto 文件

现在我们来编译上面的 addressbook.proto。

protoc 的命令格式如下:

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

SRC_DIR 为待编译的 proto 文件所在目录,以及被 import 的 proto 文件所在目录,不指定默认为当前目录。DST_DIR 为生成的 Go 代码放置的目录。翻译成我的场景便是:

protoc -I"include;." --go_out=. addressbook.proto

这里需要注意的是,在 Windows 命令行指明多个 proto 文件目录时,只能使用一个 -I 或 --proto_path 选项,多个目录可以使用分号分隔,比如我这里指明 addressbook.proto 所在目录为当前目录,addressbook.proto 中 import google/protobuf/timestamp.proto 的所在目录为当前 include 目录。不像 Linux,多个目录可以使用多个 -I 选项分别指定。

message Person 最终生成的 Go struct 为:

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

	Name        string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	Id          int32                  `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` // Unique ID number for this person.
	Email       string                 `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
	Phones      []*Person_PhoneNumber  `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
	LastUpdated *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"`
}

如果我们将 Person 序列化为 JSON 时,一些零值字段序列化为 JSON 时会被忽略,即不会出现在生成的 JSON 串中。比如 Id 字段,未显示赋值时默认值为 0,那么生成的 JSON 串中将不会有字段 id。这个是由 struct 字段的 json tag 来控制的,其中 omitempty 表示忽略零值。

我们如何让生成的 struct 的 json tag 去掉 omitempty 呢?那么便需要借助 PB 的 Custom Options 功能。

5.自定义选项(Custom Options)

5.1 简介

Custom Options 是大多数人都不会用到需要的高级功能。我们不做绝大多数,所以我们来了解一下吧。

ProtoBuf 允许您定义和使用自己的选项。请注意,这是大多数人不需要的高级功能。因为选项是由 google/protobuf/descriptor .proto 中的消息定义的,如 message FileOptions 和 message FieldOptions,定义自己的选项只需扩展这些消息。例如:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

在这里,我们通过扩展 MessageOptions 定义了一个新的消息级别选项。使用选项时,必须将选项名称括在括号中,以指示它是一个扩展。现在,以 C++ 为例,我们可以在代码中读取 my_option 选项的值,如下所示:

string value = MyMessage::descriptor()->options().GetExtension(my_option);

5.2 FieldOptions

回到我们的问题,想要控制 Golang 中生成 struct 时字段的 JSON tag,那么我们需要扩展 messafe FieldOptions 的定义。其留给用户自定义的 option 编号范围是 1000 至 max。

message FieldOptions {
  ...
  // Clients can define custom options in extensions of this message. See above.
  extensions 1000 to max;
  ...
}

因为我么使用的是 Golang 官方插件 protoc-gen-go,其生成的 json tag 会尝试以小驼峰以及 omitempty,且没有支持改写 JSON tag 的 option 扩展。

5.3 gogoprotobuf

既然 Golang 官方插件不支持,那么我们可以诉诸业界常用的开源插件 gogoprotobuf,其有多个版本:

  • protoc-gen-gofast
  • protoc-gen-gogofast
  • protoc-gen-gogofaster
  • protoc-gen-gogoslick

因为 protoc-gen-gogofaster 在编解码方面更轻更快,且支持 gogoprotobuf extensions,满足我们自定义 JSON tag 的要求。

option

作用于

类型

说明

jsontag (beta)

Field

string

if set, the json tag value between the double quotes is replaced with this string fieldname

因为 gogoprotobuf 在其 gogoproto/gogo.proto 已经对 google.protobuf.FieldOptions 扩展了 jsontag,所以我们直接import gogoproto/gogo.proto就可以用其自定义 JSON tag 的 josontag 这个 option 了。

我们先安装一下 protoc-gen-gogofaster 插件。

go install github.com/gogo/protobuf/protoc-gen-gogofaster

protoc-gen-gogofaster 将被安装到 GOPATH/bin 目录下。我的 GOPATH 为D:\go

接下来,我们需要在我们的 proto 文件 addressbook.proto 中 import gogoproto/gogo.proto,这样就可以使用扩展的 josontag 来自定义 JSON tag 了。

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";

option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";

message Person {
  string name = 1;
  int32 id = 2 [(gogoproto.jsontag) = "id"];  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}

我们使用插件 protoc-gen-gogofaster 重新编译一下 addressbook.proto。

protoc -I"include;." --gogofaster_out=. --plugin="protoc-gen-gogofaste=D:\go\bin\protoc-gen-gogofaster.exe" addressbook.proto

成功后,我们再次看下 messafe Person 生成的 Go struct 为:

type Person struct {
	Name        string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	Id          int32                  `protobuf:"varint,2,opt,name=id,proto3" json:"id"`
	Email       string                 `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
	Phones      []*Person_PhoneNumber  `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"`
	LastUpdated *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"`
}

可以看到,字段 Id 的 JSON tag 已经没有了 omitempty。至此,我们大功告成。

6.小结

本文简单介绍了 proto 文件如何定义,在 Go 中如何编译生成 Go 代码。并且通过自定义 option 的方式,利用第三方插件 protoc-gen-gogofaster 完成对字段 JSON tag 的自定义,来去掉 JSON tag 中的 omitempty。

参考文献

Language Guide (proto3) Protocol Buffer Basics: Go 在go的protobuf中进行自定义json tag标记及使用

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1.背景
  • 2.定义 proto 文件
  • 3.安装 protoc 和 protoc-gen-go
  • 4. 编译 proto 文件
  • 5.自定义选项(Custom Options)
    • 5.1 简介
      • 5.2 FieldOptions
        • 5.3 gogoprotobuf
        • 6.小结
        • 参考文献
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档