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

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: bug fix

  "update_mask": {
      "paths": [
          "hello",
          "world"
      ]
  }
```bug fix

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

参考