proto3 使用
定義一個消息類型
先來看一個非常簡單的例子。假設你想定義一個“搜尋請求”的消息格式,每一個請求含有一個查詢字元串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以采用如下的方式來定義消息類型的.proto檔案了:
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 檔案的第一行指定了你正在使用proto3文法:如果你沒有指定這個,編譯器會使用proto2。這個指定文法行必須是檔案的非空非注釋的第一個行。
- SearchRequest消息格式有3個字段,在消息中承載的資料分别對應于每一個字段。其中每個字段都有一個名字和一種類型。
指定字段類型
在上面的例子中,所有字段都是标量類型:兩個整型(page_number和result_per_page),一個string類型(query)。當然,你也可以為字段指定其他的合成類型,包括枚舉(enumerations)或其他消息類型。
配置設定辨別号
正如你所見,在消息定義中,每個字段都有唯一的一個數字辨別符。這些辨別符是用來在消息的二進制格式中識别各個字段的,一旦開始使用就不能夠再改變。注:[1,15]之内的辨別号在編碼的時候會占用一個位元組。[16,2047]之内的辨別号則占用2個位元組。是以應該為那些頻繁出現的消息元素保留 [1,15]之内的辨別号。切記:要為将來有可能添加的、頻繁出現的辨別号預留一些辨別号。
最小的辨別号可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (從FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的辨別号, Protobuf協定實作中對這些進行了預留。如果非要在.proto檔案中使用這些預留辨別号,編譯時就會報警。同樣你也不能使用早期保留的辨別号。
指定字段規則
所指定的消息字段修飾符必須是如下之一:
- singular:一個格式良好的消息應該有0個或者1個這種字段(但是不能超過1個)。
-
repeated:在一個格式良好的消息中,這種字段可以重複任意多次(包括0次)。重複的值的順序會被保留。
在proto3中,repeated的标量域預設情況蝦使用packed。
你可以了解更多的pakced屬性在Protocol Buffer 編碼
添加更多消息類型
在一個.proto檔案中可以定義多個消息類型。在定義多個相關的消息的時候,這一點特别有用——例如,如果想定義與SearchResponse消息類型對應的回複消息格式的話,你可以将它添加到相同的.proto檔案中,如:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加注釋
向.proto檔案添加注釋,可以使用C/C++/java風格的雙斜杠(//) 文法格式,如:
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.
}
保留辨別符(Reserved)
如果你通過删除或者注釋所有域,以後的使用者可以重用辨別号當你重新更新類型的時候。如果你使用舊版本加載相同的.proto檔案這會導緻嚴重的問題,包括資料損壞、隐私錯誤等等。現在有一種確定不會發生這種情況的方法就是指定保留辨別符(and/or names, which can also cause issues for JSON serialization不明白什麼意思),protocol buffer的編譯器會警告未來嘗試使用這些域辨別符的使用者。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注:不要在同一行reserved聲明中同時聲明域名字和辨別号
從.proto檔案生成了什麼?
當用protocol buffer編譯器來運作.proto檔案時,編譯器将生成所選擇語言的代碼,這些代碼可以操作在.proto檔案中定義的消息類型,包括擷取、設定字段值,将消息序列化到一個輸出流中,以及從一個輸入流中解析消息。
- 對C++來說,編譯器會為每個.proto檔案生成一個.h檔案和一個.cc檔案,.proto檔案中的每一個消息有一個對應的類。
- 對Java來說,編譯器為每一個消息類型生成了一個.java檔案,以及一個特殊的Builder類(該類是用來建立消息類接口的)。
- 對Python來說,有點不太一樣——Python編譯器為.proto檔案中的每個消息類型生成一個含有靜态描述符的子產品,,該子產品與一個元類(metaclass)在運作時(runtime)被用來建立所需的Python資料通路類。
- 對go來說,編譯器會位每個消息類型生成了一個.pd.go檔案。
- 對于Ruby來說,編譯器會為每個消息類型生成了一個.rb檔案。
- javaNano來說,編譯器輸出類似域java但是沒有Builder類
- 對于Objective-C來說,編譯器會為每個消息類型生成了一個pbobjc.h檔案和pbobjcm檔案,.proto檔案中的每一個消息有一個對應的類。
- 對于C#來說,編譯器會為每個消息類型生成了一個.cs檔案,.proto檔案中的每一個消息有一個對應的類。
你可以從如下的文檔連結中擷取每種語言更多API(proto3版本的内容很快就公布)。API Reference
标量數值類型
一個标量消息字段可以含有一個如下的類型——該表格展示了定義于.proto檔案中的類型,以及與之對應的、在自動生成的通路類中定義的類型:
.proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | |
float | float | float | float | float32 | Float | float | float | |
int32 | 使用變長編碼,對于負值的效率很低,如果你的域有可能有負值,請使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據需要) | int | integer |
uint32 | 使用變長編碼 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根據需要) | uint | integer |
uint64 | 使用變長編碼 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sint32 | 使用變長編碼,這些編碼在負值時比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據需要) | int | integer |
sint64 | 使用變長編碼,有符号的整型值。編碼時比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long | integer/string |
fixed32 | 總是4個位元組,如果數值總是比總是比228大的話,這個類型會比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根據需要) | uint | integer |
fixed64 | 總是8個位元組,如果數值總是比總是比256大的話,這個類型會比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sfixed32 | 總是4個位元組 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據需要) | int | integer |
sfixed64 | 總是8個位元組 | int64 | long | int/long | int64 | Bignum | long | integer/string |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
string | 一個字元串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 | string | String | str/unicode | string | String (UTF-8) | string | string |
bytes | 可能包含任意順序的位元組資料。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
你可以在文章Protocol Buffer 編碼中,找到更多“序列化消息時各種類型如何編碼”的資訊。
- 在java中,無符号32位和64位整型被表示成他們的整型對應形似,最高位被儲存在标志位中。
- 對于所有的情況,設定值會執行類型檢查以確定此值是有效。
- 64位或者無符号32位整型在解碼時被表示成為ilong,但是在設定時可以使用int型值設定,在所有的情況下,值必須符合其設定其類型的要求。
- python中string被表示成在解碼時表示成unicode。但是一個ASCIIstring可以被表示成str類型。
- Integer在64位的機器上使用,string在32位機器上使用
預設值
當一個消息被解析的時候,如果被編碼的資訊不包含一個特定的singular元素,被解析的對象鎖對應的域被設定位一個預設值,對于不同類型指定如下:
- 對于strings,預設是一個空string
- 對于bytes,預設是一個空的bytes
- 對于bools,預設是false
- 對于數值類型,預設是0
- 對于枚舉,預設是第一個定義的枚舉值,必須為0;
-
對于消息類型(message),域沒有被設定,确切的消息是根據語言确定的,詳見generated code guide
對于可重複域的預設值是空(通常情況下是對應語言中空清單)。
注:對于标量消息域,一旦消息被解析,就無法判斷域釋放被設定為預設值(例如,例如boolean值是否被設定為false)還是根本沒有被設定。你應該在定義你的消息類型時非常注意。例如,比如你不應該定義boolean的預設值false作為任何行為的觸發方式。也應該注意如果一個标量消息域被設定為标志位,這個值不應該被序列化傳輸。
檢視generated code guide選擇你的語言的預設值的工作細節。
枚舉
當需要定義一個消息類型的時候,可能想為一個字段指定某“預定義值序列”中的一個值。例如,假設要為每一個SearchRequest消息添加一個 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實作這一點:通過向消息定義中添加一個枚舉(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:每個枚舉類型必須将其第一個類型映射為0,這是因為:
- 必須有有一個0值,我們可以用這個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位整型值的範圍内。因為enum值是使用可變編碼方式的,對負數不夠高效,是以不推薦在enum中使用負數。如上例所示,可以在 一個消息定義的内部或外部定義枚舉——這些枚舉可以在.proto檔案中的任何消息定義裡重用。當然也可以在一個消息中聲明一個枚舉類型,而在另一個不同 的消息中使用它——采用MessageType.EnumType的文法格式。
當對一個使用了枚舉的.proto檔案運作protocol buffer編譯器的時候,生成的代碼中将有一個對應的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),它被用來在運作時生成的類中建立一系列的整型值符号常量(symbolic constants)。
在反序列化的過程中,無法識别的枚舉值會被儲存在消息中,雖然這種表示方式需要依據所使用語言而定。在那些支援開放枚舉類型超出指定範圍之外的語言中(例如C++和Go),為識别的值會被表示成所支援的整型。在使用封閉枚舉類型的語言中(Java),使用枚舉中的一個類型來表示未識别的值,并且可以使用所支援整型來通路。在其他情況下,如果解析的消息被序列号,未識别的值将保持原樣。
關于如何在你的應用程式的消息中使用枚舉的更多資訊,請檢視所選擇的語言generated code guide
使用其他消息類型
你可以将其他消息類型用作字段類型。例如,假設在每一個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消息内,如:
如果你想在它的父消息類型的外部重用這個消息類型,你需要以Parent.Type的形式使用它,如:message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; }
message SomeOtherMessage { SearchResponse.Result result = 1; }
更新一個消息類型
如果一個已有的消息格式已無法滿足新的需求——如,要在消息中添加一個額外的字段——但是同時舊版本寫的代碼仍然可用。不用擔心!更新消息而不破壞已有代碼是非常簡單的。在更新時隻要記住以下的規則即可。
- 不要更改任何已有的字段的數值辨別。
- 如果你增加新的字段,使用舊格式的字段仍然可以被你新産生的代碼所解析。你應該記住這些元素的預設值這樣你的新代碼就可以以适當的方式和舊代碼産生的資料互動。相似的,通過新代碼産生的消息也可以被舊代碼解析:隻不過新的字段會被忽視掉。注意,未被識别的字段會在反序列化的過程中丢棄掉,是以如果消息再被傳遞給新的代碼,新的字段依然是不可用的(這和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字段擁有同樣的 getters 和setters, 就像正常的可選字段一樣. 也有一個特殊的方法來檢查到底那個字段被設定. 你可以在相應的語言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++中,如果你使用
兩個oneof消息,每個消息,兩個消息将擁有對方的值,例如在下面的例子中,Swap()
會擁有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 重用問題:
- 将字段移入或移除oneof:在消息被序列号或者解析後,你也許會失去一些資訊(有些字段也許會被清除)
- 删除一個字段或者加入一個字段:在消息被序列号或者解析後,這也許會清除你現在設定的oneof字段
- 分離或者融合oneof:行為與移動正常字段相似。
Map(映射)
如果你希望建立一個關聯映射,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指南找到更多資訊。
向後相容性問題
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;
...
}
包的聲明符會根據使用語言的不同影響生成的代碼。
- 對于C++,産生的類會被包裝在C++的命名空間中,如上例中的
會被封裝在Open
空間中; - 對于Java,包聲明符會變為java的一個包,除非在.proto檔案中提供了一個明确有foo::bar
;java_package
- 對于 Python,這個包聲明符是被忽略的,因為Python子產品是按照其在檔案系統中的位置進行組織的。
- 對于Go,包可以被用做Go包名稱,除非你顯式的提供一個
在你的.proto檔案中。option go_package
- 對于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檔案中定義的所有類型名。 對于不同語言的代碼生成器會知道如何來指向每個具體的類型,即使它們使用了不同的規則。
定義服務(Service)
如果想要将消息類型用在RPC(遠端方法調用)系統中,可以在.proto檔案中定義一個RPC服務接口,protocol buffer編譯器将會根據所選擇的不同語言生成服務接口代碼及存根。如,想要定義一個RPC服務并具有一個方法,該方法能夠接收 SearchRequest并傳回一個SearchResponse,此時可以在.proto檔案中進行如下定義:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
最直覺的使用protocol buffer的RPC系統是gRPC一個由谷歌開發的語言和平台中的開源的PRC系統,gRPC在使用protocl buffer時非常有效,如果使用特殊的protocol buffer插件可以直接為您從.proto檔案中産生相關的RPC代碼。
如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC實作,你可以從proto2語言指南中找到更多資訊
還有一些第三方開發的PRC實作使用Protocol Buffer。參考第三方插件wiki檢視這些實作的清單。
JSON 映射
Proto3 支援JSON的編碼規範,使他更容易在不同系統之間共享資料,在下表中逐個描述類型。
如果JSON編碼的資料丢失或者其本身就是
null
,這個資料會在解析成protocol buffer的時候被表示成預設值。如果一個字段在protocol buffer中表示為預設值,體會在轉化成JSON的時候編碼的時候忽略掉以節省空間。具體實作可以提供在JSON編碼中可選的預設值。
proto3 | JSON | JSON示例 | 注意 |
---|---|---|---|
message | object | {“fBar”: v, “g”: null, …} | 産生JSON對象,消息字段名可以被映射成lowerCamelCase形式,并且成為JSON對象鍵,null被接受并成為對應字段的預設值 |
enum | string | “FOO_BAR” | 枚舉值的名字在proto檔案中被指定 |
map | object | {“k”: v, …} | 所有的鍵都被轉換成string |
repeated V | array | [v, …] | null被視為空清單 |
bool | true, false | true, false | |
string | string | “Hello World!” | |
bytes | base64 string | “YWJjMTIzIT8kKiYoKSctPUB+” | |
int32, fixed32, uint32 | number | 1, -10, 0 | JSON值會是一個十進制數,數值型或者string類型都會接受 |
int64, fixed64, uint64 | string | “1”, “-10” | JSON值會是一個十進制數,數值型或者string類型都會接受 |
float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON值會是一個數字或者一個指定的字元串如”NaN”,”infinity”或者”-Infinity”,數值型或者字元串都是可接受的,指數符号也可以接受 |
Any | object | {“@type”: “url”, “f”: v, … } | 如果一個Any保留一個特上述的JSON映射,則它會轉換成一個如下形式: 否則,該值會被轉換成一個JSON對象, 字段會被插入所指定的确定的值 |
Timestamp | string | “1972-01-01T10:00:20.021Z” | 使用RFC 339,其中生成的輸出将始終是Z-歸一化啊的,并且使用0,3,6或者9位小數 |
Duration | string | “1.000340012s”, “1s” | 生成的輸出總是0,3,6或者9位小數,具體依賴于所需要的精度,接受所有可以轉換為納秒級的精度 |
Struct | object | { … } | 任意的JSON對象,見struct.proto |
Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | 包裝器在JSON中的表示方式類似于基本類型,但是允許nulll,并且在轉換的過程中保留null |
FieldMask | string | “f.fooBar,h” | 見fieldmask.proto |
ListValue | array | [foo, bar, …] | |
Value | value | 任意JSON值 | |
NullValue | null | JSON null |
選項
在定義.proto檔案時能夠标注一系列的options。Options并不改變整個檔案聲明的含義,但卻能夠影響特定環境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到。
一些選項是檔案級别的,意味着它可以作用于最外範圍,不包含在任何消息内部、enum或服務定義中。一些選項是消息級别的,意味着它可以用在消息定義的内部。當然有些選項可以作用在域、enum類型、enum值、服務類型及服務方法中。到目前為止,并沒有一種有效的選項能作用于所有的類型。
如下就是一些常用的選擇:
-
(檔案選項) :這個選項表明生成java類所在的包。如果在.proto檔案中沒有明确的聲明java_package,就采用預設的包名。當然了,預設方式産生的 java包名并不是最好的方式,按照應用名稱倒序方式進行排序的。如果不需要産生java代碼,則該選項将不起任何作用。如:java_package
option java_package = "com.example.foo";
-
(檔案選項): 該選項表明想要生成Java類的名稱。如果在.proto檔案中沒有明确的java_outer_classname定義,生成的class名稱将會根據.proto檔案的名稱采用駝峰式的命名方式進行生成。如(foo_bar.proto生成的java類名為FooBar.java),如果不生成java代碼,則該選項不起任何作用。如:java_outer_classname
option java_outer_classname = "Ponycopter";
optimize_for
(檔案選項): 可以被設定為 SPEED, CODE_SIZE,或者LITE_RUNTIME。這些值将通過如下的方式影響C++及java代碼的生成:
-
-
: protocol buffer編譯器将通過在消息類型上執行序列化、文法分析及其他通用的操作。這種代碼是最優的。SPEED (default)
-
: protocol buffer編譯器将會産生最少量的類,通過共享或基于反射的代碼來實作序列化、文法分析及各種其它操作。采用該方式産生的代碼将比SPEED要少得多, 但是操作要相對慢些。當然實作的類及其對外的API與SPEED模式都是一樣的。這種方式經常用在一些包含大量的.proto檔案而且并不盲目追求速度的 應用中。CODE_SIZE
-
: protocol buffer編譯器依賴于運作時核心類庫來生成代碼(即采用libprotobuf-lite 替代libprotobuf)。這種核心類庫由于忽略了一 些描述符及反射,要比全類庫小得多。這種模式經常在移動手機平台應用多一些。編譯器采用該模式産生的方法實作與SPEED模式不相上下,産生的類通過實作 MessageLite接口,但它僅僅是Messager接口的一個子集。LITE_RUNTIME
-
option optimize_for = CODE_SIZE;
-
(檔案選項):對于C++産生的代碼啟用arena allocationcc_enable_arenas
-
(檔案選項):設定Objective-C類的字首,添加到所有Objective-C從此.proto檔案産生的類和枚舉類型。沒有預設值,所使用的字首應該是蘋果推薦的3-5個大寫字元,注意2個位元組的字首是蘋果所保留的。objc_class_prefix
-
(字段選項):如果設定為deprecated
則表示該字段已經被廢棄,并且不應該在新的代碼中使用。在大多數語言中沒有實際的意義。在java中,這回變成true
注釋,在未來,其他語言的代碼生成器也許會在字辨別符中産生廢棄注釋,廢棄注釋會在編譯器嘗試使用該字段時發出警告。如果字段沒有被使用你也不希望有新使用者使用它,嘗試使用保留語句替換字段聲明。@Deprecated
int32 old_field = 6 [deprecated=true];
自定義選項
ProtocolBuffers允許自定義并使用選項。該功能應該屬于一個進階特性,對于大部分人是用不到的。如果你的确希望建立自己的選項,請參看 Proto2 Language Guide。注意建立自定義選項使用了拓展,拓展隻在proto3中可用。