Use the protobuf reflection API to inspect and manipulate messages, fields, and descriptors at runtime without compile-time knowledge of the message type.
The protobuf reflection API lets you work with messages whose types are not known at compile time. It provides runtime access to descriptors (the schema of a message), field values by name or number, and dynamic message instances. This is the foundation for tools like JSON transcoding, protocol bridges, and schema-driven validation.
Every generated message class exposes a Descriptor through its static descriptor() method or via GetDescriptor() on an instance. The Descriptor class, defined in descriptor.h, provides access to all schema information:
#include "google/protobuf/descriptor.h"#include "myproject/messages.pb.h"const google::protobuf::Descriptor* desc = MyMessage::descriptor();// Basic metadatastd::cout << desc->name() << std::endl; // e.g. "MyMessage"std::cout << desc->full_name() << std::endl; // e.g. "myproject.MyMessage"std::cout << desc->field_count() << std::endl;// Iterate all fieldsfor (int i = 0; i < desc->field_count(); ++i) { const google::protobuf::FieldDescriptor* field = desc->field(i); std::cout << field->name() << " " // field name << field->number() << " " // field tag number << field->type() << std::endl; // FieldDescriptor::TYPE_*}// Lookup by name or numberconst google::protobuf::FieldDescriptor* f = desc->FindFieldByName("query");const google::protobuf::FieldDescriptor* f2 = desc->FindFieldByNumber(1);
The Reflection class provides runtime get/set access to individual fields. You obtain a Reflection pointer from a message instance:
#include "google/protobuf/message.h"MyMessage msg;msg.set_query("hello");msg.set_page_number(2);const google::protobuf::Reflection* refl = msg.GetReflection();const google::protobuf::Descriptor* desc = msg.GetDescriptor();// Get a field by nameconst google::protobuf::FieldDescriptor* query_field = desc->FindFieldByName("query");// Read a string fieldstd::string val = refl->GetString(msg, query_field);// Check presence (for fields with explicit presence)if (refl->HasField(msg, query_field)) { std::cout << "query is set: " << val << std::endl;}// Set a field value on a mutable messageMyMessage* mutable_msg = msg.New();const google::protobuf::FieldDescriptor* page_field = desc->FindFieldByNumber(2);refl->SetInt32(mutable_msg, page_field, 5);
DynamicMessageFactory constructs Message instances for any Descriptor, including those loaded at runtime from binary FileDescriptorProto data. This is essential for scenarios like a proxy that processes message types it was not compiled against.
#include "google/protobuf/dynamic_message.h"#include "google/protobuf/descriptor.h"#include "google/protobuf/descriptor_database.h"// Build a descriptor from a runtime-loaded FileDescriptorProtogoogle::protobuf::DescriptorPool pool;const google::protobuf::FileDescriptor* file = pool.BuildFile(file_descriptor_proto);const google::protobuf::Descriptor* desc = pool.FindMessageTypeByName("com.example.DynamicMessage");// Create a factory and get the prototypegoogle::protobuf::DynamicMessageFactory factory;const google::protobuf::Message* prototype = factory.GetPrototype(desc);// Allocate a mutable instancegoogle::protobuf::Message* msg = prototype->New();// Use reflection to set fieldsconst google::protobuf::Reflection* refl = msg->GetReflection();const google::protobuf::FieldDescriptor* name_field = desc->FindFieldByName("name");refl->SetString(msg, name_field, "hello");// Serialize to bytesstd::string bytes;msg->SerializeToString(&bytes);delete msg;
DynamicMessageFactory::GetPrototype() is thread-safe and caches prototypes per descriptor. Calling it twice with the same Descriptor returns the same object. Objects created from prototype->New() must be destroyed before the factory is destroyed.
Descriptors used with a DynamicMessageFactory must outlive the factory. Do not destroy a DescriptorPool while a DynamicMessageFactory that references it is still alive.
If your Descriptor comes from the generated pool (DescriptorPool::generated_pool()), you can tell the factory to use the compiled-in generated implementation instead of creating a new dynamic implementation:
google::protobuf::DynamicMessageFactory factory;factory.SetDelegateToGeneratedFactory(true);// For descriptors from generated_pool(), this returns the generated prototypeconst google::protobuf::Message* prototype = factory.GetPrototype(MyMessage::descriptor());
Python’s protobuf API uses the HasField, ClearField, and ListFields methods on message instances:
from myproject import messages_pb2msg = messages_pb2.MyMessage()msg.query = "hello"msg.page_number = 2# List all set fieldsfor field_desc, value in msg.ListFields(): print(f"{field_desc.name} = {value}")# Access descriptordesc = messages_pb2.MyMessage.DESCRIPTORfor field in desc.fields: print(f"{field.name}: {field.type}")# Check and clear fieldsif msg.HasField("query"): msg.ClearField("query")
For dynamic message creation in Python, use google.protobuf.message_factory:
from google.protobuf import descriptor_pb2from google.protobuf import descriptor_poolfrom google.protobuf import message_factory# Load a FileDescriptorProto and create message classes dynamicallypool = descriptor_pool.DescriptorPool()pool.Add(file_descriptor_proto)factory = message_factory.MessageFactory(pool=pool)MsgClass = factory.GetPrototype( pool.FindMessageTypeByName("com.example.DynamicMessage"))msg = MsgClass()msg.name = "hello"
The DescriptorPool holds a collection of file descriptors and resolves cross-file symbol references. The generated code registers itself with DescriptorPool::generated_pool() at startup. You can also construct your own pool and populate it at runtime:
#include "google/protobuf/descriptor.h"#include "google/protobuf/descriptor_database.h"// Find a message type by fully-qualified nameconst google::protobuf::Descriptor* desc = google::protobuf::DescriptorPool::generated_pool() ->FindMessageTypeByName("myproject.MyMessage");// Find all extensions of a message typestd::vector<const google::protobuf::FieldDescriptor*> extensions;google::protobuf::DescriptorPool::generated_pool() ->FindAllExtensions(desc, &extensions);// Find a service by nameconst google::protobuf::ServiceDescriptor* svc = google::protobuf::DescriptorPool::generated_pool() ->FindServiceByName("myproject.MyService");