protocol buffers(二)

这篇文章翻译自https://developers.google.com/protocol-buffers/

默认值

当一个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 network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

目前Any处于开发模式下
Currently the runtime libraries for working with Any types are under development.

你可能感兴趣的:(protocol buffers(二))