4

How does one get the options associated with a protocol buffer field?

E.g., suppose I have a field with a custom options:

message Foo {
  optional string title = 1 [(indexed) = true];
}

I can get a list of fields:

for f in foo.ListFields():
  print f

How do I access the "indexed" state? (I can see there is a list of f "_options", but that seems "internal"? Is there a proper way to access option extensions by name)?

rsb
  • 1,020
  • 1
  • 10
  • 25

1 Answers1

13

I'll use as an example the nanopb custom options, as defined here. However the answer itself is not in any way nanopb-specific, nanopb uses the standard protobuf style for custom options:

message NanoPBOptions {
   optional int32 max_size = 1;
   ...
}
extend google.protobuf.FieldOptions {
   optional NanoPBOptions nanopb = 1010;
}

and an option defined like this:

message Person {
   optional string email = 3 [(nanopb).max_size = 40];
}

The API used to get the option value varies between languages. However the basic flow is the same:

  1. Get the message descriptor from the object.
  2. Get the field descriptor from the message descriptor.
  3. Get the options from the field descriptor.
  4. Get the extension field from the options, and the value you want from that.

In Python:

desc = person_pb2.Person.DESCRIPTOR
field_desc = desc.fields_by_name['email']
options = field_desc.GetOptions()
value = options.Extensions[nanopb_pb2.nanopb].max_size

In Java:

desc = PersonProto.Person.getDescriptor();
field_desc = desc.findFieldByName("email");
options = field_desc.getOptions();
value = options.getExtension(Nanopb.nanopb).getMaxSize();

In C++:

desc = Person::descriptor()
field_desc = desc->FindFieldByName("email");
options = field_desc->options();
value = options.GetExtension(nanopb).max_size());
jpa
  • 10,351
  • 1
  • 28
  • 45
  • Thank you for your explanation. I am trying to make this work and am having some trouble. First, a simple question: we need to first run protoc on the proto file defining the options to get the compiled version (in this case, we run protoc to get `nanopb_pb2.py`. Is there any way of doing this on the fly (i.e. oh I need these custom options, let me compile their proto so I can access them)? Secondly, when I access `field_desc.options.Extensions[nanopb_pb2.nanopb].max_size` I get `0`. Any ideas as to what I may be doing wrong? – LoveToCode Sep 04 '20 at 16:30
  • I see, you are accessing after compiling with protoc. I am trying to access from within a protoc plugin. – LoveToCode Sep 04 '20 at 16:49
  • @LoveToCode Protoc plugins work so that protoc first processes the file and then passes it to the plugin, so the differences are quite small. [See main_plugin](https://github.com/nanopb/nanopb/blob/master/generator/nanopb_generator.py#L2043) function. – jpa Sep 06 '20 at 06:41
  • the part I am not understanding is that in order for the custom options to be loaded, the plugin must have imported the extension (i.e. `import nanopb_pb2`) as generated by the default plugin _before_ calling `CodeGeneratorRequest.ParseFromString`, but how is the plugin going to know what it needs to import before calling `CodeGeneratorRequest.ParseFromString`? – LoveToCode Sep 20 '20 at 17:35
  • @LoveToCode Well, usually your code would know what kind of options it cares about, import the definitions for all of them and ignore other options. But the [unknown fields](https://stackoverflow.com/questions/32897438/how-to-access-unknown-fields-python-protobuf) mechanism should give access to options that you can't parse. – jpa Sep 20 '20 at 18:15