Skip to content

What is the idiomatic way to get the corresponding struct field for a FieldDescriptorProto? #457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
junghoahnsc opened this issue Nov 21, 2017 · 4 comments
Labels

Comments

@junghoahnsc
Copy link

Hello,

I'm trying to loop each field of a message and check proto field option. For example,

rval := reflect.Value(m)
...
_, descProt := descriptor.ForMessage(m)
for _, fd := range d.GetField() {
  // check the field option
  opts := fd.GetOptions()
  c, err := proto.GetExtension(opts, xxx)
  ...
  if condition {
     field := rval.FieldByName(fd.GetName() ???)
  }
}

Here, fd.GetName() returns the proto field name, so we need to convert to Go name.
Should I use https://github.com/golang/protobuf/blob/master/protoc-gen-go/generator/generator.go#L2736 ? Or is there any better way to do that?

Thanks,

@dsnet dsnet added the question label Dec 5, 2017
@dsnet
Copy link
Member

dsnet commented Dec 5, 2017

Unfortunately, there's currently not really an idiomatic way to do this.

  • The generated field name is a combination of both generator.CamelCase and generator.allocNames.
  • There is no compatibility guarantees for the generator package and you should not depend on it.
  • This code makes the assumption that generated structs are always just flat structs with fields. This may not always be the case in the future, and is certainly not true for all implementations of proto.Message.

The solution for this is #364, so that you can use some proto reflection API to obtain the field value.

@dsnet
Copy link
Member

dsnet commented Dec 6, 2017

I'm going to close this in favor of #364.

@dsnet dsnet closed this as completed Dec 6, 2017
@jhump
Copy link
Contributor

jhump commented Jan 11, 2018

FWIW, @junghoahnsc, it is possible to extract the field with existing APIs. But it isn't trivial (and not very pretty):

rval := reflect.Value(m)
props := proto.GetProperties(reflect.TypeOf(m).Elem())
// ...
_, descProt := descriptor.ForMessage(m)
for _, fd := range descProt.GetField(m) {
    // check the field option
    opts := fd.GetOptions()
    c, err := proto.GetExtension(opts, xxx)
    // ...
    if condition {
        var field reflect.Value
        for _, p := range props.Prop {
            if int32(p.Tag) == fd.GetNumber() {
                field = rval.FieldByName(p.Name)
                break
            }
        }
        if !field.IsValid() {
            // must be a one-of if not found above
            for _, oo := range props.OneofTypes {
                if int32(oo.Prop.Tag) == fd.GetNumber() {
                    field = rval.Field(oo.Field).Elem().FieldByName(oo.Prop.Name)
                    break
                }
            }
        }
        if !field.IsValid() {
            panic(fmt.Sprintf("cannot find struct field for %s, tag %d", fd.GetName(), fd.GetNumber()))
        }
        // do something with field...
    }
}

Note that this code is a naive implementation. (A better one would probably pre-compute a map of tag number -> accessor function to avoid paying the linear scan for each field query. As is, the example code is quadratic when it could be linear.)

@junghoahnsc
Copy link
Author

junghoahnsc commented Jan 12, 2018

@jhump Thanks for the tip!

And I noticed that StructProperties has decoderTags and decoderOrigNames and I guess it would be much easier if it has public APIs to use them.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants