protobuf3 自定义option_Protobuf3 语法指南
以前我翻譯了?Protobuf2 語法指南,現(xiàn)在?千念飛羽把protobuf3的語法指南也翻譯了,我也轉(zhuǎn)載一下,讀者可以有個參考。 譯文地址是:?Protobuf3語言指南。
英文原文:
Language Guide (proto3)
中文出處:
Protobuf語言指南
[譯]Protobuf 語法指南
中文出處是proto2的譯文,proto3的英文出現(xiàn)后在原來基礎(chǔ)上增改了,水平有限,還請指正
這個指南描述了如何使用Protocol buffer 語言去描述你的protocol buffer 數(shù)據(jù), 包括 .proto文件符號和如何從.proto文件生成類。包含了proto2版本的protocol buffer語言:對于老版本的proto3 符號,請見Proto2 Language Guide(以及中文譯本,抄了很多這里的感謝下老版本的翻譯者)
本文是一個參考指南——如果要查看如何使用本文中描述的多個特性的循序漸進(jìn)的例子,請在教程中查找需要的語言的教程。
定義一個消息類型
先來看一個非常簡單的例子。假設(shè)你想定義一個“搜索請求”的消息格式,每一個請求含有一個查詢字符串、你感興趣的查詢結(jié)果所在的頁數(shù),以及每一頁多少條查詢結(jié)果。可以采用如下的方式來定義消息類型的.proto文件了:
1
2
3
4
5
6
7
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
文件的第一行指定了你正在使用proto3語法:如果你沒有指定這個,編譯器會使用proto2。這個指定語法行必須是文件的非空非注釋的第一個行。
SearchRequest消息格式有3個字段,在消息中承載的數(shù)據(jù)分別對應(yīng)于每一個字段。其中每個字段都有一個名字和一種類型。
指定字段類型
在上面的例子中,所有字段都是標(biāo)量類型:兩個整型(page_number和result_per_page),一個string類型(query)。當(dāng)然,你也可以為字段指定其他的合成類型,包括枚舉(enumerations)或其他消息類型。
分配標(biāo)識號
正如你所見,在消息定義中,每個字段都有唯一的一個數(shù)字標(biāo)識符。這些標(biāo)識符是用來在消息的二進(jìn)制格式中識別各個字段的,一旦開始使用就不能夠再改變。注:[1,15]之內(nèi)的標(biāo)識號在編碼的時候會占用一個字節(jié)。[16,2047]之內(nèi)的標(biāo)識號則占用2個字節(jié)。所以應(yīng)該為那些頻繁出現(xiàn)的消息元素保留 [1,15]之內(nèi)的標(biāo)識號。切記:要為將來有可能添加的、頻繁出現(xiàn)的標(biāo)識號預(yù)留一些標(biāo)識號。
最小的標(biāo)識號可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (從FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的標(biāo)識號, Protobuf協(xié)議實現(xiàn)中對這些進(jìn)行了預(yù)留。如果非要在.proto文件中使用這些預(yù)留標(biāo)識號,編譯時就會報警。同樣你也不能使用早期保留的標(biāo)識號。
指定字段規(guī)則
所指定的消息字段修飾符必須是如下之一:
singular:一個格式良好的消息應(yīng)該有0個或者1個這種字段(但是不能超過1個)。
repeated:在一個格式良好的消息中,這種字段可以重復(fù)任意多次(包括0次)。重復(fù)的值的順序會被保留。
在proto3中,repeated的標(biāo)量域默認(rèn)情況蝦使用packed。
添加更多消息類型
在一個.proto文件中可以定義多個消息類型。在定義多個相關(guān)的消息的時候,這一點特別有用——例如,如果想定義與SearchResponse消息類型對應(yīng)的回復(fù)消息格式的話,你可以將它添加到相同的.proto文件中,如:
1
2
3
4
5
6
7
8
9
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加注釋
向.proto文件添加注釋,可以使用C/C++/Java風(fēng)格的雙斜杠(//) 語法格式,如:
1
2
3
4
5
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.
}
保留標(biāo)識符(Reserved)
如果你通過刪除或者注釋所有域,以后的用戶在更新這個類型的時候可能重用這些標(biāo)識號。如果你使用舊版本加載相同的.proto文件會導(dǎo)致嚴(yán)重的問題,包括數(shù)據(jù)損壞、隱私錯誤等等。現(xiàn)在有一種確保不會發(fā)生這種情況的方法就是為字段tag(reserved name可能會JSON序列化的問題)指定reserved標(biāo)識符,protocol buffer的編譯器會警告未來嘗試使用這些域標(biāo)識符的用戶。
1
2
3
4
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注:不要在同一行reserved聲明中同時聲明域名字和tag number。
從.proto文件生成了什么?
當(dāng)用protocol buffer編譯器來運(yùn)行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼可以操作在.proto文件中定義的消息類型,包括獲取、設(shè)置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。
對C++來說,編譯器會為每個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每一個消息有一個對應(yīng)的類。
對Java來說,編譯器為每一個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來創(chuàng)建消息類接口的)。
對Python來說,有點不太一樣——Python編譯器為.proto文件中的每個消息類型生成一個含有靜態(tài)描述符的模塊,,該模塊與一個元類(metaclass)在運(yùn)行時(runtime)被用來創(chuàng)建所需的Python數(shù)據(jù)訪問類。
對go來說,編譯器會位每個消息類型生成了一個.pd.go文件。
對于Ruby來說,編譯器會為每個消息類型生成了一個.rb文件。
javaNano來說,編譯器輸出類似域java但是沒有Builder類
對于Objective-C來說,編譯器會為每個消息類型生成了一個pbobjc.h文件和pbobjcm文件,.proto文件中的每一個消息有一個對應(yīng)的類。
對于C#來說,編譯器會為每個消息類型生成了一個.cs文件,.proto文件中的每一個消息有一個對應(yīng)的類。
你可以從如下的文檔鏈接中獲取每種語言更多API(proto3版本的內(nèi)容很快就公布)。API Reference
標(biāo)量數(shù)值類型
一個標(biāo)量消息字段可以含有一個如下的類型——該表格展示了定義于.proto文件中的類型,以及與之對應(yīng)的、在自動生成的訪問類中定義的類型:
.proto TypeNotesC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP Type
double
double
double
float
float64
Float
double
float
float
float
float
float
float32
Float
float
float
int32
使用變長編碼,對于負(fù)值的效率很低,如果你的域有可能有負(fù)值,請使用sint64替代
int32
int
int
int32
Fixnum 或者 Bignum(根據(jù)需要)
int
integer
uint32
使用變長編碼
uint32
int
int/long
uint32
Fixnum 或者 Bignum(根據(jù)需要)
uint
integer
uint64
使用變長編碼
uint64
long
int/long
uint64
Bignum
ulong
integer/string
sint32
使用變長編碼,這些編碼在負(fù)值時比int32高效的多
int32
int
int
int32
Fixnum 或者 Bignum(根據(jù)需要)
int
integer
sint64
使用變長編碼,有符號的整型值。編碼時比通常的int64高效。
int64
long
int/long
int64
Bignum
long
integer/string
fixed32
總是4個字節(jié),如果數(shù)值總是比總是比228大的話,這個類型會比uint32高效。
uint32
int
int
uint32
Fixnum 或者 Bignum(根據(jù)需要)
uint
integer
fixed64
總是8個字節(jié),如果數(shù)值總是比總是比256大的話,這個類型會比uint64高效。
uint64
long
int/long
uint64
Bignum
ulong
integer/string
sfixed32
總是4個字節(jié)
int32
int
int
int32
Fixnum 或者 Bignum(根據(jù)需要)
int
integer
sfixed64
總是8個字節(jié)
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
可能包含任意順序的字節(jié)數(shù)據(jù)。
string
ByteString
str
[]byte
String (ASCII-8BIT)
ByteString
string
你可以在文章Protocol Buffer 編碼中,找到更多“序列化消息時各種類型如何編碼”的信息。
在java中,無符號32位和64位整型被表示成他們的整型對應(yīng)形式,最高位被儲存在標(biāo)志位中。
對于所有的情況,設(shè)定值會執(zhí)行類型檢查以確保此值是有效。
64位或者無符號32位整型在解碼時被表示成為ilong,但是在設(shè)置時可以使用int型值設(shè)定,在所有的情況下,值必須符合其設(shè)置其類型的要求。
python中string被表示成在解碼時表示成unicode。但是一個ASCIIstring可以被表示成str類型。
Integer在64位的機(jī)器上使用,string在32位機(jī)器上使用
默認(rèn)值
當(dāng)一個消息被解析的時候,如果被編碼的信息不包含一個特定的singular元素,被解析的對象鎖對應(yīng)的域被設(shè)置位一個默認(rèn)值,對于不同類型指定如下:
對于string,默認(rèn)是一個空string
對于bytes,默認(rèn)是一個空的bytes
對于bool,默認(rèn)是false
對于數(shù)值類型,默認(rèn)是0
對于枚舉,默認(rèn)是第一個定義的枚舉值,必須為0;
對于消息類型(message),域沒有被設(shè)置,確切的消息是根據(jù)語言確定的,詳見generated code guide
對于可重復(fù)域的默認(rèn)值是空(通常情況下是對應(yīng)語言中空列表)。
注:對于標(biāo)量消息域,一旦消息被解析,就無法判斷域釋放被設(shè)置為默認(rèn)值(例如,例如boolean值是否被設(shè)置為false)還是根本沒有被設(shè)置。你應(yīng)該在定義你的消息類型時非常注意。例如,比如你不應(yīng)該定義boolean的默認(rèn)值false作為任何行為的觸發(fā)方式。也應(yīng)該注意如果一個標(biāo)量消息域被設(shè)置為標(biāo)志位,這個值不應(yīng)該被序列化傳輸。
枚舉
當(dāng)需要定義一個消息類型的時候,可能想為一個字段指定某“預(yù)定義值序列”中的一個值。例如,假設(shè)要為每一個SearchRequest消息添加一個 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實現(xiàn)這一點:通過向消息定義中添加一個枚舉(enum)并且為每個可能的值定義一個常量就可以了。
在下面的例子中,在消息格式中添加了一個叫做Corpus的枚舉類型——它含有所有可能的值 ——以及一個類型為Corpus的字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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值作為默認(rèn)值。
這個零值必須為第一個元素,為了兼容proto2語義,枚舉類的第一個值總是默認(rèn)值。
你可以通過將不同的枚舉常量指定位相同的值。如果這樣做你需要將allow_alias設(shè)定位true,否則編譯器會在別名的地方產(chǎn)生一個錯誤信息。
1
2
3
4
5
6
7
8
9
10
11
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位整型值的范圍內(nèi)。因為enum值是使用可變編碼方式的,對負(fù)數(shù)不夠高效,因此不推薦在enum中使用負(fù)數(shù)。如上例所示,可以在 一個消息定義的內(nèi)部或外部定義枚舉——這些枚舉可以在.proto文件中的任何消息定義里重用。當(dāng)然也可以在一個消息中聲明一個枚舉類型,而在另一個不同 的消息中使用它——采用MessageType.EnumType的語法格式。
當(dāng)對一個使用了枚舉的.proto文件運(yùn)行protocol buffer編譯器的時候,生成的代碼中將有一個對應(yīng)的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),它被用來在運(yùn)行時生成的類中創(chuàng)建一系列的整型值符號常量(symbolic constants)。
在反序列化的過程中,無法識別的枚舉值會被保存在消息中,雖然這種表示方式需要依據(jù)所使用語言而定。在那些支持開放枚舉類型超出指定范圍之外的語言中(例如C++和Go),為識別的值會被表示成所支持的整型。在使用封閉枚舉類型的語言中(Java),使用枚舉中的一個類型來表示未識別的值,并且可以使用所支持整型來訪問。在其他情況下,如果解析的消息被序列號,未識別的值將保持原樣。
關(guān)于如何在你的應(yīng)用程序的消息中使用枚舉的更多信息,請查看所選擇的語言generated code guide。
使用其他消息類型
你可以將其他消息類型用作字段類型。例如,假設(shè)在每一個SearchResponse消息中包含Result消息,此時可以在相同的.proto文件中定義一個Result消息類型,然后在SearchResponse消息中指定一個Result類型的字段,如:
1
2
3
4
5
6
7
8
9
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
導(dǎo)入定義
在上面的例子中,Result消息類型與SearchResponse是定義在同一文件中的。如果想要使用的消息類型已經(jīng)在其他.proto文件中已經(jīng)定義過了呢?
你可以通過導(dǎo)入(importing)其他.proto文件中的定義來使用它們。要導(dǎo)入其他.proto文件的定義,你需要在你的文件中添加一個導(dǎo)入聲明,如:
1
import "myproject/other_protos.proto";
默認(rèn)情況下你只能使用直接導(dǎo)入的.proto文件中的定義. 然而, 有時候你需要移動一個.proto文件到一個新的位置, 可以不直接移動.proto文件, 只需放入一個偽 .proto 文件在老的位置, 然后使用import public轉(zhuǎn)向新的位置。import public 依賴性會通過任意導(dǎo)入包含import public聲明的proto文件傳遞。例如:
1
2
// 這是新的proto
// All definitions are moved here
1
2
3
4
// 這是久的proto
// 這是所有客戶端正在導(dǎo)入的包
import public "new.proto";
import "other.proto";
1
2
3
// 客戶端proto
import "old.proto";
// 現(xiàn)在你可以使用新舊兩種包的proto定義了。
通過在編譯器命令行參數(shù)中使用-I/--proto_pathprotocal 編譯器會在指定目錄搜索要導(dǎo)入的文件。如果沒有給出標(biāo)志,編譯器會搜索編譯命令被調(diào)用的目錄。通常你只要指定proto_path標(biāo)志為你的工程根目錄就好。并且指定好導(dǎo)入的正確名稱就好。
使用proto2消息類型
在你的proto3消息中導(dǎo)入proto2的消息類型也是可以的,反之亦然,然后proto2枚舉不可以直接在proto3的標(biāo)識符中使用(如果僅僅在proto2消息中使用是可以的)。
嵌套類型
你可以在其他消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內(nèi),如:
1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果你想在它的父消息類型的外部重用這個消息類型,你需要以Parent.Type的形式使用它,如:
1
2
3
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
當(dāng)然,你也可以將消息嵌套任意多層,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
}
更新一個消息類型
如果一個已有的消息格式已無法滿足新的需求——如,要在消息中添加一個額外的字段——但是同時舊版本寫的代碼仍然可用。不用擔(dān)心!更新消息而不破壞已有代碼是非常簡單的。在更新時只要記住以下的規(guī)則即可。
不要更改任何已有的字段的數(shù)值標(biāo)識。
如果你增加新的字段,使用舊格式的字段仍然可以被你新產(chǎn)生的代碼所解析。你應(yīng)該記住這些元素的默認(rèn)值這樣你的新代碼就可以以適當(dāng)?shù)姆绞胶团f代碼產(chǎn)生的數(shù)據(jù)交互。相似的,通過新代碼產(chǎn)生的消息也可以被舊代碼解析:只不過新的字段會被忽視掉。注意,未被識別的字段會在反序列化的過程中丟棄掉,所以如果消息再被傳遞給新的代碼,新的字段依然是不可用的(這和proto2中的行為是不同的,在proto2中未定義的域依然會隨著消息被序列化)
非required的字段可以移除——只要它們的標(biāo)識號在新的消息類型中不再使用(更好的做法可能是重命名那個字段,例如在字段前添加“OBSOLETE_”前綴,那樣的話,使用的.proto文件的用戶將來就不會無意中重新使用了那些不該使用的標(biāo)識號)。
int32, uint32, int64, uint64,和bool是全部兼容的,這意味著可以將這些類型中的一個轉(zhuǎn)換為另外一個,而不會破壞向前、 向后的兼容性。如果解析出來的數(shù)字與對應(yīng)的類型不相符,那么結(jié)果就像在C++中對它進(jìn)行了強(qiáng)制類型轉(zhuǎn)換一樣(例如,如果把一個64位數(shù)字當(dāng)作int32來 讀取,那么它就會被截斷為32位的數(shù)字)。
sint32和sint64是互相兼容的,但是它們與其他整數(shù)類型不兼容。
string和bytes是兼容的——只要bytes是有效的UTF-8編碼。
嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本。
fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的。
枚舉類型與int32,uint32,int64和uint64相兼容(注意如果值不相兼容則會被截斷),然而在客戶端反序列化之后他們可能會有不同的處理方式,例如,未識別的proto3枚舉類型會被保留在消息中,但是他的表示方式會依照語言而定。int類型的字段總會保留他們的
Any
Any類型消息允許你在沒有指定他們的.proto定義的情況下使用消息作為一個嵌套類型。一個Any類型包括一個可以被序列化bytes類型的任意消息,以及一個URL作為一個全局標(biāo)識符和解析消息類型。為了使用Any類型,你需要導(dǎo)入import google/protobuf/any.proto。
1
2
3
4
5
6
import "google/protobuf/any.proto";
message ErrorStatus {
string message =1;
repeated google.protobuf.Any details =2;
}
對于給定的消息類型的默認(rèn)類型URL是type.googleapis.com/packagename.messagename。
不同語言的實現(xiàn)會支持動態(tài)庫以線程安全的方式去幫助封裝或者解封裝Any值。例如在java中,Any類型會有特殊的pack()和unpack()訪問器,在C++中會有PackFrom()和UnpackTo()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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類型的動態(tài)庫仍在開發(fā)之中
如果你已經(jīng)很熟悉proto2語法,使用Any替換擴(kuò)展。
Oneof
如果你的消息中有很多可選字段, 并且同時至多一個字段會被設(shè)置, 你可以加強(qiáng)這個行為,使用oneof特性節(jié)省內(nèi)存.
Oneof字段就像可選字段, 除了它們會共享內(nèi)存, 至多一個字段會被設(shè)置。 設(shè)置其中一個字段會清除其它字段。 你可以使用case()或者WhichOneof()?方法檢查哪個oneof字段被設(shè)置, 看你使用什么語言了.
使用Oneof
為了在.proto定義Oneof字段, 你需要在名字前面加上oneof關(guān)鍵字, 比如下面例子的test_oneof:
1
2
3
4
5
6
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后你可以增加oneof字段到 oneof 定義中. 你可以增加任意類型的字段, 但是不能使用repeated 關(guān)鍵字.
在產(chǎn)生的代碼中, oneof字段擁有同樣的 getters 和setters, 就像正常的可選字段一樣. 也有一個特殊的方法來檢查到底那個字段被設(shè)置. 你可以在相應(yīng)的語言API指南中找到oneof API介紹.
Oneof 特性
設(shè)置oneof會自動清楚其它oneof字段的值. 所以設(shè)置多次后,只有最后一次設(shè)置的字段有值.
1
2
3
4
5
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++,需確保代碼不會導(dǎo)致內(nèi)存泄漏. 下面的代碼會崩潰, 因為sub_message 已經(jīng)通過set_name()刪除了
1
2
3
4
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。
1
2
3
4
5
6
7
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后兼容性問題
當(dāng)增加或者刪除oneof字段時一定要小心. 如果檢查oneof的值返回None/NOT_SET, 它意味著oneof字段沒有被賦值或者在一個不同的版本中賦值了。 你不會知道是哪種情況,因為沒有辦法判斷如果未識別的字段是一個oneof字段。
Tag 重用問題:
將字段移入或移除oneof:在消息被序列號或者解析后,你也許會失去一些信息(有些字段也許會被清除)
刪除一個字段或者加入一個字段:在消息被序列號或者解析后,這也許會清除你現(xiàn)在設(shè)置的oneof字段
分離或者融合oneof:行為與移動常規(guī)字段相似。
Map
如果你希望創(chuàng)建一個關(guān)聯(lián)映射,protocol buffer提供了一種快捷的語法:
1
map map_field = N;
其中key_type可以是任意Integer或者string類型(所以,除了floating和bytes的任意標(biāo)量類型都是可以的)value_type可以是任意類型。
例如,如果你希望創(chuàng)建一個project的映射,每個Projecct使用一個string作為key,你可以像下面這樣定義:
1
map projects = 3;
Map的字段可以是repeated。
序列化后的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map
當(dāng)為.proto文件產(chǎn)生生成文本格式的時候,map會按照key 的順序排序,數(shù)值化的key會按照數(shù)值排序。
從序列化中解析或者融合時,如果有重復(fù)的key則后一個key不會被使用,當(dāng)從文本格式中解析map時,如果存在重復(fù)的key。
生成map的API現(xiàn)在對于所有proto3支持的語言都可用了,你可以從API指南找到更多信息。
向后兼容性問題
map語法序列化后等同于如下內(nèi)容,因此即使是不支持map語法的protocol buffer實現(xiàn)也是可以處理你的數(shù)據(jù)的:
1
2
3
4
5
6
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Package
當(dāng)然可以為.proto文件新增一個可選的package聲明符,用來防止不同的消息類型有命名沖突。如:
1
2
package foo.bar;
message Open { ... }
在其他的消息格式定義中可以使用包名+消息名的方式來定義域的類型,如:
1
2
3
4
5
message Foo {
...
required foo.bar.Open open =1;
...
}
包的聲明符會根據(jù)使用語言的不同影響生成的代碼。
對于C++,產(chǎn)生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在 foo::bar空間中; - 對于Java,包聲明符會變?yōu)閖ava的一個包,除非在.proto文件中提供了一個明確有java_package;
對于 Python,這個包聲明符是被忽略的,因為Python模塊是按照其在文件系統(tǒng)中的位置進(jìn)行組織的。
對于Go,包可以被用做Go包名稱,除非你顯式的提供一個option go_package在你的.proto文件中。
對于Ruby,生成的類可以被包裝在內(nèi)置的Ruby名稱空間中,轉(zhuǎn)換成Ruby所需的大小寫樣式 (首字母大寫;如果第一個符號不是一個字母,則使用PB_前綴),例如Open會在Foo::Bar名稱空間中。
對于javaNano包會使用Java包,除非你在你的文件中顯式的提供一個option java_package。
對于C#包可以轉(zhuǎn)換為PascalCase后作為名稱空間,除非你在你的文件中顯式的提供一個option csharp_namespace,例如,Open會在Foo.Bar名稱空間中
包及名稱的解析
Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內(nèi)部開始查找,依次向外進(jìn)行,每個包會被看作是其父類包的內(nèi)部類。當(dāng)然對于 (foo.bar.Baz)這樣以“.”分隔的意味著是從最外圍開始的。
ProtocolBuffer編譯器會解析.proto文件中定義的所有類型名。 對于不同語言的代碼生成器會知道如何來指向每個具體的類型,即使它們使用了不同的規(guī)則。
定義服務(wù)(Service)
如果想要將消息類型用在RPC(遠(yuǎn)程方法調(diào)用)系統(tǒng)中,可以在.proto文件中定義一個RPC服務(wù)接口,protocol buffer編譯器將會根據(jù)所選擇的不同語言生成服務(wù)接口代碼及存根。如,想要定義一個RPC服務(wù)并具有一個方法,該方法能夠接收 SearchRequest并返回一個SearchResponse,此時可以在.proto文件中進(jìn)行如下定義:
1
2
3
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
最直觀的使用protocol buffer的RPC系統(tǒng)是gRPC,一個由谷歌開發(fā)的語言和平臺中的開源的PRC系統(tǒng),gRPC在使用protocl buffer時非常有效,如果使用特殊的protocol buffer插件可以直接為您從.proto文件中產(chǎn)生相關(guān)的RPC代碼。
如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC實現(xiàn),你可以從proto2語言指南中找到更多信息
還有一些第三方開發(fā)的PRC實現(xiàn)使用Protocol Buffer。參考第三方插件wiki查看這些實現(xiàn)的列表。
JSON 映射
Proto3 支持JSON的編碼規(guī)范,使他更容易在不同系統(tǒng)之間共享數(shù)據(jù),在下表中逐個描述類型。
如果JSON編碼的數(shù)據(jù)丟失或者其本身就是null,這個數(shù)據(jù)會在解析成protocol buffer的時候被表示成默認(rèn)值。如果一個字段在protocol buffer中表示為默認(rèn)值,體會在轉(zhuǎn)化成JSON的時候編碼的時候忽略掉以節(jié)省空間。具體實現(xiàn)可以提供在JSON編碼中可選的默認(rèn)值。
proto3JSONJSON示例注意
message
object
{“fBar”: v, “g”: null, …}
產(chǎn)生JSON對象,消息字段名可以被映射成lowerCamelCase形式,并且成為JSON對象鍵,null被接受并成為對應(yīng)字段的默認(rèn)值
enum
string
“FOO_BAR”
枚舉值的名字在proto文件中被指定
map
object
{“k”: v, …}
所有的鍵都被轉(zhuǎn)換成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值會是一個十進(jìn)制數(shù),數(shù)值型或者string類型都會接受
int64, fixed64, uint64
string
“1”, “-10”
JSON值會是一個十進(jìn)制數(shù),數(shù)值型或者string類型都會接受
float, double
number
1.1, -10.0, 0, “NaN”, “Infinity”
JSON值會是一個數(shù)字或者一個指定的字符串如”NaN”,”infinity”或者”-Infinity”,數(shù)值型或者字符串都是可接受的,指數(shù)符號也可以接受
Any
object
{“@type”: “url”, “f”: v, … }
如果一個Any保留一個特上述的JSON映射,則它會轉(zhuǎn)換成一個如下形式:{"@type": xxx, "value": yyy}否則,該值會被轉(zhuǎn)換成一個JSON對象,@type字段會被插入所指定的確定的值
Timestamp
string
“1972-01-01T10:00:20.021Z”
使用RFC 339,其中生成的輸出將始終是Z-歸一化啊的,并且使用0,3,6或者9位小數(shù)
Duration
string
“1.000340012s”, “1s”
生成的輸出總是0,3,6或者9位小數(shù),具體依賴于所需要的精度,接受所有可以轉(zhuǎn)換為納秒級的精度
Struct
object
{ … }
任意的JSON對象,見struct.proto
Wrapper types
various types
2, “2”, “foo”, true, “true”, null, 0, …
包裝器在JSON中的表示方式類似于基本類型,但是允許nulll,并且在轉(zhuǎn)換的過程中保留null
FieldMask
string
“f.fooBar,h”
見fieldmask.proto
ListValue
array
[foo, bar, …]
Value
value
任意JSON值
NullValue
null
JSON null
選項
定義.proto文件時能夠標(biāo)注一系列的option。Option并不改變整個文件聲明的含義,但卻能夠影響特定環(huán)境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到。
一些選項是文件級別的,意味著它可以作用于最外范圍,不包含在任何消息內(nèi)部、enum或服務(wù)定義中。一些選項是消息級別的,意味著它可以用在消息定義的內(nèi)部。當(dāng)然有些選項可以作用在域、enum類型、enum值、服務(wù)類型及服務(wù)方法中。到目前為止,并沒有一種有效的選項能作用于所有的類型。
如下就是一些常用的選項:
java_package (文件選項) :這個選項表明生成java類所在的包。如果在.proto文件中沒有明確的聲明java_package,就采用默認(rèn)的包名。當(dāng)然了,默認(rèn)方式產(chǎn)生的 java包名并不是最好的方式,按照應(yīng)用名稱倒序方式進(jìn)行排序的。如果不需要產(chǎn)生java代碼,則該選項將不起任何作用。如:
1
option java_package = "com.example.foo";
java_outer_classname (文件選項): 該選項表明想要生成Java類的名稱。如果在.proto文件中沒有明確的java_outer_classname定義,生成的class名稱將會根據(jù).proto文件的名稱采用駝峰式的命名方式進(jìn)行生成。如(foo_bar.proto生成的java類名為FooBar.java),如果不生成java代碼,則該選項不起任何作用。如:
1
option java_outer_classname = "Ponycopter";
optimize_for(文件選項): 可以被設(shè)置為 SPEED, CODE_SIZE,或者LITE_RUNTIME。這些值將通過如下的方式影響C++及java代碼的生成:
SPEED (default): protocol buffer編譯器將通過在消息類型上執(zhí)行序列化、語法分析及其他通用的操作。這種代碼是最優(yōu)的。
CODE_SIZE: protocol buffer編譯器將會產(chǎn)生最少量的類,通過共享或基于反射的代碼來實現(xiàn)序列化、語法分析及各種其它操作。采用該方式產(chǎn)生的代碼將比SPEED要少得多, 但是操作要相對慢些。當(dāng)然實現(xiàn)的類及其對外的API與SPEED模式都是一樣的。這種方式經(jīng)常用在一些包含大量的.proto文件而且并不盲目追求速度的 應(yīng)用中。
LITE_RUNTIME: protocol buffer編譯器依賴于運(yùn)行時核心類庫來生成代碼(即采用libprotobuf-lite 替代libprotobuf)。這種核心類庫由于忽略了一 些描述符及反射,要比全類庫小得多。這種模式經(jīng)常在移動手機(jī)平臺應(yīng)用多一些。編譯器采用該模式產(chǎn)生的方法實現(xiàn)與SPEED模式不相上下,產(chǎn)生的類通過實現(xiàn) MessageLite接口,但它僅僅是Messager接口的一個子集。
1
option optimize_for = CODE_SIZE;
cc_enable_arenas(文件選項):對于C++產(chǎn)生的代碼啟用arena allocation
objc_class_prefix(文件選項):設(shè)置Objective-C類的前綴,添加到所有Objective-C從此.proto文件產(chǎn)生的類和枚舉類型。沒有默認(rèn)值,所使用的前綴應(yīng)該是蘋果推薦的3-5個大寫字符,注意2個字節(jié)的前綴是蘋果所保留的。
deprecated(字段選項):如果設(shè)置為true則表示該字段已經(jīng)被廢棄,并且不應(yīng)該在新的代碼中使用。在大多數(shù)語言中沒有實際的意義。在java中,這回變成@Deprecated注釋,在未來,其他語言的代碼生成器也許會在字標(biāo)識符中產(chǎn)生廢棄注釋,廢棄注釋會在編譯器嘗試使用該字段時發(fā)出警告。如果字段沒有被使用你也不希望有新用戶使用它,嘗試使用保留語句替換字段聲明。
1
int32 old_field = 6 [deprecated=true];
自定義選項
ProtocolBuffers允許自定義并使用選項。該功能應(yīng)該屬于一個高級特性,對于大部分人是用不到的。如果你的確希望創(chuàng)建自己的選項,請參看 Proto2 Language Guide。注意創(chuàng)建自定義選項使用了拓展,拓展只在proto3中可用。
生成訪問類
可以通過定義好的.proto文件來生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代碼,需要基于.proto文件運(yùn)行protocol buffer編譯器protoc。如果你沒有安裝編譯器,下載安裝包并遵照README安裝。對于Go,你還需要安裝一個特殊的代碼生成器插件。你可以通過GitHub上的protobuf庫找到安裝過程
通過如下方式調(diào)用protocol編譯器:
1
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聲明了一個.proto文件所在的解析import具體目錄。如果忽略該值,則使用當(dāng)前目錄。如果有多個目錄則可以多次調(diào)用--proto_path,它們將會順序的被訪問并執(zhí)行導(dǎo)入。-I=IMPORT_PATH是--proto_path的簡化形式。
當(dāng)然也可以提供一個或多個輸出路徑:
--cpp_out 在目標(biāo)目錄DST_DIR中產(chǎn)生C++代碼,可以在C++代碼生成參考中查看更多。
--java_out 在目標(biāo)目錄DST_DIR中產(chǎn)生Java代碼,可以在?Java代碼生成參考中查看更多。
--python_out 在目標(biāo)目錄 DST_DIR 中產(chǎn)生Python代碼,可以在Python代碼生成參考中查看更多。
--go_out 在目標(biāo)目錄 DST_DIR 中產(chǎn)生Go代碼,可以在GO代碼生成參考中查看更多。
--ruby_out在目標(biāo)目錄 DST_DIR 中產(chǎn)生Ruby代碼,參考正在制作中。
--javanano_out在目標(biāo)目錄DST_DIR中生成JavaNano,JavaNano代碼生成器有一系列的選項用于定制自定義生成器的輸出:你可以通過生成器的README查找更多信息,JavaNano參考正在制作中。
--objc_out在目標(biāo)目錄DST_DIR中產(chǎn)生Object代碼,可以在Objective-C代碼生成參考中查看更多。
--csharp_out在目標(biāo)目錄DST_DIR中產(chǎn)生Object代碼,可以在C#代碼生成參考中查看更多。
--php_out在目標(biāo)目錄DST_DIR中產(chǎn)生Object代碼,可以在PHP代碼生成參考中查看更多。
作為一個方便的拓展,如果DST_DIR以.zip或者.jar結(jié)尾,編譯器會將輸出寫到一個ZIP格式文件或者符合JAR標(biāo)準(zhǔn)的.jar文件中。注意如果輸出已經(jīng)存在則會被覆蓋,編譯器還沒有智能到可以追加文件。
你必須提議一個或多個.proto文件作為輸入,多個.proto文件可以只指定一次。雖然文件路徑是相對于當(dāng)前目錄的,每個文件必須位于其IMPORT_PATH下,以便每個文件可以確定其規(guī)范的名稱。
總結(jié)
以上是生活随笔為你收集整理的protobuf3 自定义option_Protobuf3 语法指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信好名字大全,最火网名485个
- 下一篇: r语言mfrow全程_R语言中的色彩_L