目录
七、嵌套类型
八、更新一个消息类型
九、Any
十、Oneof
1、概述
2、使用Oneof
3、Oneof 特色
4、向后兼容性问题
十一、Map(映射)
1、概述
2、向后兼容性问题
十二、包
1、概述
七、嵌套类型
你可以在其他消息类型中定义和使用消息类型,如:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果你想在它的父消息类型的外部重用这个消息类型,你需要以的形式使用它,如:
message SomeOtherMessage {
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;
}
}
}
八、更新一个消息类型
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
-
不要更改任何已有的字段的数值标识。
-
如果你增加新的字段,使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就可以以适当的方式和旧代码产生的数据交互。相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉。注意,未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的(这和proto2中的行为是不同的,在proto2中未定义的域依然会随着消息被序列化)
-
非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
-
int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
-
sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
-
string和bytes是兼容的——只要bytes是有效的UTF-8编码。
-
嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
-
fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
-
枚举类型与int32,uint32,int64和uint64相兼容(注意如果值不相兼容则会被截断),然而在客户端反序列化之后他们可能会有不同的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但是他的表示方式会依照语言而定。int类型的字段总会保留他们的
九、Any
Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型,你需要导入import google/protobuf/
import "google/protobuf/";
message ErrorStatus {
string message = 1;
repeated details = 2;
}
对于给定的消息类型的默认类型URL是/
不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装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 : ()) {
if (<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
(&network_error);
... processing network_error ...
}
}
目前,用于Any类型的动态库仍在开发之中
十、Oneof
1、概述
如果您有一条包含许多字段的消息,并且最多同时设置一个字段,那么您可以通过使用其中一个特性来强制执行此行为,并节省内存。
Oneof字段类似于常规字段,只不过共享内存中的一个字段中的所有字段都是常规字段,而且最多可以同时设置一个字段。设置其中的任何成员都会自动清除所有其他成员。根据所选择的语言,可以使用特殊 case()
或 WhichOneof()
方法检查 one of 中的哪个值被设置(如果有的话)。
2、使用Oneof
使用 oneof
关键字定义你的 .proto
文件,在下面的案例中测试 test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后将 oneof 字段添加到该字段的定义中,除了 map
字段和 repeated
字段,你可以添加任何类型的字段;
在你生成的代码中,oneof字段有同一个setter和getter常规字段,您还可以获得一个特殊的方法来检查其中一个设置了哪个值(如果有的话)。您可以在相关的 API 参考文献中找到更多关于所选语言的 API。
3、Oneof 特色
设置oneof会自动清除其它oneof字段的值,所以设置多次后,只有最后一次设置的字段有值;
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
-
如果解析器遇到同一个oneof中有多个成员,只有最会一个会被解析成消息。
-
oneof不支持
repeated
. -
反射API对oneof 字段有效.
-
如果使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃, 因为
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
- 在C++中,如果你使用
Swap()
两个oneof消息,每个消息,两个消息将拥有对方的值,例如在下面的例子中,msg1
会拥有sub_message
并且msg2
会有name
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
4、向后兼容性问题
当增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT_SET
, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况,因为没有办法判断如果未识别的字段是一个oneof字段。
Tage 重用问题:
-
将字段移入或移除oneof:在消息被序列号或者解析后,你也许会失去一些信息(有些字段也许会被清除)
-
删除一个字段或者加入一个字段:在消息被序列号或者解析后,这也许会清除你现在设置的oneof字段
-
分离或者融合oneof:行为与移动常规字段相似。
十一、Map(映射)
1、概述
如果你希望创建一个关联映射,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的字段可以是repeated。
-
序列化后的顺序和map迭代器的顺序是不确定的,所以你不要期望以固定顺序处理Map
-
当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序。
-
从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key。
生成map的API现在对于所有proto3支持的语言都可用了,你可以从API指南找到更多信息;
2、向后兼容性问题
map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
十二、包
1、概述
当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:
package ;
message Open { ... }
在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:
message Foo {
...
required open = 1;
...
}
包的声明符会根据使用语言的不同影响生成的代码。
-
对于C++,产生的类会被包装在C++的命名空间中,如上例中的
Open
会被封装在foo::bar
空间中; - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package
; -
对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。
-
对于Go,包可以被用做Go包名称,除非你显式的提供一个
option go_package
在你的.proto文件中。 -
对于Ruby,生成的类可以被包装在内置的Ruby名称空间中,转换成Ruby所需的大小写样式 (首字母大写;如果第一个符号不是一个字母,则使用PB_前缀),例如
Open
会在Foo::Bar
名称空间中。 -
对于javaNano包会使用Java包,除非你在你的文件中显式的提供一个
option java_package
。 -
对于C#包可以转换为
PascalCase
后作为名称空间,除非你在你的文件中显式的提供一个option csharp_namespace
,例如,Open
会在名称空间中