你可以在其他消息类型中定义和使用消息类型,如:
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;
}
}
}
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型,你需要导入import google/protobuf/any.proto
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
对于给定的消息类型的默认类型URL是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 Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
目前,用于Any类型的动态库仍在开发之中
如果您有一条包含许多字段的消息,并且最多同时设置一个字段,那么您可以通过使用其中一个特性来强制执行此行为,并节省内存。
Oneof字段类似于常规字段,只不过共享内存中的一个字段中的所有字段都是常规字段,而且最多可以同时设置一个字段。设置其中的任何成员都会自动清除所有其他成员。根据所选择的语言,可以使用特殊 case()
或 WhichOneof()
方法检查 one of 中的哪个值被设置(如果有的话)。
使用 oneof
关键字定义你的 .proto
文件,在下面的案例中测试 test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后将 oneof 字段添加到该字段的定义中,除了 map
字段和 repeated
字段,你可以添加任何类型的字段;
在你生成的代码中,oneof字段有同一个setter和getter常规字段,您还可以获得一个特殊的方法来检查其中一个设置了哪个值(如果有的话)。您可以在相关的 API 参考文献中找到更多关于所选语言的 API。
设置oneof会自动清除其它oneof字段的值,所以设置多次后,只有最后一次设置的字段有值;
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
repeated
.
sub_message
已经通过set_name()
删除了
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
Swap()
两个oneof消息,每个消息,两个消息将拥有对方的值,例如在下面的例子中,msg1
会拥有sub_message
并且msg2
会有name
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
当增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT_SET
, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况,因为没有办法判断如果未识别的字段是一个oneof字段。
Tage 重用问题:
如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:
map<key_type, value_type> map_field = N;
其中key_type
可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type
可以是任意类型。
例如,如果你希望创建一个project的映射,每个Projecct
使用一个string作为key,你可以像下面这样定义:
map<string, Project> projects = 3;
生成map的API现在对于所有proto3支持的语言都可用了,你可以从API指南找到更多信息;
map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:
package foo.bar;
message Open { ... }
在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:
message Foo {
...
required foo.bar.Open open = 1;
...
}
包的声明符会根据使用语言的不同影响生成的代码。
Open
会被封装在 foo::bar
空间中; - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package
;
option go_package
在你的.proto文件中。
Open
会在Foo::Bar
名称空间中。
option java_package
。
PascalCase
后作为名称空间,除非你在你的文件中显式的提供一个option csharp_namespace
,例如,Open
会在Foo.Bar
名称空间中