【golang微服務】Protocol Buffers V3中文語法指南 Protocol Buffers V3中文語法指南[翻譯]

Protocol Buffers V3中文語法指南[翻譯]

本文是官方protocol buffers v3指南的翻譯。

本文翻譯自https://developers.google.com/protocol-buffers/docs/proto3

定義一個消息類型

首先讓我們看一個非常簡單的例子。假設你想要定義一個搜索請求消息格式,其中每個搜索請求都包含一個查詢詞字符串、你感興趣的查詢結果所在的特定頁碼數和每一頁應展示的結果數。

下面是用於定義這個消息類型的 .proto 文件。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定使用 proto3 語法: 如果不這樣寫,protocol buffer編譯器將假定你使用 proto2。這個聲明必須是文件的第一個非空非註釋行。
  • SearchRequest 消息定義指定了三個字段(名稱/值對) ,每個字段表示希望包含在此類消息中的每一段數據。每個字段都有一個名稱和一個類型。

指定字段類型

在上面的示例中,所有字段都是標量類型(scalar types): 兩個整數(page_numberresult_per_page)和一個字符串(query)。但是也可以爲字段指定組合類型,包括枚舉和其他消息類型。

分配字段編號

如你所見,消息定義中的每個字段都有一個唯一的編號。這些字段編號用來在消息二進制格式中標識字段,在消息類型使用後就不能再更改。注意,範圍1到15中的字段編號需要一個字節進行編碼,包括字段編號和字段類型。範圍16到2047的字段編號採用兩個字節。因此,應該爲經常使用的消息元素保留數字1到15的編號。切記爲將來可能添加的經常使用的元素留出一些編號。

你可以指定的最小字段數是1,最大的字段數是 229−1229−1 ,即536,870,911。你也不能使用19000到19999 (FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber)的編號,它們是預留給Protocol Buffers協議實現的。如果你在你的.proto文件中使用了預留的編號Protocol Buffers編譯器就會報錯。同樣,你也不能使用任何之前保留的字段編號。

指定字段規則

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

  • singular: 格式正確的消息可以有這個字段的零個或一個(但不能多於一個)。這是 proto3語法的默認字段規則。
  • repeated: 該字段可以在格式正確的消息中重複任意次數(包括零次)。重複值的順序將被保留。

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

你可以在 Protocol Buffer Encoding 中找到關於packed編碼的更多信息。

添加更多消息類型

可以在一個.proto 文件中定義多個消息類型。如果你正在定義多個相關的消息,這是非常有用的——例如,如果想定義與 SearchRequest 消息類型對應的應答消息格式SearchResponse,你就可以將其添加到同一個.proto文件中。

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

message SearchResponse {
 ...
}

添加註釋

要給你的.proto文件添加註釋,需要使用C/C++風格的///* ... */語法。

/* SearchRequest 表示一個分頁查詢 
 * 其中有一些字段指示響應中包含哪些結果 */

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // 頁碼數
  int32 result_per_page = 3;  // 每頁返回的結果數
}

保留字段

如果你通過完全刪除字段或將其註釋掉來更新消息類型,那麼未來的用戶在對該類型進行自己的更新時可以重用字段號。如果其他人以後加載舊版本的相同.proto文件,這可能會導致嚴重的問題,包括數據損壞,隱私漏洞等等。確保這種情況不會發生的一種方法是指定已刪除字段的字段編號(和/或名稱,這也可能導致 JSON 序列化問題)是保留的(reserved)。如果將來有任何用戶嘗試使用這些字段標識符,protocol buffer編譯器將發出提示。

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

注意,不能在同一個reserved語句中混合字段名和字段編號。

從你的.proto文件生成了什麼?

當你使用 protocol buffer 編譯器來運行.proto文件時,編譯器用你選擇的語言生成你需要使用文件中描述的消息類型,包括獲取和設置字段值,將消息序列化爲輸出流,以及從輸入流解析消息的代碼。

  • C++來說,編譯器會爲每個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每一個消息有一個對應的類。
  • 對於 Java,編譯器生成一個.java 文件,每種消息類型都有一個類,還有一個特殊的 Builder 類用於創建消息類實例。
  • 對於 Kotlin,除了 Java 生成的代碼之外,編譯器還生成一個每種消息類型的 .kt 文件,包含一個 DSL,可用於簡化消息實例的創建。
  • Python 稍有不同ー Python 編譯器爲.proto文件中的每個消息類型生成一個帶靜態描述符的模塊,然後與 metaclass 一起使用,在運行時創建必要的 Python 數據訪問類。
  • 對於 Go,編譯器爲文件中的每種消息類型生成一個類型(type)到一個.pb.go 文件。
  • 對於 Ruby,編譯器生成一個.rb 文件,其中包含一個包含消息類型的 Ruby 模塊。
  • 對於 Objective-C,編譯器從每個.proto文件生成一個 pbobjc.hpbobjc.m 文件,.proto文件中描述的每種消息類型都有一個類。
  • 對於 C# ,編譯器生從每個.proto文件生成一個.cs 文件。.proto文件中描述的每種消息類型都有一個類。
  • 對於 Dart,編譯器爲文件中的每種消息類型生成一個.pb.dart 文件。

你可以通過學習所選語言的教程(proto3版本即將推出)瞭解更多關於使用每種語言的 API 的信息。有關 API 的更多細節,請參閱相關的 API reference(proto3版本也即將推出)。

標量值類型

標量消息字段可以具有以下類型之一——該表顯示了.proto文件,以及自動生成類中的對應類型(省略了Ruby、C#和Dart):

.proto Type Notes C++ Type Java/Kotlin Type[1] Python Type[3] Go Type PHP Type
double double double float float64 float
float float float float float32 float
int32 使用可變長度編碼。編碼負數效率低下——如果你的字段可能有負值,則使用 sint32代替。 int32 int int int32 integer
int64 使用可變長度編碼。編碼負數效率低下——如果你的字段可能有負值,則使用 sint64代替。 int64 long int/long[4] int64 integer/string[6]
uint32 使用變長編碼。 uint32 int[2] int/long[4] uint32 integer
uint64 使用變長編碼。 uint64 long[2] int/long[4] uint64 integer/string[6]
sint32 使用可變長度編碼。帶符號的 int 值。這些編碼比普通的 int32更有效地編碼負數。 int32 int int int32 integer
sint64 使用可變長度編碼。帶符號的 int 值。這些編碼比普通的 int64更有效地編碼負數。 int64 long int/long[4] int64 integer/string[6]
fixed32 總是四個字節。如果值經常大於228,則比 uint32更有效率。 uint32 int[2] int/long[4] uint32 integer
fixed64 總是8字節。如果值經常大於256,則比 uint64更有效率。 uint64 integer/string[6]
sfixed32 總是四個字節。 int32 int int int32 integer
sfixed64 總是八個字節。 int64 integer/string[6]
bool bool boolean bool bool boolean
string 字符串必須始終包含 UTF-8編碼的或7位 ASCII 文本,且不能長於232。 string String str/unicode[5] string string
bytes 可以包含任何不超過232字節的任意字節序列。 string ByteString str (Python 2) bytes (Python 3) []byte string

在使用 Protocol Buffer Encoding 對消息進行序列化時,可以瞭解有關這些類型如何編碼的更多信息。

[1] Kotlin 使用來自 Java 的相應類型,甚至是無符號類型,以確保混合 Java/Kotlin 代碼庫的兼容性。

[2] 在 Java 中,無符號的32位和64位整數使用它們的有符號對應項來表示,最高位存儲在有符號位中。

[3] 在任何情況下,爲字段設置值都將執行類型檢查,以確保其有效。

[4] 64位或無符號的32位整數在解碼時總是表示爲 long ,但如果在設置字段時給出 int,則可以表示爲 int。在任何情況下,值必須與設置時表示的類型相匹配。見[2]。

[5] Python 字符串在解碼時表示爲 unicode,但如果給出了 ASCII 字符串,則可以表示爲 str (這可能會更改)。

[6] 整數用於64位機器,字符串用於32位機器。

默認值

當解析消息時,如果編碼消息不包含特定的 singular 元素,則解析對象中的相應字段將設置爲該字段的默認值。

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

repeated 字段的默認值爲空(通常是適當語言中的空列表)。

請注意,對於標量消息字段,一旦消息被解析,就無法判斷字段是顯式設置爲默認值(例如,是否一個布爾值是被設置爲 false)還是根本沒有設置: 在定義消息類型時應該牢記這一點。例如,如果你不希望某個行爲在默認情況下也發生,那麼就不要設置一個布爾值,該布爾值在設置爲 false 時會開啓某些行爲。還要注意,如果將標量消息字段設置爲默認值,則該值將不會在傳輸過程中序列化。

有關生成的代碼的默認工作方式的更多詳細信息,請參閱所選語言的生成代碼指南

枚舉

在定義消息類型時,你可能希望其中一個字段只能是預定義的值列表中的一個值。例如,假設你想爲每個 SearchRequest 添加一個語料庫字段,其中語料庫可以是 UNIVERSALWEBIMAGESLOCALNEWSPRODUCTSVIDEO。你可以通過在消息定義中添加一個枚舉,爲每個可能的值添加一個常量來非常簡單地完成這項工作。

在下面的例子中,我們添加了一個名爲 Corpusenum,包含所有可能的值,以及一個類型爲 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 enum 的第一個常量映射爲零: 每個 enum 定義必須包含一個常量,該常量映射爲零作爲它的第一個元素。這是因爲:

  1. 必須有一個零值,這樣我們就可以使用0作爲數值默認值。
  2. 零值必須是第一個元素,以便與 proto2語義兼容,其中第一個枚舉值總是默認值。

你可以通過將相同的值分配給不同的枚舉常量來定義別名。爲此,你需要將 allow _ alias 選項設置爲 true,否則,當發現別名時,protocol 編譯器將生成錯誤消息。

message MyMessage1 {
  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}
message MyMessage2 {
  enum EnumNotAllowingAlias {
    UNKNOWN = 0;
    STARTED = 1;
    // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  }
}

枚舉的常數必須在32位整數的範圍內。由於枚舉值在傳輸時使用變長編碼,因此負值效率低,因此不推薦使用。可以在消息定義中定義枚舉,如上面的例子所示,也可以在外面定義——這樣就可以在.proto文件中的消息定義中重用這些枚舉。你還可以使用_MessageType_._EnumType_ 語法,使用在一個消息中聲明的enum類型作爲不同消息中的字段類型。

當對一個使用了枚舉的.proto文件運行 protocol buffer 編譯器的時候,對於 Java, Kotlin,或 C++ 生成的代碼中將有一個對應的enum,或者對於 Python 會生成一個特殊的EnumDescriptor類,它被用於在運行時生成的類中創建一組帶有整數值的符號常量。

注意:生成的代碼可能會受到特定於語言的枚舉數限制(單種語言的數量低於千)。請檢查你計劃使用的語言的限制。

在反序列化過程中,不可識別的枚舉值將保留在消息中,儘管當消息被反序列化時,這種值的表示方式依賴於語言。在支持值超出指定符號範圍(如 C++ 和 Go)的開放枚舉類型的語言中,未知枚舉值僅存儲爲其底層的整數表示形式。在具有閉合枚舉類型(如 Java)的語言中,枚舉中的一個類型將用於表示一個無法識別的值,並且可以使用特殊的訪問器訪問底層的整數。在這兩種情況下,如果消息被序列化,那麼不可識別的值仍然會與消息一起被序列化。

有關如何在應用程序中使用消息enum的詳細信息,請參閱爲所選語言生成的代碼指南

預留值

如果通過完全刪除枚舉條目或註釋掉枚舉類型來更新枚舉類型,那麼未來的用戶在自己更新該類型時可以重用該數值。這可能會導致嚴重的問題,如果以後有人加載舊版本的相同.proto文件,包括數據損壞,隱私漏洞等等。確保不發生這種情況的一種方法是指定已刪除條目的數值(和/或名稱,這也可能導致 JSON 序列化問題)爲 reserved。如果任何未來的用戶試圖使用這些標識符,protocol buffer 編譯器將報錯。你可以使用 max關鍵字指定保留的數值範圍最大爲可能的值。

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

注意,不能在同一個保留語句中混合字段名和數值。

使用其他消息類型

你可以使用其他消息類型作爲字段類型。例如,假設你希望在每個 SearchResponse消息中包含UI個 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文件中定義了,該怎麼辦?

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

import "myproject/other_protos.proto";

默認情況下,只能從直接導入的 .proto 文件中使用定義。但是,有時你可能需要將 .proto 文件移動到新的位置。你可以在舊目錄放一個佔位的.proto文件使用import public 概念將所有導入轉發到新位置,而不必直接移動.proto文件並修改所有的地方。

注意,Java 中沒有 import public 功能。

import public依賴項可以被任何導入包含import public語句的 proto 的代碼傳遞依賴。例如:

// 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 參數設置爲項目的根目錄併爲所有導入使用正確的名稱。

使用proto2消息類型

導入 proto2消息類型並在 proto3消息中使用它們是可能的,反之亦然。然而,proto2 enum 不能直接在 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;
    }
  }
}

更新消息類型

如果現有的消息類型不再滿足你的所有需要——例如,你希望消息格式有一個額外的字段——但是你仍然希望使用用舊格式創建的代碼,不要擔心!在不破壞任何現有代碼的情況下更新消息類型非常簡單,只需記住以下規則:

  • 不要更改任何現有字段的字段編號
  • 如果添加新字段,那麼任何使用“舊”消息格式通過代碼序列化的消息仍然可以通過新生成的代碼進行解析。你應該記住這些元素的默認值,以便新代碼能夠正確地與舊代碼生成的消息交互。類似地,新代碼創建的消息可以通過舊代碼解析: 舊的二進制文件在解析時直接忽略新字段。有關詳細信息,請參閱 未知字段 部分。
  • 字段可以被刪除,只要字段編號不再用於你更新的消息類型。你可能希望改爲重命名字段,或者爲其添加”OBSOLETE_“前綴,或者聲明字段編號爲reserved,以便.proto的未來用戶不可能不小心重複使用這個編號。
  • int32uint32int64uint64bool都是兼容的——這意味着你可以在不破壞向前或向後兼容性的情況下將一個字段從這些類型中的一個更改爲另一個。
  • 如果一個數字被解析到一個並不適當的類型中,你會得到與在 C++ 中將數字轉換爲該類型相同的效果(例如,如果一個64位的數字被讀作 int32,它將被截斷爲32位)
  • sint32sint64相互兼容,但與其他整數類型不兼容。
  • stringbytes是兼容的,只要字節是有效的 UTF-8。
  • 如果字節包含消息的編碼版本,則嵌入的消息與bytes兼容。
  • fixed32sfixed32兼容 fixed64sfixed64兼容。
  • 對於stringbytes和消息字段,optional字段與repeated字段兼容。給定重複字段的序列化數據作爲輸入,如果該字段是基本類型字段,期望該字段爲可選字段的客戶端將接受最後一個輸入值; 如果該字段是消息類型字段,則合併所有輸入元素。注意,這對於數字類型(包括 bools 和 enums)通常是不安全的。重複的數值類型字段可以按packed的格式序列化,如果是optional字段,則無法正確解析這些字段。
  • Enum 在格式方面與 int32、 uint32、 int64和 uint64兼容(請注意,如果不適合,值將被截斷)。但是要注意,當消息被反序列化時,客戶端代碼可能會區別對待它們: 例如,未被識別的 proto3 enum類型將保留在消息中,但是當消息被反序列化時,這種類型的表示方式依賴於語言。Int 字段總是保留它們的值。
  • 將單個值更改爲oneof成員是安全的,並且二進制兼容。如果確保沒有代碼一次設置多個字段,那麼將多個字段移動到新的oneof字段中可能是安全的。將任何字段移動到現有的字段中都是不安全的。

未知字段

未知字段是格式良好的協議緩衝區序列化數據,表示解析器不識別的字段。例如,當舊二進制解析由新二進制發送的帶有新字段的數據時,這些新字段將成爲舊二進制中的未知字段。

最初,proto3消息在解析過程中總是丟棄未知字段,但在3.5版本中,我們重新引入了未知字段的保存來匹配 proto2行爲。在3.5及以後的版本中,解析期間保留未知字段,並將其包含在序列化輸出中。

Any

Any 消息類型允許你將消息作爲嵌入類型使用,而不需要其 .proto 定義。Any包含一個任意序列化的字節消息,以及一個解析爲該消息的類型作爲消息的全局唯一標識符的URL。要使用 Any類型,需要導入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 ...
  }
}

目前正在開發用於處理任何類型的運行時庫。

如果你已經熟悉 proto2語法,Any 可以保存任意的 proto3消息,類似於 proto2消息,可以允許擴展。

oneof

如果你有一條包含多個字段的消息,並且最多同時設置其中一個字段,那麼你可以通過使用oneof來實現並節省內存。

oneof字段類似於常規字段,只不過oneof中的所有字段共享內存,而且最多可以同時設置一個字段。設置其中的任何成員都會自動清除所有其他成員。根據所選擇的語言,可以使用特殊 case()WhichOneof() 方法檢查 one of 中的哪個值被設置(如果有的話)。

使用oneof

要定義 oneof 字段需要在你的.proto文件中使用oneof關鍵字並在後面跟上名稱,在下面的例子中字段名稱爲test_oneof

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

然後將其中一個字段添加到該字段的定義中。你可以添加任何類型的字段,除了map字段和repeated字段。

在生成的代碼中,其中一個字段具有與常規字段相同的 getter 和 setter。你還可以獲得一個特殊的方法來檢查其中一個設置了哪個值(如果有的話)。你可以在相關的 API 參考文獻中找到更多關於所選語言的 API。

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 不支持repeated
  • 反射 api 適用於 oneof 字段。
  • 如果將 oneof 字段設置爲默認值(例如將 int32 oneof 字段設置爲0) ,則將設置該字段的“ case”,並在連接上序列化該值。
  • 如果你使用 C++ ,確保你的代碼不會導致內存崩潰。下面的示例代碼將崩潰,因爲通過調用 set_name()方法已經刪除了 sub_message
  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());

向後兼容性問題

添加或刪除一個字段時要小心。如果檢查 one of 的值返回None/NOT_SET,這可能意味着 one of 沒有被設置,或者它已經被設置爲 one of 的不同版本中的一個字段。這沒有辦法區分,因爲沒有辦法知道未知字段是否是 oneof 的成員。

標籤重用問題

  • 將字段移入或移出 oneof:在序列化和解析消息之後,你可能會丟失一些信息(某些字段將被清除)。但是,你可以安全地將單個字段移動到新的 oneof 字段中,並且如果已知只設置了一個字段,則可以移動多個字段。
  • 刪除一個oneof 字段再添加回來:這可能會在消息被序列化和解析後清除當前設置的 oneof 字段。
  • 拆分或合併oneof:這與移動常規字段有類似的問題。

Maps

如果你想創建一個關聯映射作爲你數據定義的一部分,protocol buffers提供了一個方便的快捷語法:

map<key_type, value_type> map_field = N;

…其中key_type可以是任何整型或字符串類型(因此,除了浮點類型和字節以外的任何標量類型) 。注意,枚舉不是有效的key_typevalue_type可以是除另一個映射以外的任何類型。

例如,如果你想創建一個項目映射,其中每個Project消息都與一個字符串鍵相關聯,你可以這樣定義:

map<string, Project> projects = 3;
  • 映射字段不能重複。
  • 映射值的有線格式排序和映射迭代排序是未定義的,因此不能依賴於映射項的特定排序。
  • 當爲 .proto 生成文本格式時,映射按鍵排序。數字鍵按數字排序。
  • 當從連接解析或合併時,如果有重複的映射鍵,則使用最後看到的鍵。當從文本格式解析映射時,如果有重複的鍵,解析可能會失敗。
  • 如果爲映射字段提供了鍵但沒有值,則該字段序列化時的行爲與語言相關。在 C++ 、 Java、 Kotlin 和 Python 中,類型的默認值是序列化的,而在其他語言中,沒有任何值是序列化的。

生成的映射 API 目前可用於所有支持 proto3的語言。你可以在相關的 API 參考中找到更多關於所選語言的映射 API 的信息。

向後兼容性

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

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

repeated MapFieldEntry map_field = N;

任何支持映射的protocol buffers實現都必須生成並接受上述定義可以接受的數據。

Packages

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

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

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

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

package 說明符影響生成代碼的方式取決於你選擇的語言:

  • 對於C++,產生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在 foo::bar空間中;
  • 對於JavaKotlin,包聲明符會變爲java的一個包,除非在.proto文件中提供了一個明確有option java_package
  • 對於 Python,這個包聲明符是被忽略的,因爲Python模塊是按照其在文件系統中的位置進行組織的
  • 對於Go,包可以被用做Go包名稱,除非你顯式的提供一個option go_package在你的.proto文件中。
  • 對於Ruby,生成的類可以被包裝在內置的Ruby名稱空間中,轉換成Ruby所需的大小寫樣式 (首字母大寫;如果第一個符號不是一個字母,則使用PB_前綴),例如Open會在Foo::Bar名稱空間中。
  • C# 中,包在轉換到 PascalCase 後被用作名稱空間,除非你在.proto文件中提供option csharp_namespace。例如,Open 將位於Foo.Bar名稱空間中。

package和名稱解析

在 protocol buffer 語言中,類型名稱解析的工作原理類似於 C++ : 首先搜索最內層的作用域,然後搜索下一個最內層的作用域,依此類推,每個包都被認爲是其父包的“ inner”。前導的“ .”(例如,.foo.bar.Baz)表示從最外側的範圍開始。

protocol buffer 通過解析導入的 .proto 文件來解析所有類型名稱。每種語言的代碼生成器都知道如何引用該語言中的每種類型,即使它有不同的作用域規則。

定義服務

如果希望將消息類型與 RPC (遠程過程調用)系統一起使用,可以在.proto 文件和 protocol buffer 編譯器將用你選擇的語言生成服務接口代碼和存根。因此,例如你希望定義一個 RPC 服務,其方法接受你的 SearchRequest並返回一個 SearchResponse,則可以在.proto文件如下定義。

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

使用 protocol buffers 最直接的 RPC 系統是 gRPC,這是 Google 開發的一個語言和平臺中立的開源 RPC 系統,可以與 protocol buffers 一起使用。gRPC 特別適用於protocol buffers ,它可以讓你直接從你的.proto文件使用特殊的 protocol buffers 編譯器插件。

如果你不想使用 gRPC,你也可以在你自己的 RPC 實現中使用協議緩衝。你可以在《proto2語言指南》中找到更多相關信息。

還有一些正在進行的第三方項目正在開發 RPC 的實施協議緩衝。有關我們所知道的項目的鏈接列表,請參閱第三方添加項 wiki 頁面

JSON 映射

proto3支持 JSON 的規範編碼,使得系統之間更容易共享數據。下表按類型逐一描述了編碼。

如果 json 編碼的數據中缺少某個值,或者該值爲 null,那麼在解析爲 protocol buffer 時,該值將被解釋爲適當的默認值。如果一個字段在 protocol buffer 中具有默認值,爲了節省空間,默認情況下 json 編碼的數據中將省略該字段。具體實現可以提供在JSON編碼中可選的默認值。

proto3 JSON JSON example Notes
message object {"fooBar": v, "g": null, …} 生成 JSON 對象。消息字段名映射到 lowerCamelCase 併成爲 JSON 對象鍵。如果指定了 json_name 字段選項,則將使用指定的值作爲鍵。解析器接受 lowerCamelCase 名稱(或 json_name 選項指定的名稱)和原始 proto 字段名稱。 null 是所有字段類型的接受值,並被視爲相應字段類型的默認值。
enum string "FOO_BAR" 使用 proto 中指定的枚舉值的名稱。解析器接受枚舉名稱和整數值。
map object {"k": v, …} 所有鍵都轉換爲字符串。
repeated V array [v, …] null 被接受爲空列表 []
bool true, false true, false
string string "Hello World!"
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" JSON 值將是使用帶填充的標準 base64編碼方式編碼爲字符串的數據。接受帶/不帶填充的標準或 URL 安全的 base64編碼。
int32, fixed32, uint32 number 1, -10, 0 JSON 值將是一個十進制數字。接受數字或字符串。
int64, fixed64, uint64 string "1", "-10" JSON 值將是一個十進制字符串。接受數字或字符串。
float, double number 1.1, -10.0, 0, "NaN", "Infinity" JSON 值將是一個數字或一個特殊的字符串值“NaN”、“ Infinity”和“-Infinity”。接受數字或字符串。也接受指數表示法。-0被認爲等效於0。
Any object {"@type": "url", "f": v, … } 如果Any包含一個具有特殊 JSON 映射的值,它將被轉換如下: {"@type": xxx, "value": yyy}. 否則,該值將轉換爲 JSON 對象,並插入"@type"字段以指示實際的數據類型。
Timestamp string "1972-01-01T10:00:20.021Z" 使用 RFC3339,其中生成的輸出將始終是 Z 標準化的,並使用0、3、6或9個小數位。也接受“ Z”以外的偏移量。
Duration string "1.000340012s", "1s" 生成的輸出總是包含0、3、6或9個小數位,具體取決於所需的精度,後綴“ s”。接受任何小數位(也可以沒有) ,只要他們符合納秒精度和後綴“ s”是必需的。
Struct object { … } 任何JSON對象。請參見 struct.proto
Wrapper types various types 2, "2", "foo", true, "true", null, 0, … Wrappers 使用與包裝原語類型相同的 JSON 表示,只是在數據轉換和傳輸期間允許並保留 null
FieldMask string "f.fooBar,h" 請參見 field_mask.proto.
ListValue array [foo, bar, …]
Value value 任何 JSON 值。請檢查 google.protobuf.Value 以獲取詳細信息。
NullValue null JSON null
Empty object {} 一個空的JSON對象

JSON選項

一個proto3協議 JSON 實現可能提供以下選項:

  • 提供默認值的字段:在proto3 JSON 輸出中,值爲默認值的字段被省略。可以提供一個選項,用默認值覆蓋此行爲和輸出字段。
  • 忽略位置字段:在缺省情況下,Proto3 JSON 解析器應該拒絕未知字段,但在解析過程中可能會提供一個忽略未知字段的選項。
  • 使用 proto 字段名而不是小駝峯名稱:默認情況下,proto3 JSON 打印機應該將字段名轉換爲 lowerCamelCase,並使用它作爲 JSON 名稱。可以提供一個選項,用原型字段名作爲 JSON 名。需要協議3 JSON 解析器同時接受轉換後的 lowerCamelCase 名稱和原始字段名稱。
  • 以整數而不是字符串形式展示枚舉值:在 JSON 輸出中,默認情況下使用枚舉值的名稱。可以提供一個選項來代替使用枚舉值的數值。

剩下options等內容本文略。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章