背景
通常来说, 使用proto定义message的Field是使用下划线,比如:
# proto定义
message Req {
string key_name = 1;
}
# 生成的.pb.go 中req的定义
type Req struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
KeyName string `protobuf:"bytes,1,opt,name=key_name,json=keyName,proto3" json:"key_name,omitempty"`
注意: proto的定义,现在默认推荐proto3, 所以此处忽略 proto2中可以给字段声明optional和required。
不过,需要特别注意一下tag中如果添加了omitempty选项,proto2和proto3中对于默认值的处理有所不同。
比如:proto2中对于添加了optional的bool类型,生成的字段是个指针类型,默认值为nil才是empty,false就不是默认值了,而对于proto3中bool的定义,默认值就是false,对于添加omitempty的tag来说就是空了(切记)
从生产的pb.go文件可以看到, 默认生成的json tag是根据字段名的定义来的
可能你会疑惑: protobuf声明的tag中有name=key_name和json=keyName的两者,这应该是用在不同marshal和unMarshal的场景, 比如之前提到的jsonpb库,可以指定marshal的选项: OrigName= true / fasle:
import (
"github.com/gogo/protobuf/jsonpb"
"encoding/json"
)
marshaler := &jsonpb.Marshaler{
OrigName: false, // 是否按字段的声明(.proto) name
EnumsAsInts: true,
EmitDefaults: true,
}
# 一个req 示例
req := &Req {
KeyName: "123",
}
# OrigName=false
out, _ := marshaler.MarshalToString(req)
t.Logf("out of jsopb:%v\n", out). // {"keyName":"123"}
outJson, _ := json.Marshal(req)
t.Logf("out of json:%v\n", string(outJson)) // {"key_name":"123"}
# OrigName=true
marshaler = jsonpb.Marshaler{
OrigName: true,
}
out, _ = marshaler.MarshalToString(req)
t.Logf("out of jsopb:%v\n", out). // {"key_name":"123"}
- OrigName= true: 此时就是按照protobuf声明的tag中name=key_name来处理的
- OrigName= false: 此时就是按照protobuf声明的tag中json=keyName来处理的
到此阶段, pb能生成的基本就是这样的。那如果我想添加自定义的tag呢?
比如: keyName 映射成 key_real_name 等, 这样的需求应该还不少
添加自定义tag
可以使用这个工具来给Field添加
先安装一下:
go install github.com/favadi/protoc-go-inject-tag@latest
修改对应的proto定义, 然后执行:protoc-go-inject-tag -input="./*.pb.go"
message Req {
string key_name = 1; // @gotags: json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"
}
# 对应的pb生产文件:
type Req struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
KeyName string `protobuf:"bytes,1,opt,name=key_name,json=keyName,proto3" json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"` // @gotags: json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"
}
可以看到,通过@gotags 自定义的tag声明, 生效了。
1. json的tag可以指定新的名称
2. 添加了用于映射mongodb中字段的bson tag
3. 同时, 还添加了orm组件的字段tag 映射名称
是不是很方便, Enjoy ~
小技巧
问题: 如果想要复用proto的message定义, 一般可以直接在proto中引用对应message的定义即可, 那如果我不行新增一个层级呢?
正常使用情况:
message Req {
string key_name = 1; // @gotags: json:"key_real_name" bson:"key_in_monogo" orm:"key_in_orm"
}
message ReqMeta {
string meta = 1;
}
// 引用Req和ReqMeta
message ReqAndMeta {
Req req = 1;
ReqMeta meta = 2;
}
如果是这样, json格式为:
{"req":{"key_real_name":"123"},"meta":{"meta":"meta123"}}
如果这就是你想要的, 那通过在proto中定义一下就可以了。
如果你不想要新增一个req和meta层级呢?
直接在proto中引用定义肯定不行, 但是可以使用golang的特性: 组合
type ReqAndMetaCombine struct {
*Req
*ReqMeta
}
通过组合多个proto中的message,最后的json格式如下:
{"key_real_name":"123","meta":"meta123"}
确实少了一个层级, it works ~