Language Guide (proto3)

本指南介紹如何使用協議緩衝區語言來構建協議緩衝區數據,包括.proto文件語法以及如何從.proto文件生成數據訪問類。
它涵蓋了協議緩衝區語言的proto3版本:有關舊的proto2語法的信息,請參閱Proto2語言指南

這是一個參考指南 - 對於使用本文檔中描述的許多功能的逐步示例,請參閱所選語言的教程(目前僅使用proto2;更多的proto3文檔即將推出)。

定義消息類型

首先我們來看一個非常簡單的例子。假設您要定義一個搜索請求消息格式,其中每個搜索請求都有一個查詢字符串,您感興趣的結果頁以及每頁返回結果數。
這是您用來定義消息類型的.proto文件。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 該文件的第一行指定您使用的是proto3語法:如果不這樣做,協議緩衝區編譯器將假定您正在使用proto2。這必須是文件的第一個非空,非註釋行。
  • SearchRequest消息定義指定三個字段(名/值對),一個此類型消息中要包含的每條數據。每個字段都有一個名字和一個類型。

指定字段類型

在上述示例中,所有字段都是scalar類型:兩個整數(page_numberresult_per_page)和一個字符串(查詢)。您也可以爲字段指定複合類型,包括枚舉和其他消息類型。

分配標籤

如你所見,消息定義中的每個字段都有唯一的編號標籤。這些標籤用於以消息二進制格式標識您的字段,一旦您的消息類型被使用該tag就不應該再更改。
請注意,值範圍爲1到15的標籤需要一個字節進行編碼,包括標識號和字段的類型(您可以在協議緩衝區編碼中找到更多信息)。16到2047範圍內的標籤需要兩個字節。
因此,您應該爲非常頻繁出現的消息元素預留標籤1到15。記住要爲將來可能添加的頻繁出現的元素預留空間。

您可以指定的最小標籤號碼是1,最大的標籤號碼是1,073,741,823或536,870,911。您也不能使用號碼19000到19999(FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber),因爲它們被保留用於協議緩衝區實現 -
如果您在.proto中使用這些保留號碼之一,則協議緩衝區編譯器會抱怨。同樣,您也不能使用任何以前Reserved的標籤。

指定字段規則

消息字段可以是以下之一:

  • 單數:一個格式正確的消息可以具有零個或一個這個的字段(但不超過一個)。
  • repeated:該字段可以在格式正確的消息中重複任意次數(包括零)。重複值的順序將被保留。

在proto3中,標量數字類型的repeated字段默認使用壓縮編碼。

您可以在協議緩衝區編碼中找到有關打包編碼的更多信息。

添加更多消息類型

多個消息類型可以在單個.proto文件中定義。如果您要定義多個相關的消息,這很有用 - 例如,如果要定義與您的SearchResponse消息類型相對應的回覆消息格式,則可以將其添加到相同的.proto文件:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

添加註釋

要向.proto文件添加註釋,請使用C/C++ 風格的//語法。

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的舊版本,這可能會導致嚴重的問題, 包括數據損壞,隱私錯誤等。
確保不會發生這種情況的一種方法是通過reserved指定已刪除的字段的字段標籤(或名稱, 這也可能導致JSON序列化的問題)被保留。如果將來的用戶嘗試使用這些字段標識符,協議緩衝區編譯器將會抱怨。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

請注意,您不能在相同的reserved語句中混合字段名稱和標籤號。

你的.proto產生了什麼?

當您在.proto上運行協議緩衝區編譯器時,編譯器將以您選擇的語言生成代碼,處理文件中描述的消息類型,包括獲取和設置字段值,將消息序列化爲輸出流,並從輸入流解析您的消息。

  • 對於Java,編譯器會爲每個消息類型生成一個包含類的.java文件,以及一個用於創建消息類實例的特殊Builder類。

您可以通過所選語言的教程(proto3版本即將推出),瞭解有關爲每種語言使用API​​的更多信息。有關更多API詳細信息,請參閱相關API參考(proto3版本即將推出)。

標量值類型

標量消息字段可以具有以下類型之一:該表顯示.proto文件中指定的類型以及自動生成的類中的相應類型:

.proto TypeNotesC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP Type
doubledoubledoublefloatfloat64Floatdoublefloat
floatfloatfloatfloatfloat32Floatfloatfloat
int32使用可變長度編碼。編碼負數的效率不高 - 如果你的字段要使用負數值,請改用sint32int32intintint32Fixnum or Bignum (as required)intinteger
int64使用可變長度編碼。編碼負數的效率不高 – 如果你的字段要使用負數值,請改用sint64int64longint/long[3]int64Bignumlonginteger/string[5]
uint32使用可變長度編碼。uint32int[1]int/long[3]uint32Fixnum or Bignum (as required)uintinteger
uint64使用可變長度編碼。uint64long[1]int/long[3]uint64Bignumulonginteger/string[5]
sint32使用可變長度編碼,這些在編碼負值時比int32高效的多int32intintint32Fixnum or Bignum (as required)intinteger
sint64使用可變長度編碼,有符號的整型值。編碼時比通常的int64高效int64longint/long[3]int64Bignumlonginteger/string[5]
fixed32總是4個字節,如果數值總是比228大的話,這個類型會比uint32高效uint32int[1]intuint32Fixnum or Bignum (as required)uintinteger
fixed64總是8個字節,如果數值總是比256大的話,這個類型會比uint64高效uint64long[1]int/long[3]uint64Bignumulonginteger/string[5]
sfixed32總是4個字節int32intintint32Fixnum or Bignum (as required)intinteger
sfixed64總是8個字節int64longint/long[3]int64Bignumlonginteger/string[5]
boolboolbooleanboolboolTrueClass/FalseClassboolboolean
string一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本stringStringstr/unicode[4]stringString (UTF-8)stringstring
bytes可能包含任意順序的字節數據stringByteStringstr[]byteString (ASCII-8BIT)ByteStringstring

您可以在協議緩衝區編碼中,瞭解序列化消息時更多關於這些類型的編碼方式。

默認值

當消息被解析時,如果編碼的消息不包含特定的單個元素,則解析對象中的相應字段將被設置爲該字段的默認值。這些默認值是特定於類型的:

  • 對於字符串,默認值爲空字符串。
  • 對於字節,默認值爲空字節。
  • 對於布爾,默認值爲false。
  • 對於數值類型,默認值爲零。
  • 對於枚舉,默認值是第一個定義的枚舉值,它必須爲0。
  • 對於消息字段,該字段不設置。其確切的值取決於語言。有關詳細信息,請參閱代碼生成指南

重複字段的默認值爲空(通常爲語言對應的空列表)。

請注意,對於標量消息字段,一旦消息被解析,就無法告知字​​段是否被明確地設置爲默認值(例如布爾值是否設置爲false),或者根本不設置:在定義消息類型時要牢記。
例如,如果您不希望默認情況下也會發生這種行爲,那麼在設置爲false時,不要使用布爾值來切換某些行爲。另請注意,如果標量消息字段設置爲默認值,該值將不會被序列化。

枚舉

當您定義消息類型時,您可能希望其中一個字段只有一個預定義的值列表。例如,假設您要爲每個SearchRequest添加語料庫字段,其中語料庫可以是UNIVERSALWEBIMAGESLOCALNEWSPRODUCTSVIDEO
您可以通過爲每個可能的值添加一個常量來爲消息定義添加一個枚舉。

在下面的示例中,我們添加了一個名爲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;
}

您可以看到,語料庫枚舉的第一個常量映射爲零:每個枚舉定義必須包含一個常量,將其定義爲零作爲其第一個元素。這是因爲:

  • 必須有一個零值,所以我們可以使用0作爲枚舉默認值。
  • 零值需要是第一個元素,用於與第一個枚舉值始終爲默認值的proto2語義兼容。

您可以通過爲不同的枚舉常量分配相同的值來定義別名。爲此,您需要將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.
}

枚舉器常量必須在32位整數的範圍內。由於枚舉值在線上使用varint編碼,所以負值無效,因此不推薦使用。
您可以在消息內部或外部定義枚舉,如上述示例 - 這些枚舉可以在您的.proto文件中的任何消息定義中重用。您還可以使用MessageType.EnumType語法在一個消息中聲明的枚舉類型作爲不同消息中的字段的類型。

當對一個使用了枚舉的.proto文件運行protocol buffer編譯器的時候,生成的代碼中將有一個對應的enum類(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),
它被用來在運行時生成的類中創建一系列的整型符號常量。

在反序列化的過程中,無法識別的枚舉值會被保存在消息中,雖然這種表示方式需要依據所使用語言而定。在那些支持開放枚舉類型超出指定範圍之外的語言中(例如C++和Go),未識別的值會被表示成所支持的整型。
在使用封閉枚舉類型的語言中(Java),使用枚舉中的特殊值來表示未識別的值,並且可以使用特殊訪問器訪問其基礎整數。在其他情況下,如果消息被序列化,則無法識別的值也仍將被序列化。

有關如何在應用程序中使用消息枚舉的更多信息,請參閱所選語言的代碼生成指南

使用其他消息類型

您可以使用其他消息類型作爲字段類型。例如,假設您想在每個SearchResponse消息中包含Result消息 - 爲此,您可以在同一個.proto中定義一個Result消息類型,然後在SearchResponse
指定一個Result類型的字段:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

導入定義

在上述示例中,Result消息類型與SearchResponse相同的文件中定義 - 如果要用作字段類型的消息類型已經在另一個.proto文件中定義了呢?

您可以導入來自其他.proto文件的定義。要導入另一個.proto的定義,您需要在文件的頂部添加一個import語句:

import "myproject/other_protos.proto";

默認情況下你只能使用直接導入的.proto文件中的定義. 然而, 有時候你需要移動一個老.proto文件到一個新的位置, 可以不直接移動老.proto文件,
只需在老的位置放入一個僞.proto文件, 然後使用import public轉向新的位置即可。import public依賴性會通過任意導入包含import public聲明的proto文件傳遞。例如:

// 這是new.proto
// 所有相關定義被移到這裏
// 這是old.proto
// 這裏聲明瞭所有客戶端正在導入的包
import public "new.proto";
import "other.proto";
// 這是client.proto
import "old.proto"; // 不需要改動old.proto導入站點
// 現在你可以使用old.proto和new.proto的定義,但不能使用other.proto的定義。

通過在編譯器命令行參數中使用-I/--proto_path標誌,編譯器會在指定目錄搜索要導入的文件。如果沒有給出標誌,編譯器會搜索編譯命令被調用的目錄。
通常你只要指定proto_path標誌爲你的工程根目錄並且對所有導入使用完全限定名稱就好。

使用proto2消息類型

可以導入proto2消息類型並在proto3消息中使用它們,反之亦然。
然而,proto2枚舉不能直接在proto3語法中使用(如果導入的proto2消息使用它們就可以)。

嵌套類型

您可以在其他消息類型中定義和使用消息類型,如以下示例所示:此處,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;
    }
  }
}

更新消息類型

如果一個已有的消息格式已無法滿足新的需求——如,要在消息中添加一個額外的字段——但是同時舊版本寫的代碼仍然可用。不用擔心!更新消息而不破壞已有代碼是非常簡單的。在更新時只要記住以下的規則即可。

  • 不要更改任何已有的字段的數值標識。
  • 如果你增加新的字段,使用舊格式的字段仍然可以被你新產生的代碼所解析。你應該記住這些元素的默認值這樣你的新代碼就可以以適當的方式和舊代碼產生的數據交互。相似的,通過新代碼產生的消息也可以被舊代碼解析:只不過新的字段會被忽視掉。注意,未被識別的字段會在反序列化的過程中丟棄掉,所以如果消息再被傳遞給新的代碼,新的字段依然是不可用的(這和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/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類型的動態(運行時)庫仍在開發之中

如果你已經很熟悉proto2語法,使用Any替換擴展

Oneof

如果你的消息中有很多可選字段, 並且同時至多一個字段會被設置, 你可以加強這個行爲,使用oneof特性節省內存.

Oneof字段就像可選字段, 除了它們會共享內存, 至多一個字段會被設置。 設置其中一個字段會清除其它字段。 你可以使用case()或者WhichOneof()方法檢查哪個oneof字段被設置,看你使用什麼語言了。

使用Oneof

爲了在.proto定義oneof字段, 你需要在名字前面加上oneof關鍵字, 比如下面例子的test_oneof:

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

然後你可以增加oneof字段到oneof定義中. 你可以增加任意類型的字段, 但是不能使用repeated關鍵字.

在產生的代碼中, oneof字段擁有同樣的getterssetters, 就像正常的可選字段一樣. 也有一個特殊的方法來檢查到底那個字段被設置. 你可以在相應的語言API指南中找到oneof API介紹.

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();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

向後兼容性問題

當增加或者刪除oneof字段時一定要小心. 如果檢查oneof的值返回None/NOT_SET, 它意味着oneof字段沒有被賦值或者在一個不同的版本中賦值了。
你不會知道是哪種情況,因爲沒有辦法判斷如果未識別的字段是一個oneof字段。

Tag重用問題:

  • 將字段移入或移除oneof:在消息被序列化或者解析後,你也許會失去一些信息(有些字段也許會被清除)。
  • 刪除一個字段或者加入一個字段:在消息被序列化或者解析後,這也許會清除你現在設置的oneof字段。
  • 拆分或合併oneof:行爲與移動常規字段相似。

Map

如果要創建一個關聯映射作爲數據定義的一部分,協議緩衝區提供了一個方便的快捷語法:

map<key_type, value_type> map_field = N;

其中key_type可以是任何整數或字符串類型(因此,除浮點類型和字節之外的任何標量類型)。value_type可以是任何類型。

例如,如果你希望創建一個project的映射,每個Project使用一個string作爲key,你可以像下面這樣定義:

map<string, Project> projects = 3;
  • Map的字段不能是repeated
  • 序列化後的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map。
  • 當爲.proto生成文本格式時,map按鍵排序。數字鍵按數字排序。
  • 從序列化中解析或者合併時,如果有重複的key則使用所看到的最後一個鍵。當從文本格式中解析map時,如果存在重複的key,解析將失敗。

向後兼容性

map語法序列化後等同於如下內容,因此即使是不支持map語法的protocol buffer實現也是可以處理你的數據的:

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

您可以向.proto文件添加可選的軟件包說明符,以防止協議消息類型之間的名稱衝突。

package foo.bar;
message Open { ... }

然後,您可以在定義消息類型的字段時使用包說明符:

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

包的聲明符會根據使用語言的不同影響生成的代碼:

  • 對於C++,產生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在foo::bar命名空間中。
  • 對於Java,包聲明符會變爲java的一個包,除非在.proto文件中提供了一個明確的option 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會在Foo.Bar命名空間中。

包及名稱的解析

Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內部開始查找,依次向外進行,每個包會被看作是其父類包的內部包。
當然對於(foo.bar.Baz)這樣以“.”分隔的意味着是從最外層的範圍開始。

ProtocolBuffer編譯器會解析.proto文件中定義的所有類型名。對於不同語言的代碼生成器會知道如何引用該語言中的每種類型,即使它們使用了不同的規則。

服務定義

如果想要將消息類型用在RPC(遠程方法調用)系統中,可以在.proto文件中定義一個RPC服務接口,protocol buffer編譯器將會根據所選擇的不同語言生成服務接口代碼及存根。如,想要定義一個RPC服務並具有一個方法,該方法能夠接收SearchRequest並返回一個SearchResponse,此時可以在.proto文件中進行如下定義:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

最直觀的使用protocol buffer的RPC系統是gRPC:在Google開發的一種語言和平臺中立的開源RPC系統。gRPC在使用protocl buffer時非常有效,如果使用特殊的protocol buffer插件可以直接爲您從.proto文件中產生相關的RPC代碼。

如果您不想使用gRPC,也可以使用協議緩衝區實現自己的RPC。您可以在proto2語言指南中瞭解更多信息。

還有一些第三方使用Protocol Buffer開發的PRC實現。參考第三方插件wiki查看這些實現的列表。

JSON映射

Proto3 支持JSON的編碼規範,使他更容易在不同系統之間共享數據,在下表中逐個描述類型。

如果JSON編碼數據中缺少值,或者如果其值爲空,這個數據會在解析成protocol buffer的時候被表示成默認值。如果一個字段在protocol buffer中被表示爲默認值,則會在轉化成JSON編碼的時候忽略掉以節省空間。

proto3JSONJSON exampleNotes
messageobject{"fBar": v, "g": null, …} 產生JSON對象,消息字段名可以被映射成lowerCamelCase形式,並且成爲JSON對象鍵,null被接受併成爲對應字段的默認值
enumstring"FOO_BAR"使用在proto中指定的枚舉值的名稱
map<K,V>object{"k": v, …}所有鍵都轉換爲字符串
repeated Varray[v, …]null被視爲空列表
booltrue, falsetrue, false
stringstring"Hello World!"
bytesbase64 string"YWJjMTIzIT8kKiYoKSctPUB+"
int32, fixed32, uint32number1, -10, 0JSON值會是一個十進制數,數值型或者string類型都會接受
int64, fixed64, uint64string"1", "-10"JSON值會是一個十進制數,數值型或者string類型都會接受
float, doublenumber1.1, -10.0, 0, "NaN", "Infinity"JSON值會是一個數字或者一個指定的字符串如”NaN”,”infinity”或者”-Infinity”,數值型或者字符串都是可接受的,指數符號也可以接受
Anyobject{"@type": "url", "f": v, … }如果Any包含一個具有特殊JSON映射的值,那麼它將被轉換如下:{“@type”:xxx,“value”:yyy}。否則,該值將被轉換爲JSON對象,並且將插入“@type”字段以指示實際的數據類型。
Timestampstring"1972-01-01T10:00:20.021Z"使用RFC 3339,其中生成的輸出將始終爲Z-normalized,並使用0,3,6或9個小數位數
Durationstring"1.000340012s", "1s"生成的輸出總是0,3,6或者9位小數,具體依賴於所需要的精度,接受所有可以轉換爲納秒級的精度
Structobject{ … }任何JSON對象。參見struct.proto.
Wrapper typesvarious types2, "2", "foo", true, "true", null, 0,包裝類型在JSON中使用與包裝的原始類型相同的表示形式,但在數據轉換和傳輸期間允許和保留null。
FieldMaskstring"f.fooBar,h"參見fieldmask.proto.
ListValuearray[foo, bar, …]
Valuevalue任何JSON值
NullValuenullJSON null值

選項

在定義.proto文件時能夠標註一系列的options。Options不改變聲明的整體含義,但可能會影響在特定上下文中的處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到。

一些選項是文件級選項,這意味着它們應該寫在頂級作用域中,而不是在任何消息,枚舉或服務定義內。一些選項是消息級選項,這意味着它們應該寫入消息定義內。
一些選項是字段級選項,這意味着它們應該寫在字段定義中。當然有些選項可以作用在域、enum類型、enum值、服務類型及服務方法中。到目前爲止,並沒有一種有效的選項能作用於所有的類型。

以下是一些常用的選項:

  • java_package(文件選項) :這個選項表明生成java類所在的包。如果在.proto文件中沒有明確的聲明java_package,就採用默認的包名。當然,默認方式產生的java包名並不是最好的方式,不會按照以反向域名開始。如果不選擇產生java代碼,則該選項將不起任何作用。如:
option java_package = "com.example.foo";
  • java_multiple_files(文件選項):使頂層消息,枚舉和服務可以在包級別上定義的,而不是在一個以Proto後綴命名外部類文件。(其實也就是生成多個java源文件,而不是在一個類中定義)
option java_multiple_files = true;
  • java_outer_classname(文件選項): 該選項表明想要生成Java類的名稱。如果在.proto文件中沒有明確的java_outer_classname定義,生成的class名稱將會根據.proto文件的名稱採用駝峯式的命名方式進行生成。如(foo_bar.proto生成的java類名爲FooBar.java),如果不選擇產生java代碼,則該選項不起任何作用。如:
option java_outer_classname = "Ponycopter";
  • optimize_for(文件選項): 可以被設置爲SPEED, CODE_SIZE,或者LITE_RUNTIME。這些值將通過如下的方式影響C++及java代碼的生成:

    • SPEED (default): protocol buffer編譯器將生成用於對消息類型進行序列化,解析和執行其他常見操作的代碼。這種代碼是最優的。
    • CODE_SIZE: protocol buffer編譯器將會產生最少量的類,通過共享或基於反射的代碼來實現序列化、語法分析及各種其它操作。採用該方式產生的代碼將比SPEED要少得多, 但是操作要相對慢些。當然實現的類及其對外的API與SPEED模式都是一樣的。這種方式經常用在一些包含大量的.proto文件而且並不盲目追求速度的應用中。
    • LITE_RUNTIME: protocol buffer編譯器依賴於運行時核心類庫來生成代碼(即採用libprotobuf-lite替代libprotobuf)。這種核心類庫由於忽略了一些描述符及反射,要比全類庫小得多。這種模式經常在移動手機平臺應用多一些。編譯器採用該模式產生的方法實現與SPEED模式不相上下,產生的類實現MessageLite接口,但它僅提供了Messager接口的一個子集。
option optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件選項): 對於C++產生的代碼啓用arena allocation
  • objc_class_prefix(文件選項): 將Objective-C類前綴應用在所有Objective-C生成的類和該.proto生成的枚舉。沒有默認值。您應該使用Apple推薦的3-5個大寫字母之間的前綴。請注意,所有2個字母的前綴都由Apple保留。
  • deprecated(字段選項): 如果設置爲true則表示該字段已經被廢棄,並且不應該在新的代碼中使用。在大多數語言中沒有實際的意義。在java中,這回變成@Deprecated註釋,在未來,其他語言的代碼生成器也許會在字標識符中產生廢棄註釋,廢棄註釋會在編譯器嘗試使用該字段時發出警告。如果字段沒有被使用你也不希望有新用戶使用它,嘗試使用保留語句替換字段聲明。
int32 old_field = 6 [deprecated=true];

自定義選項

ProtocolBuffers允許自定義並使用選項。該功能應該屬於一個高級特性,對於大部分人是用不到的。如果你的確希望創建自己的選項,請參看Proto2語言指南
注意創建自定義選項使用了擴展,擴展只在proto3中可用。

生成訪問類

可以通過定義好的.proto文件來生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代碼,需要基於.proto文件運行protocol buffer編譯器protoc。如果你沒有安裝編譯器,下載安裝包並遵照README安裝。對於Go,你還需要安裝一個特殊的代碼生成器插件。你可以通過GitHub上的golang/protobuf找到安裝過程。

協議編譯器的調用如下:

protoc --proto_path=IMPORT_PATH 
        --cpp_out=DST_DIR 
        --java_out=DST_DIR 
        --python_out=DST_DIR 
        --go_out=DST_DIR 
        --ruby_out=DST_DIR 
        --javanano_out=DST_DIR 
        --objc_out=DST_DIR 
        --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH指定在解析import指令時查找.proto文件的目錄。如果忽略該值,則使用當前目錄。可以多次傳遞--proto_path選項來指定多個導入目錄;,它們將會按順序被訪問並執行導入。-I=IMPORT_PATH--proto_path的簡化形式。

  • 當然也可以提供一個或多個輸出路徑:

    • --cpp_out 在目標目錄DST_DIR中產生C++代碼,可以在C++代碼生成參考中查看更多。
    • --java_out 在目標目錄DST_DIR中產生Java代碼,可以在Java代碼生成參考中查看更多。
    • --python_out 在目標目錄 DST_DIR 中產生Python代碼,可以在Python代碼生成參考中查看更多。
    • --go_out 在目標目錄 DST_DIR 中產生Go代碼,可以在GO代碼生成參考中查看更多。
    • --ruby_out 在目標目錄 DST_DIR 中產生Go代碼,參考正在製作中。
    • --javanano_out 在目標目錄DST_DIR中生成JavaNano,JavaNano代碼生成器有一系列的選項用於定製自定義生成器的輸出:你可以通過生成器的README查找更多信息,JavaNano參考正在製作中。
    • --objc_out 在目標目錄DST_DIR中產生Objective-C代碼,可以在Objective-C代碼生成參考中查看更多。
    • --csharp_out 在目標目錄DST_DIR中產生C#代碼,可以在C#代碼生成參考中查看更多。
    • --php_out 在目標目錄DST_DIR中產生PHP代碼,可以在PHP代碼生成參考中查看更多。

作爲一個方便的拓展,如果DST_DIR.zip或者.jar結尾,編譯器會將源碼輸出寫到一個ZIP格式文件或者符合JAR標準的.jar文件中。注意如果輸出已經存在則會被覆蓋,編譯器還沒有智能到可以追加文件。

  • 您必須提供一個或多個.proto文件作爲輸入。可以一次指定多個.proto文件。雖然這些文件相對於當前目錄命名,但每個文件必須位於其IMPORT_PATH下,以便編譯器可以確定其規範名稱。

參考鏈接: Language Guide (proto3)

發佈了103 篇原創文章 · 獲贊 19 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章