如何使用反射从 protobuf 结构中仅获取数据(导出的)字段?
How to get only data (exported) fields from protobuf struct with reflect?
我的原型文件是这样的
message DeviceOption {
string ApID = 1;
string Other = 2;
}
在 运行 protoc 之后,DeviceOption
结构生成如下:
type DeviceOption struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ApID string `protobuf:"bytes,1,opt,name=ApID,proto3" json:"ApID,omitempty"`
Other string `protobuf:"bytes,2,opt,name=Other,proto3" json:"Other,omitempty"`
}
现在,我想在服务器中使用 https://pkg.go.dev/reflect 解析所有可用字段。我的代码是这样的:
v := reflect.ValueOf(pb.DeviceOption{
ApID: "random1",
Other: "random2",
})
for i := 0; i < v.NumField(); i++ {
// Do my thing
}
v.NumField()
returns 5,表示包含所有字段,包括state, sizeCache , unknownFields 我们不想要。我们只想要 ApID 和 Other.
是否有任何其他方式让 return 仅反映数据(导出的)字段,而忽略元数据(未导出的)字段?
方法 NumFields
正确 returns 导出或未导出的字段数。
转到 1.17 及更高版本
for _, f := range reflect.VisibleFields(v.Type()) {
if f.IsExported() {
// do your thing
}
}
最多 1.16
要知道导出了哪些,请检查字段 PkgPath
是否为空。
也可以使用 CanSet
, but the implementation of StructField.IsExported
in Go 1.17 is literally f.PkgPath == ""
.
PkgPath
字段的文档指出:
PkgPath
is the package path that qualifies a lower case (unexported) field name. It is empty for upper case (exported) field names. See https://golang.org/ref/spec#Uniqueness_of_identifiers
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
if f := typ.Field(i); f.PkgPath == "" {
// do your thing
}
}
对 protobuf 编译器生成的结构类型使用 reflect
很可能不是一个好主意。它们的确切结构应被视为实现细节。某些版本的编译器用于生成名为 XXX_NoUnkeyedLiteral
、XXX_unrecognized
和 XXX_sizecache
的导出字段,如果您只考虑 Go 类型的结构,这些字段将被拾取为“数据”字段.我认为没有任何具体保证不会再次发生。
google.golang.org/protobuf/reflect/protoreflect 包提供了动态检查消息的方法。给定一条消息 m
,您可以执行以下操作以遍历其上设置的所有字段:
proto.MessageReflect(m).Range(
func (field protoreflect.MessageDescriptor, value protoreflect.Value) bool {
// return `true` to continue, `false` to break
...
})
或者您可以使用 proto.MessageReflect(m).Descriptor().Fields()
获取表示消息中已声明字段列表的接口。
我的原型文件是这样的
message DeviceOption {
string ApID = 1;
string Other = 2;
}
在 运行 protoc 之后,DeviceOption
结构生成如下:
type DeviceOption struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ApID string `protobuf:"bytes,1,opt,name=ApID,proto3" json:"ApID,omitempty"`
Other string `protobuf:"bytes,2,opt,name=Other,proto3" json:"Other,omitempty"`
}
现在,我想在服务器中使用 https://pkg.go.dev/reflect 解析所有可用字段。我的代码是这样的:
v := reflect.ValueOf(pb.DeviceOption{
ApID: "random1",
Other: "random2",
})
for i := 0; i < v.NumField(); i++ {
// Do my thing
}
v.NumField()
returns 5,表示包含所有字段,包括state, sizeCache , unknownFields 我们不想要。我们只想要 ApID 和 Other.
是否有任何其他方式让 return 仅反映数据(导出的)字段,而忽略元数据(未导出的)字段?
方法 NumFields
正确 returns 导出或未导出的字段数。
转到 1.17 及更高版本
for _, f := range reflect.VisibleFields(v.Type()) {
if f.IsExported() {
// do your thing
}
}
最多 1.16
要知道导出了哪些,请检查字段 PkgPath
是否为空。
也可以使用 CanSet
, but the implementation of StructField.IsExported
in Go 1.17 is literally f.PkgPath == ""
.
PkgPath
字段的文档指出:
PkgPath
is the package path that qualifies a lower case (unexported) field name. It is empty for upper case (exported) field names. See https://golang.org/ref/spec#Uniqueness_of_identifiers
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
if f := typ.Field(i); f.PkgPath == "" {
// do your thing
}
}
对 protobuf 编译器生成的结构类型使用 reflect
很可能不是一个好主意。它们的确切结构应被视为实现细节。某些版本的编译器用于生成名为 XXX_NoUnkeyedLiteral
、XXX_unrecognized
和 XXX_sizecache
的导出字段,如果您只考虑 Go 类型的结构,这些字段将被拾取为“数据”字段.我认为没有任何具体保证不会再次发生。
google.golang.org/protobuf/reflect/protoreflect 包提供了动态检查消息的方法。给定一条消息 m
,您可以执行以下操作以遍历其上设置的所有字段:
proto.MessageReflect(m).Range(
func (field protoreflect.MessageDescriptor, value protoreflect.Value) bool {
// return `true` to continue, `false` to break
...
})
或者您可以使用 proto.MessageReflect(m).Descriptor().Fields()
获取表示消息中已声明字段列表的接口。