Oneof

如果您有许多字段的消息,并且最多可以同时设置一个字段,则可以使用Oneof功能来执行此行为并保存内存。一个字段就像常规字段一样,除了单一共享内存中的所有字段,最多可以同时设置一个字段。设置Oneof的任何成员都会自动清除所有其他成员。

Google protobuf 文档#Oneof

示例proto

创建protoOneof.proto 的proto文件

syntax = "proto3";

package oneof_test;
option go_package ='.;oneof';

message WeiboUser{
  string user_id = 1;
  string user_nick = 2;
}

message DouyinUser{
  string auth_token = 1;
  string nick_name = 2;
}

message User{
  oneof user_source{
    string weibo_url = 1;
    string douyin_url = 2;
  }
  oneof user_info{
    WeiboUser weibo_user_info = 3;
    DouyinUser douyin_user_info = 4;
  }
}

使用命令生成go代码

protoc --proto_path=. --go_out=paths=source_relative:./oneof ./protoOneof.proto

生成的protoOneof.pb.go代码如下:

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.28.0
// 	protoc        v3.21.3
// source: protoOneof.proto

package oneof

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

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

	UserId   string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
	UserNick string `protobuf:"bytes,2,opt,name=user_nick,json=userNick,proto3" json:"user_nick,omitempty"`
}

func (x *WeiboUser) Reset() {
	*x = WeiboUser{}
	if protoimpl.UnsafeEnabled {
		mi := &file_protoOneof_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *WeiboUser) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*WeiboUser) ProtoMessage() {}

func (x *WeiboUser) ProtoReflect() protoreflect.Message {
	mi := &file_protoOneof_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use WeiboUser.ProtoReflect.Descriptor instead.
func (*WeiboUser) Descriptor() ([]byte, []int) {
	return file_protoOneof_proto_rawDescGZIP(), []int{0}
}

func (x *WeiboUser) GetUserId() string {
	if x != nil {
		return x.UserId
	}
	return ""
}

func (x *WeiboUser) GetUserNick() string {
	if x != nil {
		return x.UserNick
	}
	return ""
}

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

	AuthToken string `protobuf:"bytes,1,opt,name=auth_token,json=authToken,proto3" json:"auth_token,omitempty"`
	NickName  string `protobuf:"bytes,2,opt,name=nick_name,json=nickName,proto3" json:"nick_name,omitempty"`
}

func (x *DouyinUser) Reset() {
	*x = DouyinUser{}
	if protoimpl.UnsafeEnabled {
		mi := &file_protoOneof_proto_msgTypes[1]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *DouyinUser) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*DouyinUser) ProtoMessage() {}

func (x *DouyinUser) ProtoReflect() protoreflect.Message {
	mi := &file_protoOneof_proto_msgTypes[1]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use DouyinUser.ProtoReflect.Descriptor instead.
func (*DouyinUser) Descriptor() ([]byte, []int) {
	return file_protoOneof_proto_rawDescGZIP(), []int{1}
}

func (x *DouyinUser) GetAuthToken() string {
	if x != nil {
		return x.AuthToken
	}
	return ""
}

func (x *DouyinUser) GetNickName() string {
	if x != nil {
		return x.NickName
	}
	return ""
}

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

	// Types that are assignable to UserSource:
	//	*User_WeiboUrl
	//	*User_DouyinUrl
	UserSource isUser_UserSource `protobuf_oneof:"user_source"`
	// Types that are assignable to UserInfo:
	//	*User_WeiboUserInfo
	//	*User_DouyinUserInfo
	UserInfo isUser_UserInfo `protobuf_oneof:"user_info"`
}

func (x *User) Reset() {
	*x = User{}
	if protoimpl.UnsafeEnabled {
		mi := &file_protoOneof_proto_msgTypes[2]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *User) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*User) ProtoMessage() {}

func (x *User) ProtoReflect() protoreflect.Message {
	mi := &file_protoOneof_proto_msgTypes[2]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use User.ProtoReflect.Descriptor instead.
func (*User) Descriptor() ([]byte, []int) {
	return file_protoOneof_proto_rawDescGZIP(), []int{2}
}

func (m *User) GetUserSource() isUser_UserSource {
	if m != nil {
		return m.UserSource
	}
	return nil
}

func (x *User) GetWeiboUrl() string {
	if x, ok := x.GetUserSource().(*User_WeiboUrl); ok {
		return x.WeiboUrl
	}
	return ""
}

func (x *User) GetDouyinUrl() string {
	if x, ok := x.GetUserSource().(*User_DouyinUrl); ok {
		return x.DouyinUrl
	}
	return ""
}

func (m *User) GetUserInfo() isUser_UserInfo {
	if m != nil {
		return m.UserInfo
	}
	return nil
}

func (x *User) GetWeiboUserInfo() *WeiboUser {
	if x, ok := x.GetUserInfo().(*User_WeiboUserInfo); ok {
		return x.WeiboUserInfo
	}
	return nil
}

func (x *User) GetDouyinUserInfo() *DouyinUser {
	if x, ok := x.GetUserInfo().(*User_DouyinUserInfo); ok {
		return x.DouyinUserInfo
	}
	return nil
}

type isUser_UserSource interface {
	isUser_UserSource()
}

type User_WeiboUrl struct {
	WeiboUrl string `protobuf:"bytes,1,opt,name=weibo_url,json=weiboUrl,proto3,oneof"`
}

type User_DouyinUrl struct {
	DouyinUrl string `protobuf:"bytes,2,opt,name=douyin_url,json=douyinUrl,proto3,oneof"`
}

func (*User_WeiboUrl) isUser_UserSource() {}

func (*User_DouyinUrl) isUser_UserSource() {}

type isUser_UserInfo interface {
	isUser_UserInfo()
}

type User_WeiboUserInfo struct {
	WeiboUserInfo *WeiboUser `protobuf:"bytes,3,opt,name=weibo_user_info,json=weiboUserInfo,proto3,oneof"`
}

type User_DouyinUserInfo struct {
	DouyinUserInfo *DouyinUser `protobuf:"bytes,4,opt,name=douyin_user_info,json=douyinUserInfo,proto3,oneof"`
}

func (*User_WeiboUserInfo) isUser_UserInfo() {}

func (*User_DouyinUserInfo) isUser_UserInfo() {}

var File_protoOneof_proto protoreflect.FileDescriptor

var file_protoOneof_proto_rawDesc = []byte{
	0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x2e, 0x70, 0x72, 0x6f,
	0x74, 0x6f, 0x12, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x22, 0x41,
	0x0a, 0x09, 0x57, 0x65, 0x69, 0x62, 0x6f, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x75,
	0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73,
	0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x63,
	0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x69, 0x63,
	0x6b, 0x22, 0x48, 0x0a, 0x0a, 0x44, 0x6f, 0x75, 0x79, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x12,
	0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20,
	0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b,
	0x0a, 0x09, 0x6e, 0x69, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xe7, 0x01, 0x0a, 0x04,
	0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x09, 0x77, 0x65, 0x69, 0x62, 0x6f, 0x5f, 0x75, 0x72,
	0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x77, 0x65, 0x69, 0x62, 0x6f,
	0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0a, 0x64, 0x6f, 0x75, 0x79, 0x69, 0x6e, 0x5f, 0x75, 0x72,
	0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x64, 0x6f, 0x75, 0x79, 0x69,
	0x6e, 0x55, 0x72, 0x6c, 0x12, 0x3f, 0x0a, 0x0f, 0x77, 0x65, 0x69, 0x62, 0x6f, 0x5f, 0x75, 0x73,
	0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
	0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x57, 0x65, 0x69, 0x62, 0x6f,
	0x55, 0x73, 0x65, 0x72, 0x48, 0x01, 0x52, 0x0d, 0x77, 0x65, 0x69, 0x62, 0x6f, 0x55, 0x73, 0x65,
	0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x42, 0x0a, 0x10, 0x64, 0x6f, 0x75, 0x79, 0x69, 0x6e, 0x5f,
	0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
	0x16, 0x2e, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x6f, 0x75,
	0x79, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x48, 0x01, 0x52, 0x0e, 0x64, 0x6f, 0x75, 0x79, 0x69,
	0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x0d, 0x0a, 0x0b, 0x75, 0x73, 0x65,
	0x72, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72,
	0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x6f, 0x6e, 0x65, 0x6f, 0x66,
	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_protoOneof_proto_rawDescOnce sync.Once
	file_protoOneof_proto_rawDescData = file_protoOneof_proto_rawDesc
)

func file_protoOneof_proto_rawDescGZIP() []byte {
	file_protoOneof_proto_rawDescOnce.Do(func() {
		file_protoOneof_proto_rawDescData = protoimpl.X.CompressGZIP(file_protoOneof_proto_rawDescData)
	})
	return file_protoOneof_proto_rawDescData
}

var file_protoOneof_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_protoOneof_proto_goTypes = []interface{}{
	(*WeiboUser)(nil),  // 0: oneof_test.WeiboUser
	(*DouyinUser)(nil), // 1: oneof_test.DouyinUser
	(*User)(nil),       // 2: oneof_test.User
}
var file_protoOneof_proto_depIdxs = []int32{
	0, // 0: oneof_test.User.weibo_user_info:type_name -> oneof_test.WeiboUser
	1, // 1: oneof_test.User.douyin_user_info:type_name -> oneof_test.DouyinUser
	2, // [2:2] is the sub-list for method output_type
	2, // [2:2] is the sub-list for method input_type
	2, // [2:2] is the sub-list for extension type_name
	2, // [2:2] is the sub-list for extension extendee
	0, // [0:2] is the sub-list for field type_name
}

func init() { file_protoOneof_proto_init() }
func file_protoOneof_proto_init() {
	if File_protoOneof_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_protoOneof_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*WeiboUser); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_protoOneof_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*DouyinUser); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
		file_protoOneof_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*User); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	file_protoOneof_proto_msgTypes[2].OneofWrappers = []interface{}{
		(*User_WeiboUrl)(nil),
		(*User_DouyinUrl)(nil),
		(*User_WeiboUserInfo)(nil),
		(*User_DouyinUserInfo)(nil),
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_protoOneof_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   3,
			NumExtensions: 0,
			NumServices:   0,
		},
		GoTypes:           file_protoOneof_proto_goTypes,
		DependencyIndexes: file_protoOneof_proto_depIdxs,
		MessageInfos:      file_protoOneof_proto_msgTypes,
	}.Build()
	File_protoOneof_proto = out.File
	file_protoOneof_proto_rawDesc = nil
	file_protoOneof_proto_goTypes = nil
	file_protoOneof_proto_depIdxs = nil
}

测试代码:

user := User{
		UserSource: &User_WeiboUrl{WeiboUrl: "https://weibo.com"},
		UserInfo: &User_WeiboUserInfo{
			WeiboUserInfo: &WeiboUser{
				UserId:   "66666",
				UserNick: "czyt",
			},
		},
	}
	// set user source will overwrite the value before
	user.UserSource = &User_DouyinUrl{DouyinUrl: "https://douyin.com"}
	marshal, err := json.Marshal(user)
	if err != nil {
		log.Fatalln(err)
	}
	log.Println(string(marshal))

FieldMask

使用FieldMask 需要引用google/protobuf/field_mask.proto。参考Google的文档

FieldMask 是一个 protobuf 消息,包含一个名为 paths 的字段,用于指定用于指定读取操作返回或更新操作修改的字段。空的FieldMask默认应用到全部字段。

message FieldMask {
  repeated string paths = 1;
}

示例proto

定义一个proto

import "google/protobuf/field_mask.proto";
// 更新用户请求
message UpdateUserReq{
  string id = 1;
  User payload = 2;
  optional google.protobuf.FieldMask update_mask = 3;
}

生成消息体

// 更新用户请求
type UpdateUserReq struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Id         string                 `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
	Payload    *User                  `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
	UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,json=updateMask,proto3,oneof" json:"update_mask,omitempty"`
}

func (x *UpdateUserReq) Reset() {
	*x = UpdateUserReq{}
	if protoimpl.UnsafeEnabled {
		mi := &file_v1_user_reqresp_proto_msgTypes[4]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *UpdateUserReq) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*UpdateUserReq) ProtoMessage() {}

func (x *UpdateUserReq) ProtoReflect() protoreflect.Message {
	mi := &file_v1_user_reqresp_proto_msgTypes[4]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use UpdateUserReq.ProtoReflect.Descriptor instead.
func (*UpdateUserReq) Descriptor() ([]byte, []int) {
	return file_v1_user_reqresp_proto_rawDescGZIP(), []int{4}
}

func (x *UpdateUserReq) GetId() string {
	if x != nil {
		return x.Id
	}
	return ""
}

func (x *UpdateUserReq) GetPayload() *User {
	if x != nil {
		return x.Payload
	}
	return nil
}

func (x *UpdateUserReq) GetUpdateMask() *fieldmaskpb.FieldMask {
	if x != nil {
		return x.UpdateMask
	}
	return nil
}

使用

for _, v := range req.UpdateMask.GetPaths() {
		log.Info("v: ", v)
	}

更多API,参考 https://pkg.go.dev/google.golang.org/protobuf/types/known/fieldmaskpb 除开标准的库之外,还可以使用fieldmask-utils,这个库提供了一系列的方法。

package main

import fieldmask_utils "github.com/mennanov/fieldmask-utils"

// A function that maps field mask field names to the names used in Go structs.
// It has to be implemented according to your needs.
func naming(s string) string {
	if s == "foo" {
		return "Foo"
	}
	return s
}

func main () {
	var request UpdateUserRequest
	userDst := &testproto.User{} // a struct to copy to
	mask, _ := fieldmask_utils.MaskFromPaths(request.FieldMask.Paths, naming)
	fieldmask_utils.StructToStruct(mask, request.User, userDst)
	// Only the fields mentioned in the field mask will be copied to userDst, other fields are left intact
}

需要注意是这里的naming方法,该库底层还是依赖于go的反射实现,所以对于用户传入的field字段,需要编写方法使得field字段名称和Struct中的名称一致,可以参考下面的方法,该方法使用了第三方库strcase

func Naming(fieldName string) string {
	named := strcase.ToCamel(fieldName)
	// 跳过指定字段
	if named == "Amount" {
		return ""
	}
	return named
}

Http中使用的注意事项

参考 issue:https://github.com/golang/protobuf/issues/1273

Field masks have special syntax in the JSON encoding: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#json-encoding-of-field-masks

 Thanks, it looks grammar has changed. In gateway(v1) can be used as flow:
 
 ```json
 "update_mask": {
        "paths": [
            "hello",
            "world"
        ]
    }
 ```
 
 but in gateway(v2) need to update the field format
 
 ```json
  "update_mask":  "hello,world"
 ```

Json Mapping

protobuf 3 的int64``fixed64 uint64类型在json序列化时会被转换为string。 Proto3支持JSON中的规范编码,从而更容易在系统之间共享数据。在下表中以类型为基础描述编码。如果在JSON编码的数据中缺少一个值,或者其值为null,则将其解释为解析为协议缓冲区的适当默认值。如果一个字段在协议缓冲区中具有默认值,则默认情况下将在JSON编码的数据中省略它以节省空间。实现可以提供在JSON编码输出中发射具有默认值的字段的选项。

proto3 JSON JSON example Notes
message object {"fooBar": v, "g": null, …} Generates JSON objects. Message field names are mapped to lowerCamelCase and become JSON object keys. If the json_name field option is specified, the specified value will be used as the key instead. Parsers accept both the lowerCamelCase name (or the one specified by the json_name option) and the original proto field name. null is an accepted value for all field types and treated as the default value of the corresponding field type.
enum string "FOO_BAR" The name of the enum value as specified in proto is used. Parsers accept both enum names and integer values.
map<K,V> object {"k": v, …} All keys are converted to strings.
repeated V array [v, …] null is accepted as the empty list [].
bool true, false true, false
string string "Hello World!"
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" JSON value will be the data encoded as a string using standard base64 encoding with paddings. Either standard or URL-safe base64 encoding with/without paddings are accepted.
int32, fixed32, uint32 number 1, -10, 0 JSON value will be a decimal number. Either numbers or strings are accepted.
int64, fixed64, uint64 string "1", "-10" JSON value will be a decimal string. Either numbers or strings are accepted.
float, double number 1.1, -10.0, 0, "NaN", "Infinity" JSON value will be a number or one of the special string values “NaN”, “Infinity”, and “-Infinity”. Either numbers or strings are accepted. Exponent notation is also accepted. -0 is considered equivalent to 0.
Any object {"@type": "url", "f": v, … } If the Any contains a value that has a special JSON mapping, it will be converted as follows: {"@type": xxx, "value": yyy}. Otherwise, the value will be converted into a JSON object, and the "@type" field will be inserted to indicate the actual data type.
Timestamp string "1972-01-01T10:00:20.021Z" Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits. Offsets other than “Z” are also accepted.
Duration string "1.000340012s", "1s" Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision, followed by the suffix “s”. Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision and the suffix “s” is required.
Struct object { … } Any JSON object. See struct.proto.
Wrapper types various types 2, "2", "foo", true, "true", null, 0, … Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer.
FieldMask string "f.fooBar,h" See field_mask.proto.
ListValue array [foo, bar, …]
Value value Any JSON value. Check google.protobuf.Value for details.
NullValue null JSON null
Empty object {} An empty JSON object

参考 https://developers.google.com/protocol-buffers/docs/proto3#json

protobuf 自定义Go字段的json Tag

官方文档 说明

A proto3 JSON implementation may provide the following options:

  • Emit fields with default values: Fields with default values are omitted by default in proto3 JSON output. An implementation may provide an option to override this behavior and output fields with their default values.
  • Ignore unknown fields: Proto3 JSON parser should reject unknown fields by default but may provide an option to ignore unknown fields in parsing.
  • Use proto field name instead of lowerCamelCase name: By default proto3 JSON printer should convert the field name to lowerCamelCase and use that as the JSON name. An implementation may provide an option to use proto field name as the JSON name instead. Proto3 JSON parsers are required to accept both the converted lowerCamelCase name and the proto field name.
  • Emit enum values as integers instead of strings: The name of an enum value is used by default in JSON output. An option may be provided to use the numeric value of the enum value instead.

proto

message Blog {
  int64 id = 1 [json_name = "uid"];
  string titleName = 2;
  string author_name = 3[json_name = "author_name"];
  string img = 4;
  int64 CountNum = 5;
}

生成go文件

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

	Id         int64  `protobuf:"varint,1,opt,name=id,json=uid,proto3" json:"id,omitempty"`
	TitleName  string `protobuf:"bytes,2,opt,name=titleName,proto3" json:"titleName,omitempty"`
	AuthorName string `protobuf:"bytes,3,opt,name=author_name,json=author_name,proto3" json:"author_name,omitempty"`
	Img        string `protobuf:"bytes,4,opt,name=img,proto3" json:"img,omitempty"`
	CountNum   int64  `protobuf:"varint,5,opt,name=CountNum,proto3" json:"CountNum,omitempty"`
}

goland的实时模板如下,方便快速使用

[json_name = "$FIELD_NAME$"$END$]

设置参考下图 image-20220929093236710

参考