默认值
当一个Message被解析,如果对应字段没有值,解析器会对字段赋予默认值。下面是各个类型的默认值:
- 对于string,默认值是空字符串。
- 对于byte数组,默认值是空的byte数组。
- 对于bool,默认值是false。
- 对于数值类型,默认值是0。
- 对于枚举,默认值是枚举中第一个字段,且值必须是0。
- 对于Message字段,没有默认值,不同语言有不同的处理。
对于repeated字段的默认值是空list。
注意对于字段是Message类型的时候,一旦message被解析就无法赋值默认值,或者压根没有赋值。你需要小心的定义一个Message字段。例如,当你不希望默认行为发生的时候,将默认行为开关设置成false。
另外注意如果message指定了默认值,默认值并不参与序列化传递。
枚举
当想定义一个Message类型是,可能需要一个字段只有预定义的指定值。例如添加一个corpus字段在SearchRequest上,corpus只能是UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS 或 VIDEO,其中一个值。在Message上定义一个enum可以很简单实现。
在下面的例子中,我们添加了一个enum Corpus和一个Corpus字段。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
正如你所看到,Corpus枚举的第一个字段值等0,对于每个enum来说第一个字段值必须是0,因为:
- 这样可以用0表示默认值。
- 枚举的第一个值一般都是默认值。
你可以定义一个别名(相同的值,不同的字段名)。这样做需要开启别名开关(options allow_alias=true),如果没有开启,生成器发现别名时会报错。
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
枚举的字段值必须在int32范围内。当enum编码时,负值是低效的因此不推荐。定义enum可以在Message里,也可以在Message外。Message外的enum可以被多个Message使用。Message也可以使用别的Message定义的enum,句法是MessageType.EnumType
。
当你运行protocol buffer编译器的时候,enum会被生成为对应的C++或者java的enum类型,Python中会生成EnumDescriptor 类。
在反序列化时,未识别的enum值会被保留,不同语言有不同的处理。
在可以动态扩展enum的语言中,例如c++和go,这个未知的enum值会用int存储。在不可以动态扩展enum的语言中,例如java,可以通过特殊的方法获取。任何情况下,无法识别的enum值都会被序列化。
枚举中的保留值
如果你移除了enum类型中的字段,可能会被新的字段使用了移除字段的值。这样将会导致使用旧enum生成的服务器出现问题。可以通过 reserved
防止这种情况发生。编译器会警告如果使用了保留值。你可以通过max
表示一个值到最大值的区间。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
注意不能在一个reserved中同时包含字段名称和字段值。
使用另一个Message类型
可以使用另一个Message类型作为字段类型。例如包含Result的SearchResponse。在同一个proto文件中定义一个Result Message,然后指定SearchResponse字段类型为Result:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
导入定义 Importing Definitions
在上面的例子中Result和SearchResponse在同一个文件中,当想使用在其他proto文件中定义的Message时?可以通过导入方式使用别的文件中定义的Message。添加import语句在文件开头:
import "myproject/other_protos.proto";
默认情况下通过import proto文件可以使用定义好的Message。然而当你需要移动一个proto文件到其它地方时,不需要修改所有引用它的proto文件,只需要放入一个替身proto文件在原位置,替身文件中import public
原proto文件。import public
会传递定义,例如:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
protocol编译器会搜索通过命令行参数-I/--proto_path
指定的一组文件夹。如果没有指定文件夹,编译器会搜索当前目录。通常你需要设定--proto_path
指向你的项目目录,然后import时使用全路径。
使用proto2的Message类型
在proto3中import proto2 文件是可以的。然而proto2中的enum不能直接在proto3中使用。
嵌套类型
可以使用和定义Message在其他Message中。下面的例子中Result定义在SearchRespose中:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果你想使用这个Message在父Message外面,可以通过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;
}
}
}
更新Message类型
如果一个Message类型不符合你的现在的需求,例如你想扩展一个字段。但是你还在用旧的Message类型,不用担心!更新一个Message类型不会影响已经存在的代码,只要符合下面的规则:
- 不要修改任何已经存在的字段的标签值。
- 如果你扩展了新字段,新生成的代码对旧的Message依然可以解析成功。只要指定好新增字段的默认值,旧的Message就可以很好的被解析到新的Message类型。同样新的Message可以被旧的Message解析器很好的解析,新增的字段会被就Message解析器忽略。
- 字段可以被移除,只要保证标签值在以后不被使用。你可以重命名要移除的字段,例如加入前缀
OBSOLETE_
,或者通过reserved
标签值,这样就可以保证这个标签值以后不会被使用。 - int32,uint32,int64,uint64和bool都是兼容的 - 这意味着你可以修改这些的类型变成这里的其他类型(例如int32修改从bool),而不会破坏向前兼容性和向后兼容性。如果一个类型反序列化不符合对应类型,将会出现精度丢失,例如int64 转int32。
- sint32和sint64 是相互兼容的,但是与其他整数类型不兼容。
- string 和byte数组是相互兼容,如果编码是UTF8。
- 嵌套Message和byte数组兼容,如果byte数组中包含Message版本信息。
- fixed32 和 sfixed32 相互兼容, fixed64 和 sfixed64 相互兼容。
- enum和int32,uint32,int64,uint64兼容。然而客户端解析会稍有不同:例如未识别的enum值会被保留到Message中,但是反序列化不同语言有不同的处理方式。
未知字段
未知字段是protocol buffer序列化数据中不能被解析器识别的字段。例如,当一个旧的解析器解析新的Messag类型时,新添加的字段都会变成位置字段。
Proto3 实现的解析器可以对包含未知字段的Message解析。然后解析器实现可能保留未知字段也可能不保留未知字段。你不能确定未知字段是被保留或者删除。对于大多数protocol buffer实现中,未知字段不能被proto运行时访问,因为在反序列化的时候已经被删除。这个与proto2的行为不一致。
Any
Any
类型可以让你使用没有定义的类型。 Any
会被序列化成byte数组,同时会拥有一个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处于开发模式下
Currently the runtime libraries for working with Any types are under development.
网友评论