【问题标题】:How to get only data (exported) fields from protobuf struct with reflect?如何使用反射仅从 protobuf 结构中获取数据(导出)字段?
【发布时间】:2021-10-04 23:48:54
【问题描述】:

我的 proto 文件是这样的

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() 返回 5,表示它包含了所有字段,包括我们不想要的 state、sizeCache、unknownFields .我们只需要 ApID 和 Other。

有没有其他方法可以让反射只返回数据(导出)字段,而忽略元数据(未导出)字段?

【问题讨论】:

  • 如何定义“数据字段”和“元数据字段”?我知道您在示例中的意思,但请形式化它。
  • @icza 虽然 OP 的问题可能会引发一些猜测,但在这种情况下,我认为不太可能发生误解,因此立即编辑和回答可能是一个好的做法
  • @icza 我是 protobuf 的新手,猜测那些字段 state、sizeCache、unknownFields 是元数据,protobuf 添加了这些字段以提供一些控制。而本例中的数据字段是 ApID 和 Other ,由我们在 .proto 文件中定义
  • @AlexPham 我认为您的意思(以及您可以合理做出的唯一区别)是在导出(大写)和未导出(未大写)字段之间

标签: go reflection protocol-buffers


【解决方案1】:

NumFields 方法正确返回已导出或未导出的字段数。

转到 1.17 及更高版本

使用StructField.IsExported

for _, f := range reflect.VisibleFields(v.Type()) {
     if f.IsExported() {
          // do your thing
     }
}

高达 1.16

要知道哪些是导出的,请检查字段 PkgPath 是否为空。

也可以使用CanSet,但在Go 1.17中StructField.IsExported的实现是literallyf.PkgPath == ""。

PkgPath 字段上的文档指出:

PkgPath 是限定小写(未导出)字段名称的包路径。大写(导出)字段名称为空。见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
    }
}

【讨论】:

    【解决方案2】:

    在 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() 获取一个接口,该接口表示消息中声明的字段列表。

    另见:A new Go API for Protocol Buffers

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-11
      • 1970-01-01
      • 1970-01-01
      • 2017-07-06
      • 1970-01-01
      • 1970-01-01
      • 2018-04-21
      • 1970-01-01
      相关资源
      最近更新 更多