Protobuf
标量值类型
默认值
解析消息后,如果经过编码的消息不包含特定单数元素,则解析对象中的相应字段将设置为该字段的默认值。这些默认值因类型而异:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于布尔值,默认值为 false。
- 对于数值类型,默认值为零。
- 对于枚举,默认值为第一个定义的枚举值,必须为 0。
- 对于消息字段,系统不会设置此字段。其确切值取决于语言。如需了解详情,请参阅生成的代码指南。
重复字段的默认值为空(通常,采用相应语言的空列表)。
请注意,对于标量消息字段,在解析消息后,就无从判断字段是明确设为默认值(例如布尔值是否设为 false)还是根本不设置:在定义消息类型时,您应该记住这一点。例如,如果您不想让某项行为默认发生,可设置一个布尔值,将其设为 false 时开启该行为。另请注意,如果标量消息字段已设置为默认值,则该值不会通过序列化进行序列化。
请参阅您所用语言的生成代码指南,详细了解生成的代码中默认值的工作原理。
枚举
在定义消息类型时,您可能希望它的某个字段仅具有一个预定义的值列表。例如,假设您想要为每个 SearchRequest 添加一个 corpus 字段,其中正文可以是 UNIVERSAL、WEB、IMAGES、LOCAL、NEWS、PRODUCTS 或 VIDEO。为此,您只需在消息定义中添加 enum 并针对每个可能的值添加一个常量。
在以下示例中,我们添加了一个名为 Corpus 的 enum(包含所有可能的值)和 Corpus 类型的字段:
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
Corpus corpus = 4;
}
如您所见,Corpus 枚举的第一个常量映射到零:每个枚举定义必须包含一个映射到零的第一个常量作为其第一个元素。原因如下:
您可以通过为不同的枚举常量分配相同的值来定义别名。为此,您需要将 allow_alias 选项设置为 true,否则协议编译器会在发现别名时生成错误消息。虽然所有别名值在反序列化期间都有效,但序列化时始终使用第一个值。
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
ENAA_FINISHED = 2;
}
枚举器常量必须在 32 位整数范围内。由于 enum 值使用线上的变体编码,因此负值效率低下,因此不推荐使用。您可以在消息定义中定义 enum(如上例所示),也可在外部进行定义。这些 enum 可在 .proto 文件中的任何消息定义中重复使用。您还可以使用语法 _MessageType_._EnumType_ 将一条消息中声明的 enum 类型用作其他消息中的字段类型。
在使用 enum 的 .proto 上运行协议缓冲区编译器时,生成的代码将具有用于 Java、Kotlin 或 C++ 的相应 enum,或用于 Python 的特殊 EnumDescriptor 类,该类用于在运行时生成的类中创建一组包含整数值的符号常量。
注意:生成的代码可能受特定于语言的枚举器数量限制(一种语言为数千种)的限制。请查看您计划使用的语言的限制。
在反序列化期间,无法识别的枚举值将保留在消息中,但当消息反序列化时如何表示该值取决于语言。在支持开放式枚举类型(例如 C++ 和 Go)范围之外的语言中,未知枚举值仅存储为其底层整数表示形式。在 Java 等封闭枚举类型语言中,枚举中的大小写用于表示无法识别的值,您可以通过特殊访问器访问底层整数。在任一情况下,如果消息已序列化,则无法识别的值仍会与消息进行序列化。
如需详细了解如何在应用中使用消息 enum,请参阅所选代码指南(针对您选择的语言)。
Any
借助 Any 消息类型,您可以将消息作为嵌入式类型使用,而无需指定 .proto 定义。Any 包含任意序列化消息(如 bytes),以及作为全局唯一标识符并解析为消息类型的网址。如需使用 Any 类型,您需要导入 google/protobuf/any.proto。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
给定消息类型的默认类型网址为 type.googleapis.com/_packagename_._messagename_。
不同的语言实现将支持运行时库帮助程序以类型安全的方式打包和解压缩 Any 值。例如,在 Java 中,Any 类型将具有特殊的 pack() 和 unpack() 访问器,而在 C++ 中则有 PackFrom() 和 UnpackTo() 方法:
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
目前,与 Any 类型搭配使用的运行时库正在开发中。
如果您已熟悉 proto2 语法,则 Any 可以保留任意 proto3 消息,类似于允许扩展程序的 proto2 消息。
Oneof
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
- A oneof cannot be .
repeated
Setting a oneof field will automatically clear all other members of the oneof. So if you set several oneof fields, only the last field you set will still have a value.
Tag Reuse Issues
- Move fields into or out of a oneof: You may lose some of your information (some fields will be cleared) after the message is serialized and parsed. However,** **you can safely move a single field into a new oneof and may be able to move multiple fields if it is known that only one is ever set. See Updating A Message Type for further details.
- Delete a oneof field and add it back: This may clear your currently set oneof field after the message is serialized and parsed.
- Split or merge oneof: This has similar issues to moving regular fields.
未知字段
未知字段是格式正确的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件使用新字段解析新二进制文件发送的数据时,这些新字段将变为旧二进制文件中的未知字段。
最初,proto3 消息在解析期间始终舍弃未知字段,但在版本 3.5 中,我们重新引入了保留未知字段以匹配 proto2 行为。在版本 3.5 及更高版本中,未知字段在解析期间会保留并包含在序列化输出中。
添加评论
如需为 .proto 文件添加注释,请使用 C/C++ 样式的 // 和 /* ... */ 语法。
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
预留值
如果您通过完全移除某个枚举条目或将其注释掉以更新某个枚举类型,那么将来的用户在对该类型进行更新时可以重复使用相应的数值。如果用户日后加载同一 .proto 的旧版本(包括数据损坏、隐私 bug 等),这可能会导致严重问题。确保不会发生这种情况的一种方法是,指定已删除条目的数值(和/或名称,也可能导致 JSON 序列化问题)为 reserved。如果任何未来用户尝试使用这些标识符,协议缓冲区编译器就会抱怨。您可以使用 max 关键字指定预留的数值范围,使其达到可能的最大值。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
请注意,您不能在同一 reserved 语句中混用字段名称和数值。
嵌套类型
您可以在其他消息类型中定义和使用消息类型,如以下示例所示:Result 消息在 SearchResponse 消息中定义:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果您要在父消息类型之外重复使用此消息类型,请将其引用为 _Parent_._Type_:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
您可以根据需要嵌套深层消息:
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
Map
map<key_type, value_type> map_field = N;
map<string, Project> projects = 3;
- 映射字段不能为
repeated。 - 映射值的线格式格式和映射迭代顺序尚未定义,因此您不能指望 Map 想按特定顺序排列。
- When generating text format for a , maps are sorted by key. Numeric keys are sorted numerically.
.proto
参考资料
https://protobuf.dev/programming-guides/proto3/#oneof