如何使用反射从 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 我们不想要。我们只想要 ApIDOther.

是否有任何其他方式让 return 仅反映数据(导出的)字段,而忽略元数据(未导出的)字段?

方法 NumFields 正确 returns 导出或未导出的字段数。

转到 1.17 及更高版本

使用StructField.IsExported

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_NoUnkeyedLiteralXXX_unrecognizedXXX_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() 获取表示消息中已声明字段列表的接口。

另请参阅:A new Go API for Protocol Buffers