語言指南 (proto 3)
本指南說明如何使用協定緩衝區語言來建構您的協定緩衝區資料,包括 .proto
檔案語法,以及如何從您的 .proto
檔案產生資料存取類別。它涵蓋協定緩衝區語言的 proto3 修訂版本。
如需 版本 語法的相關資訊,請參閱 Protobuf 版本語言指南。
如需 proto2 語法的相關資訊,請參閱 Proto2 語言指南。
這是一份參考指南 – 如需逐步範例,其中使用本文件中描述的許多功能,請參閱您選擇的語言的教學課程。
定義訊息類型
首先,讓我們看一個非常簡單的範例。假設您想要定義搜尋請求訊息格式,其中每個搜尋請求都有一個查詢字串、您感興趣的特定結果頁面,以及每頁的結果數。以下是您用來定義訊息類型的 .proto
檔案。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
檔案的第一行指定您正在使用 protobuf 語言規格的 proto3 修訂版本。
edition
(或 proto2/proto3 的syntax
)必須是檔案的第一個非空白、非註解行。- 如果未指定
edition
或syntax
,協定緩衝區編譯器會假設您正在使用 proto2。
SearchRequest
訊息定義指定三個欄位(名稱/值配對),每個欄位對應於您想要在此訊息類型中包含的每個資料片段。每個欄位都有一個名稱和一個類型。
指定欄位類型
在先前的範例中,所有欄位都是純量類型:兩個整數(page_number
和 results_per_page
)和一個字串 (query
)。您也可以為您的欄位指定列舉和複合類型,例如其他訊息類型。
指派欄位編號
您必須為訊息定義中的每個欄位指定一個介於 1
和 536,870,911
之間的數字,並具有以下限制
- 給定的數字在該訊息的所有欄位中必須是唯一的。
- 欄位編號
19,000
到19,999
保留給 Protocol Buffers 實作。如果您在訊息中使用這些保留欄位編號之一,協定緩衝區編譯器會發出警告。 - 您無法使用任何先前保留的欄位編號或已分配給擴充功能的任何欄位編號。
一旦您的訊息類型在使用中,此數字就無法變更,因為它會在訊息線路格式中識別欄位。「變更」欄位編號相當於刪除該欄位,並建立具有相同類型但新編號的新欄位。請參閱刪除欄位,了解如何正確執行此操作。
欄位編號絕對不應該重複使用。永遠不要將欄位編號從保留清單中取出,以用於新的欄位定義。請參閱重複使用欄位編號的後果。
您應該將欄位編號 1 到 15 用於最常設定的欄位。較低的欄位編號值在線路格式中佔用的空間較少。例如,範圍 1 到 15 的欄位編號需要一個位元組來編碼。範圍 16 到 2047 的欄位編號需要兩個位元組。您可以在Protocol Buffer 編碼中找到更多相關資訊。
重複使用欄位編號的後果
重複使用欄位編號會使解碼線路格式訊息變得模稜兩可。
protobuf 線路格式很精簡,無法提供一種方法來偵測使用一個定義編碼並使用另一個定義解碼的欄位。
使用一個定義編碼欄位,然後使用不同的定義解碼同一個欄位可能會導致
- 開發人員的時間浪費在除錯上
- 剖析/合併錯誤(最好的情況)
- 洩漏 PII/SPII
- 資料損毀
欄位編號重複使用的常見原因
- 重新編號欄位(有時會為了達到更美觀的欄位數字順序而進行)。重新編號實際上會刪除並重新新增所有涉及重新編號的欄位,導致不相容的線路格式變更。
- 刪除欄位且未保留該編號以防止未來重複使用。
欄位編號限制為 29 位元,而不是 32 位元,因為三位元用於指定欄位的線路格式。如需更多資訊,請參閱編碼主題。
指定欄位基數
訊息欄位可以是下列其中一種
單數:
在 proto3 中,有兩種單數欄位類型
optional
:(建議)optional
欄位處於兩種可能的狀態之一- 欄位已設定,並包含明確設定或從線路剖析的值。它將序列化到線路。
- 欄位未設定,並將傳回預設值。它不會序列化到線路。
您可以檢查是否已明確設定該值。
為了與 protobuf 版本和 proto2 達到最大的相容性,建議使用
optional
而不是隱含欄位。隱含:(不建議) 隱含欄位沒有明確的基數標籤,且行為如下
如果欄位是訊息類型,則其行為與
optional
欄位完全相同。如果欄位不是訊息,則它有兩種狀態
- 欄位已設定為明確設定或從線路剖析的非預設(非零)值。它將序列化到線路。
- 欄位已設定為預設(零)值。它不會序列化到線路。事實上,您無法判斷預設(零)值是設定的還是從線路剖析的,還是根本未提供。如需此主題的更多資訊,請參閱欄位存在。
repeated
:此欄位類型可以在格式正確的訊息中重複零次或多次。將保留重複值的順序。map
:這是一種配對的鍵/值欄位類型。請參閱地圖,以取得此欄位類型的詳細資訊。
重複欄位預設會封裝
在 proto3 中,純量數值類型的 repeated
欄位預設使用 packed
編碼。
您可以在Protocol Buffer 編碼中找到更多關於 packed
編碼的資訊。
訊息類型欄位一律具有欄位存在
在 proto3 中,訊息類型欄位已具有欄位存在。因此,新增 optional
修飾詞不會變更欄位的欄位存在。
下列程式碼範例中 Message2
和 Message3
的定義會為所有語言產生相同的程式碼,而且二進位、JSON 和 TextFormat 中的表示法沒有差異
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
格式正確的訊息
當應用於 protobuf 訊息時,「格式良好」一詞指的是序列化/反序列化的位元組。protoc 解析器會驗證給定的 proto 定義檔是否可解析。
單數欄位可能會在線路格式位元組中出現多次。解析器將會接受輸入,但只有該欄位的最後一個實例可以透過產生的繫結存取。有關此主題的更多資訊,請參閱後者勝出。
新增更多訊息類型
多個訊息類型可以在單個 .proto
檔案中定義。如果您要定義多個相關訊息,這會很有用 – 例如,如果您想定義與您的 SearchResponse
訊息類型相對應的回覆訊息格式,您可以將其新增到同一個 .proto
中。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
合併訊息會導致膨脹雖然可以在單個 .proto
檔案中定義多個訊息類型(例如訊息、列舉和服務),但是當在單個檔案中定義大量具有不同依賴關係的訊息時,也會導致依賴關係膨脹。建議每個 .proto
檔案盡可能包含較少的訊息類型。
新增註解
若要將註解新增至您的 .proto
檔案
優先在
.proto
程式碼元素之前的行使用 C/C++/Java 行尾樣式註解「//」也接受 C 樣式內嵌/多行註解
/* ... */
。- 使用多行註解時,建議使用「*」的邊距行。
/**
* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response.
*/
message SearchRequest {
string query = 1;
// Which page number do we want?
int32 page_number = 2;
// Number of results to return per page.
int32 results_per_page = 3;
}
刪除欄位
如果未正確完成,刪除欄位可能會導致嚴重的問題。
當您不再需要某個欄位,並且已從用戶端程式碼中刪除所有參考時,您可以從訊息中刪除欄位定義。但是,您必須保留已刪除的欄位編號。如果您不保留欄位編號,開發人員未來可能會重複使用該編號。
您還應該保留欄位名稱,以便您的訊息的 JSON 和 TextFormat 編碼可以繼續解析。
保留的欄位編號
如果您透過完全刪除欄位或註解掉欄位來更新訊息類型,未來的開發人員可以在對類型進行自己的更新時重複使用該欄位編號。這可能會導致嚴重的問題,如重複使用欄位編號的後果中所述。為確保不會發生這種情況,請將已刪除的欄位編號新增至 reserved
清單中。
如果任何未來的開發人員嘗試使用這些保留的欄位編號,protoc 編譯器將會產生錯誤訊息。
message Foo {
reserved 2, 15, 9 to 11;
}
保留的欄位編號範圍是包含的(9 到 11
與 9, 10, 11
相同)。
保留的欄位名稱
稍後重複使用舊的欄位名稱通常是安全的,除非使用序列化欄位名稱的 TextProto 或 JSON 編碼。為了避免這種風險,您可以將已刪除的欄位名稱新增至 reserved
清單中。
保留的名稱僅影響 protoc 編譯器行為,而不影響執行階段行為,但有一個例外:TextProto 實作可能會在解析時捨棄具有保留名稱的未知欄位(不會像其他未知欄位一樣引發錯誤)(目前只有 C++ 和 Go 實作會這樣做)。執行階段 JSON 解析不受保留名稱的影響。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
請注意,您不能在同一個 reserved
陳述式中混合欄位名稱和欄位編號。
從您的 .proto
產生什麼?
當您在 .proto
上執行協定緩衝區編譯器時,編譯器會使用您在檔案中描述的訊息類型,以您選擇的語言產生程式碼,包括取得和設定欄位值、將訊息序列化為輸出串流,以及從輸入串流解析訊息。
- 對於 C++,編譯器會從每個
.proto
產生一個.h
和.cc
檔案,其中包含檔案中描述的每個訊息類型的類別。 - 對於 Java,編譯器會產生一個
.java
檔案,其中包含每個訊息類型的類別,以及一個特殊的Builder
類別,用於建立訊息類別實例。 - 對於 Kotlin,除了產生的 Java 程式碼之外,編譯器還會針對每個訊息類型產生一個
.kt
檔案,其中包含改進的 Kotlin API。這包括簡化建立訊息實例的 DSL、可為 null 的欄位存取器和複製函數。 - Python 有點不同 – Python 編譯器會產生一個模組,其中包含您的
.proto
中每個訊息類型的靜態描述符,然後使用元類別在執行階段建立必要的 Python 資料存取類別。 - 對於 Go,編譯器會產生一個
.pb.go
檔案,其中包含檔案中每個訊息類型的類型。 - 對於 Ruby,編譯器會產生一個
.rb
檔案,其中包含包含訊息類型的 Ruby 模組。 - 對於 Objective-C,編譯器會從每個
.proto
產生一個pbobjc.h
和pbobjc.m
檔案,其中包含檔案中描述的每個訊息類型的類別。 - 對於 C#,編譯器會從每個
.proto
產生一個.cs
檔案,其中包含檔案中描述的每個訊息類型的類別。 - 對於 PHP,編譯器會為您的檔案中描述的每個訊息類型產生一個
.php
訊息檔案,並為您編譯的每個.proto
檔案產生一個.php
中繼資料檔案。中繼資料檔案用於將有效的訊息類型載入描述符池中。 - 對於 Dart,編譯器會產生一個
.pb.dart
檔案,其中包含檔案中每個訊息類型的類別。
您可以透過遵循您選擇的語言的教學課程,找到有關使用每種語言的 API 的更多資訊。如需更多 API 詳細資訊,請參閱相關的API 參考。
純量值類型
純量訊息欄位可以具有下列類型之一 – 下表顯示了 .proto
檔案中指定的類型,以及自動產生的類別中對應的類型
.proto 類型 | 附註 | C++ 類型 | Java/Kotlin 類型[1] | Python 類型[3] | Go 類型 | Ruby 類型 | C# 類型 | PHP 類型 | Dart 類型 | Rust 類型 |
---|---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | f64 | |
float | float | float | float | float32 | Float | float | float | double | f32 | |
int32 | 使用變長度編碼。對負數的編碼效率低下 – 如果您的欄位可能具有負值,請改用 sint32。 | int32 | int | int | int32 | Fixnum 或 Bignum(依需要) | int | integer | int | i32 |
int64 | 使用變長度編碼。對負數的編碼效率低下 – 如果您的欄位可能具有負值,請改用 sint64。 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
uint32 | 使用變長度編碼。 | uint32 | int[2] | int/long[4] | uint32 | Fixnum 或 Bignum(依需要) | uint | integer | int | u32 |
uint64 | 使用變長度編碼。 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
sint32 | 使用變長度編碼。帶正負號的 int 值。這些比常規 int32 更有效地編碼負數。 | int32 | int | int | int32 | Fixnum 或 Bignum(依需要) | int | integer | int | i32 |
sint64 | 使用變長度編碼。帶正負號的 int 值。這些比常規 int64 更有效地編碼負數。 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
fixed32 | 總是四個位元組。如果值通常大於 228,則比 uint32 更有效率。 | uint32 | int[2] | int/long[4] | uint32 | Fixnum 或 Bignum(依需要) | uint | integer | int | u32 |
fixed64 | 總是八個位元組。如果值通常大於 256,則比 uint64 更有效率。 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
sfixed32 | 總是四個位元組。 | int32 | int | int | int32 | Fixnum 或 Bignum(依需要) | int | integer | int | i32 |
sfixed64 | 總是八個位元組。 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | bool | |
string | 字串必須始終包含 UTF-8 編碼或 7 位元 ASCII 文字,並且長度不能超過 232。 | string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | ProtoString |
bytes | 可能包含任何任意位元組序列,長度不得超過 232。 | string | ByteString | str (Python 2) bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List | ProtoBytes |
[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 位元電腦上使用字串。
您可以在協定緩衝區編碼中找到有關在序列化訊息時如何編碼這些類型的更多資訊。
預設欄位值
當解析訊息時,如果編碼的訊息位元組不包含特定欄位,則存取已解析物件中的該欄位會傳回該欄位的預設值。預設值是類型特定的
- 對於字串,預設值為空字串。
- 對於位元組,預設值為空位元組。
- 對於布林值,預設值為 false。
- 對於數值類型,預設值為零。
- 對於訊息欄位,欄位未設定。其確切值取決於語言。如需詳細資訊,請參閱產生的程式碼指南。
- 對於列舉,預設值為第一個定義的列舉值,其必須為 0。請參閱列舉預設值。
重複欄位的預設值為空(通常是適當語言中的空清單)。
地圖欄位的預設值為空(通常是適當語言中的空地圖)。
請注意,對於隱式存在的純量欄位,一旦訊息被解析,就無法得知該欄位是被明確設定為預設值(例如,布林值被設定為 false
),還是根本沒有設定:在定義訊息類型時,您應該將這一點牢記在心。例如,如果當布林值設定為 false
時,您不希望預設也觸發某種行為,就不應該使用布林值來開啟該行為。另請注意,如果純量訊息欄位被設定為預設值,該值將不會在傳輸時序列化。如果浮點數或雙精度值被設定為 +0,則不會被序列化,但 -0 會被視為不同,並會被序列化。
請參閱您選擇的語言的程式碼產生指南,以了解有關在產生的程式碼中預設值如何運作的更多詳細資訊。
列舉
當您定義訊息類型時,您可能希望它的其中一個欄位只能擁有預定義的值列表中的一個值。例如,假設您想為每個 SearchRequest
新增一個 corpus
欄位,其中 corpus 可以是 UNIVERSAL
、WEB
、IMAGES
、LOCAL
、NEWS
、PRODUCTS
或 VIDEO
。您可以透過在您的訊息定義中新增一個 enum
,並為每個可能的值加上常數,來非常簡單地完成此操作。
在以下範例中,我們新增了一個名為 Corpus
的 enum
,其中包含所有可能的值,以及一個 Corpus
類型的欄位
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}
列舉預設值
SearchRequest.corpus
欄位的預設值為 CORPUS_UNSPECIFIED
,因為這是 enum 中定義的第一個值。
在 proto3 中,enum 定義中定義的第一個值必須為零值,並且應該命名為 ENUM_TYPE_NAME_UNSPECIFIED
或 ENUM_TYPE_NAME_UNKNOWN
。這是因為
也建議這個第一個預設值除了「此值未指定」之外,沒有任何語意含義。
列舉值別名
您可以透過為不同的 enum 常數指定相同的值來定義別名。為此,您需要將 allow_alias
選項設定為 true
。否則,當找到別名時,protocol buffer 編譯器會產生警告訊息。儘管所有別名值在反序列化期間都有效,但序列化時始終使用第一個值。
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a warning message.
ENAA_FINISHED = 2;
}
列舉器常數必須在 32 位元整數的範圍內。由於 enum
值在傳輸時使用 varint 編碼,因此負值效率低下,因此不建議使用。您可以在訊息定義中定義 enum
,如先前的範例所示,也可以在外部定義 – 這些 enum
可以在您的 .proto
檔案中的任何訊息定義中重複使用。您也可以使用在一個訊息中宣告的 enum
類型作為另一個訊息中的欄位類型,使用語法 _MessageType_._EnumType_
。
當您在使用 enum
的 .proto
上執行 protocol buffer 編譯器時,產生的程式碼將會有對應的 Java、Kotlin 或 C++ 的 enum
,或者一個用於為執行階段產生的類別中建立具有整數值的符號常數集的 Python 特殊 EnumDescriptor
類別。
重要
產生的程式碼可能受到語言特定限制,限制列舉器的數量(某種語言的數量可能只有數千個)。請檢閱您計劃使用的語言的限制。在反序列化期間,無法辨識的 enum 值將保留在訊息中,但當訊息被反序列化時,這種表示方式是與語言相關的。在支援具有超出指定符號範圍的值的開放 enum 類型的語言(例如 C++ 和 Go)中,未知的 enum 值會簡單地儲存為其基礎整數表示形式。在具有封閉 enum 類型的語言(例如 Java)中,使用 enum 中的一個案例來表示無法辨識的值,並且可以使用特殊的存取器來存取基礎整數。無論在哪種情況下,如果訊息被序列化,無法辨識的值仍將隨訊息一起序列化。
重要
有關 enum 應該如何運作以及它們目前在不同語言中如何運作的對比資訊,請參閱Enum 行為。有關如何在您的應用程式中使用訊息 enum
的更多資訊,請參閱您選擇的語言的程式碼產生指南。
保留值
如果您透過完全移除 enum 條目或將其註解掉來更新 enum 類型,未來的使用者可以在對類型進行自己的更新時重複使用數值。如果他們稍後載入相同的 .proto
的舊執行個體,可能會導致嚴重的問題,包括資料損壞、隱私錯誤等等。確保不會發生這種情況的一種方法是指定刪除條目的數值(和/或名稱,這也可能導致 JSON 序列化的問題)為 reserved
。如果任何未來的使用者嘗試使用這些識別符號,protocol buffer 編譯器將會發出抱怨。您可以使用 max
關鍵字指定保留的數值範圍直到最大可能值。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
請注意,您無法在同一個 reserved
宣告中混合欄位名稱和數值。
使用其他訊息類型
您可以使用其他訊息類型作為欄位類型。例如,假設您想在每個 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
檔案放在舊位置,以使用 import public
概念將所有匯入轉發到新的位置,而無需直接移動 .proto
檔案並在單一變更中更新所有呼叫位置。
請注意,public import 功能在 Java、Kotlin、TypeScript、JavaScript、GCL 以及使用 protobuf 靜態反射的 C++ 目標中不可用。
包含 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
協議編譯器會在協議編譯器命令列上使用 -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;
}
您可以根據需要巢狀訊息。在以下範例中,請注意,兩個名為 Inner
的巢狀類型是完全獨立的,因為它們是在不同的訊息內定義的
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;
}
}
}
更新訊息類型
如果現有的訊息類型不再滿足您的所有需求 – 例如,您希望訊息格式有一個額外的欄位 – 但您仍然希望使用使用舊格式建立的程式碼,請不要擔心!當您使用二進位傳輸格式時,更新訊息類型而不會破壞任何現有程式碼非常簡單。
注意
如果您使用 JSON 或 proto 文字格式來儲存您的 protocol buffer 訊息,您可以在您的 proto 定義中進行的變更會有所不同。請檢查Proto 最佳實務和以下規則
- 請勿變更任何現有欄位的欄位編號。「變更」欄位編號相當於刪除該欄位並新增一個具有相同類型的新欄位。如果您想重新編號欄位,請參閱刪除欄位的說明。
- 如果您新增新的欄位,使用您的「舊」訊息格式的程式碼序列化的任何訊息仍然可以由您新的產生程式碼解析。您應該記住這些元素的預設值,以便新程式碼可以正確地與舊程式碼產生的訊息互動。同樣地,由您新程式碼建立的訊息可以由您的舊程式碼解析:舊的二進位檔在解析時會直接忽略新欄位。請參閱未知欄位區段以了解詳細資訊。
- 可以移除欄位,只要更新的訊息類型不再使用該欄位編號即可。您可能想要改為重新命名欄位,或許加入「OBSOLETE_」前綴,或者將欄位編號設為保留,以便您
.proto
的未來使用者不會意外地重複使用該編號。 int32
、uint32
、int64
、uint64
和bool
都是相容的 – 這表示您可以在這些類型之間變更欄位,而不會破壞向前或向後相容性。如果從傳輸中解析的數字不符合對應的類型,您將獲得與您在 C++ 中將數字轉換為該類型相同的效果(例如,如果將 64 位元數字讀取為 int32,則它將截斷為 32 位元)。sint32
和sint64
彼此相容,但與其他整數類型不相容。- 只要位元組是有效的 UTF-8,
string
和bytes
就相容。 - 如果位元組包含訊息的編碼執行個體,則內嵌訊息與
bytes
相容。 fixed32
與sfixed32
相容,而fixed64
與sfixed64
相容。- 對於
string
、bytes
和訊息欄位,單數與repeated
相容。如果以重複欄位的序列化資料作為輸入,則期望此欄位為單數的用戶端,如果它是原始類型欄位,將會採用最後一個輸入值,或者如果它是訊息類型欄位,則會合併所有輸入元素。請注意,這對於數值類型(包括布林值和 enum)而言,通常不安全。數值類型的重複欄位預設會以 packed 格式序列化,當預期單數欄位時,將無法正確解析。 enum
在線路格式方面與int32
、uint32
、int64
和uint64
相容(請注意,如果值不符合,將會被截斷)。然而,請注意,當訊息反序列化時,客戶端程式碼可能會以不同的方式處理它們:例如,無法辨識的 proto3enum
值將會保留在訊息中,但是當訊息反序列化時,其表示方式取決於語言。整數欄位總是會保留它們的值。- 將單一
optional
欄位或擴充功能變更為新的oneof
的成員是二進位相容的,然而對於某些語言(特別是 Go),產生的程式碼的 API 將會以不相容的方式變更。因此,Google 不會在其公開 API 中進行此類變更,如 AIP-180 中所述。關於原始碼相容性的相同警告,如果確定沒有程式碼一次設定多個欄位,則將多個欄位移至新的oneof
中可能是安全的。將欄位移至現有的oneof
中是不安全的。同樣地,將單一欄位oneof
變更為optional
欄位或擴充功能是安全的。 - 在
map<K, V>
和對應的repeated
訊息欄位之間變更欄位是二進位相容的(請參閱下方的 地圖,以了解訊息佈局和其他限制)。然而,變更的安全性取決於應用程式:當反序列化和重新序列化訊息時,使用repeated
欄位定義的客戶端將會產生語意上相同的結果;然而,使用map
欄位定義的客戶端可能會重新排序項目,並捨棄具有重複鍵的項目。
未知欄位
未知欄位是格式正確的協定緩衝區序列化資料,代表剖析器無法辨識的欄位。例如,當舊的二進位檔剖析由具有新欄位的新二進位檔傳送的資料時,這些新欄位會成為舊二進位檔中的未知欄位。
Proto3 訊息會保留未知欄位,並在剖析期間和序列化輸出中包含它們,這與 proto2 的行為一致。
保留未知欄位
某些動作可能會導致未知欄位遺失。例如,如果您執行下列其中一項,未知欄位就會遺失
- 將 proto 序列化為 JSON。
- 逐一查看訊息中的所有欄位,以填入新的訊息。
若要避免遺失未知欄位,請執行下列操作
- 使用二進位格式;避免使用文字格式進行資料交換。
- 使用面向訊息的 API,例如
CopyFrom()
和MergeFrom()
,以複製資料,而不是逐欄位複製
TextFormat 有點特殊。序列化為 TextFormat 會使用其欄位編號列印未知欄位。但是,如果存在使用欄位編號的項目,則將 TextFormat 資料剖析回二進位 proto 會失敗。
Any
Any
訊息類型可讓您使用訊息作為內嵌類型,而無需其 .proto 定義。Any
包含任意序列化的訊息作為 bytes
,以及一個 URL,該 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 google::protobuf::Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
Oneof
如果您有一個具有許多單數欄位,且最多一次設定一個欄位的訊息,您可以使用 oneof 功能來強制執行此行為並節省記憶體。
Oneof 欄位類似於選用欄位,只是 oneof 中的所有欄位會共用記憶體,且最多一次只能設定一個欄位。設定 oneof 的任何成員會自動清除所有其他成員。您可以使用特殊方法 case()
或 WhichOneof()
來檢查 oneof 中設定的值(如果有的話),具體取決於您選擇的語言。
請注意,如果設定了多個值,則根據 proto 中的順序判斷的最後設定值將會覆寫所有先前的值。
oneof 欄位的欄位編號在封閉訊息中必須是唯一的。
使用 Oneof
若要在您的 .proto
中定義 oneof,請使用 oneof
關鍵字,後接您的 oneof 名稱,在本例中為 test_oneof
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然後,將您的 oneof 欄位新增至 oneof 定義。您可以新增任何類型的欄位,但 map
欄位和 repeated
欄位除外。如果您需要將 repeated 欄位新增至 oneof,則可以使用包含 repeated 欄位的訊息。
在您產生的程式碼中,oneof 欄位具有與常規欄位相同的 getter 和 setter。您還會取得一個特殊方法,用於檢查 oneof 中設定的值(如果有的話)。您可以在相關的 API 參考中找到更多關於您選擇的語言的 oneof API 的資訊。
Oneof 功能
設定 oneof 欄位會自動清除 oneof 的所有其他成員。因此,如果您設定多個 oneof 欄位,則只有您設定的最後一個欄位仍會具有值。
SampleMessage message; message.set_name("name"); CHECK_EQ(message.name(), "name"); // Calling mutable_sub_message() will clear the name field and will set // sub_message to a new instance of SubMessage with none of its fields set. message.mutable_sub_message(); CHECK(message.name().empty());
如果剖析器在線路上遇到同一 oneof 的多個成員,則在剖析的訊息中只會使用最後看到的成員。
oneof 不能為
repeated
。反射 API 適用於 oneof 欄位。
如果您將 oneof 欄位設定為預設值(例如將 int32 oneof 欄位設定為 0),則將會設定該 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()
兩個訊息,每個訊息最後都會有另一個訊息的 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_EQ(msg2.name(), "name");
向後相容性問題
新增或移除 oneof 欄位時請小心。如果檢查 oneof 的值傳回 None
/NOT_SET
,則可能表示 oneof 尚未設定,或已設定為不同版本的 oneof 中的欄位。由於無法知道線路上的未知欄位是否為 oneof 的成員,因此無法區分差異。
標籤重複使用問題
- 將單數欄位移入或移出 oneof:在訊息序列化和剖析後,您可能會遺失部分資訊(某些欄位將會被清除)。但是,您可以安全地將單一欄位移至新的 oneof 中,並且如果已知一次只會設定一個欄位,則可能可以移動多個欄位。如需更多詳細資訊,請參閱更新訊息類型。
- 刪除 oneof 欄位並將其新增回來:在訊息序列化和剖析後,這可能會清除您目前設定的 oneof 欄位。
- 分割或合併 oneof:這與移動單數欄位有類似的問題。
地圖
如果您想要建立關聯式地圖作為資料定義的一部分,協定緩衝區會提供一個方便的捷徑語法
map<key_type, value_type> map_field = N;
...其中 key_type
可以是任何整數或字串類型(因此,除了浮點類型和 bytes
之外的任何 純量類型)。請注意,列舉和 proto 訊息都不適用於 key_type
。value_type
可以是任何類型,但另一個地圖除外。
因此,舉例來說,如果您想要建立專案的地圖,其中每個 Project
訊息都與字串鍵相關聯,您可以像這樣定義它
map<string, Project> projects = 3;
地圖功能
- 地圖欄位不能為
repeated
。 - 地圖值的線路格式排序和地圖迭代排序是未定義的,因此您不能依賴您的地圖項目以特定順序排列。
- 在為
.proto
產生文字格式時,地圖會依鍵排序。數值鍵會依數值排序。 - 從線路剖析或合併時,如果有重複的地圖鍵,則會使用最後看到的鍵。從文字格式剖析地圖時,如果有重複的鍵,剖析可能會失敗。
- 如果您為地圖欄位提供鍵,但沒有提供值,則欄位序列化時的行為取決於語言。在 C++、Java、Kotlin 和 Python 中,會序列化類型的預設值,而在其他語言中則不會序列化任何值。
- 符號
FooEntry
不能與地圖foo
存在於相同的範圍中,因為FooEntry
已被地圖的實作使用。
產生的地圖 API 目前適用於所有支援的語言。您可以在相關的 API 參考中找到更多關於您選擇的語言的地圖 API 的資訊。
向後相容性
地圖語法在線路上等同於以下內容,因此不支援地圖的協定緩衝區實作仍然可以處理您的資料
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任何支援地圖的協定緩衝區實作都必須產生並接受可被較早定義接受的資料。
套件
您可以將選用的 package
指定符新增至 .proto
檔案,以防止協定訊息類型之間的名稱衝突。
package foo.bar;
message Open { ... }
然後,您可以在定義訊息類型的欄位時使用套件指定符
message Foo {
...
foo.bar.Open open = 1;
...
}
套件指定符影響產生程式碼的方式取決於您選擇的語言
- 在 C++ 中,產生的類別會包裝在 C++ 命名空間內。例如,
Open
將會在命名空間foo::bar
中。 - 在 Java 和 Kotlin 中,套件會用作 Java 套件,除非您在
.proto
檔案中明確提供option java_package
。 - 在 Python 中,會忽略
package
指令,因為 Python 模組會根據它們在檔案系統中的位置來組織。 - 在 Go 中,會忽略
package
指令,且產生的.pb.go
檔案位於以對應的go_proto_library
Bazel 規則命名的套件中。對於開放原始碼專案,您必須提供go_package
選項,或設定 Bazel-M
旗標。 - 在 Ruby 中,產生的類別會包裝在巢狀 Ruby 命名空間內,並轉換為所需的 Ruby 大小寫樣式(第一個字母大寫;如果第一個字元不是字母,則會加上
PB_
)。例如,Open
將會在命名空間Foo::Bar
中。 - 在 PHP 中,套件會用作轉換為 PascalCase 後的命名空間,除非您在
.proto
檔案中明確提供option php_namespace
。例如,Open
將會在命名空間Foo\Bar
中。 - 在 C# 中,套件會用作轉換為 PascalCase 後的命名空間,除非您在
.proto
檔案中明確提供option csharp_namespace
。例如,Open
將會在命名空間Foo.Bar
中。
請注意,即使 package
指令不會直接影響產生的程式碼,例如在 Python 中,仍然強烈建議為 .proto
檔案指定套件,否則可能會導致描述符中的命名衝突,並使 proto 無法移植到其他語言。
套件與名稱解析
協定緩衝區語言中的類型名稱解析就像 C++ 一樣:首先會搜尋最內層的範圍,然後搜尋次內層的範圍,依此類推,每個套件都被視為其父套件的「內部」。前導的 ‘.’(例如,.foo.bar.Baz
)表示從最外層範圍開始。
協定緩衝區編譯器會解析所有匯入的 .proto
檔案,藉此解析所有類型名稱。每種語言的程式碼產生器都知道如何在該語言中參照每個類型,即使其範圍規則不同也沒問題。
定義服務
如果您想將訊息類型用於 RPC(遠端程序呼叫)系統,您可以在 .proto
檔案中定義 RPC 服務介面,而協定緩衝區編譯器會以您選擇的語言產生服務介面程式碼和 Stub。舉例來說,如果您想定義一個 RPC 服務,其方法會接收 SearchRequest
並傳回 SearchResponse
,您可以在 .proto
檔案中依照下列方式定義:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
與協定緩衝區搭配使用的最直接 RPC 系統是 gRPC:這是一個在 Google 開發的、與語言和平台無關的開放原始碼 RPC 系統。gRPC 與協定緩衝區搭配運作特別良好,讓您可以使用特殊的協定緩衝區編譯器外掛程式,直接從您的 .proto
檔案產生相關的 RPC 程式碼。
如果您不想使用 gRPC,也可以將協定緩衝區與您自己的 RPC 實作搭配使用。您可以在Proto2 語言指南中找到更多相關資訊。
也有許多正在進行的第三方專案,旨在為協定緩衝區開發 RPC 實作。如需我們所知專案的連結清單,請參閱第三方附加元件 Wiki 頁面。
JSON 對應
標準 protobuf 二進位連線格式是使用 protobuf 的兩個系統之間進行通訊的首選序列化格式。為了與使用 JSON 而非 protobuf 連線格式的系統進行通訊,Protobuf 支援在JSON 中進行標準編碼。
選項
.proto
檔案中的個別宣告可以使用許多選項進行註解。選項不會變更宣告的整體意義,但可能會影響在特定內容中處理宣告的方式。可用的完整選項清單定義於 /google/protobuf/descriptor.proto
。
有些選項是檔案層級的選項,這表示它們應寫在最上層範圍,而非任何訊息、列舉或服務定義內。有些選項是訊息層級的選項,這表示它們應寫在訊息定義內。有些選項是欄位層級的選項,這表示它們應寫在欄位定義內。選項也可以寫在列舉類型、列舉值、oneof 欄位、服務類型和服務方法上;不過,目前沒有任何適用於這些的實用選項。
以下是一些最常用的選項:
java_package
(檔案選項):您想用於產生的 Java/Kotlin 類別的套件。如果.proto
檔案中沒有提供明確的java_package
選項,則預設會使用協定套件(使用.proto
檔案中的「package」關鍵字指定)。不過,協定套件通常不會構成良好的 Java 套件,因為協定套件預期不會以反向網域名稱開頭。如果未產生 Java 或 Kotlin 程式碼,則此選項沒有任何作用。option java_package = "com.example.foo";
java_outer_classname
(檔案選項):您想要產生之包裝 Java 類別的類別名稱(因此也是檔案名稱)。如果未在.proto
檔案中指定明確的java_outer_classname
,則類別名稱將透過將.proto
檔案名稱轉換為駝峰式大小寫來建構(因此foo_bar.proto
會變成FooBar.java
)。如果java_multiple_files
選項已停用,則針對.proto
檔案產生的所有其他類別/列舉等等,都將在此外層包裝 Java 類別內產生,作為巢狀類別/列舉等等。如果未產生 Java 程式碼,則此選項沒有任何作用。option java_outer_classname = "Ponycopter";
java_multiple_files
(檔案選項):如果為 false,則只會針對此.proto
檔案產生單一.java
檔案,而且針對最上層訊息、服務和列舉產生的所有 Java 類別/列舉等等,都會巢狀在外部類別內(請參閱java_outer_classname
)。如果為 true,則會針對針對最上層訊息、服務和列舉產生的每個 Java 類別/列舉等等產生個別的.java
檔案,而為此.proto
檔案產生的包裝 Java 類別將不包含任何巢狀類別/列舉等等。這是一個布林選項,預設值為false
。如果未產生 Java 程式碼,則此選項沒有任何作用。option java_multiple_files = true;
optimize_for
(檔案選項):可以設為SPEED
、CODE_SIZE
或LITE_RUNTIME
。這會以下列方式影響 C++ 和 Java 程式碼產生器(以及可能影響第三方產生器):SPEED
(預設):協定緩衝區編譯器會產生程式碼,以序列化、剖析訊息類型,並對其執行其他常見作業。此程式碼已高度最佳化。CODE_SIZE
:協定緩衝區編譯器會產生最小的類別,並依賴共用、以反射為基礎的程式碼來實作序列化、剖析及各種其他作業。產生的程式碼因此會比使用SPEED
時小得多,但作業速度會較慢。類別仍將實作與SPEED
模式中完全相同的公用 API。此模式最適用於包含大量.proto
檔案,且不需要全部都非常快速的應用程式。LITE_RUNTIME
:協定緩衝區編譯器會產生只依賴「輕量」執行階段程式庫(libprotobuf-lite
而不是libprotobuf
)的類別。輕量執行階段比完整程式庫小得多(約小一個數量級),但省略了某些功能,例如描述元和反射。這對於在行動電話等受限平台上執行的應用程式特別有用。編譯器仍會像在SPEED
模式中一樣產生所有方法的快速實作。產生的類別在每種語言中只會實作MessageLite
介面,此介面僅提供完整Message
介面的子集方法。
option optimize_for = CODE_SIZE;
cc_generic_services
、java_generic_services
、py_generic_services
(檔案選項):一般服務已遭淘汰。協定緩衝區編譯器是否應分別根據 C++、Java 和 Python 中的服務定義產生抽象服務程式碼。基於舊版原因,這些選項預設為true
。不過,自版本 2.3.0(2010 年 1 月)起,RPC 實作最好能提供程式碼產生器外掛程式,以產生更特定於每個系統的程式碼,而不是依賴「抽象」服務。// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
cc_enable_arenas
(檔案選項):針對 C++ 產生的程式碼啟用記憶體配置。objc_class_prefix
(檔案選項):設定 Objective-C 類別前置字元,該字元會加到從此 .proto 產生之所有 Objective-C 類別和列舉的前面。沒有預設值。您應該使用介於 3-5 個大寫字元之間的前置字元,如 Apple 建議。請注意,所有 2 個字母的前置字元都由 Apple 保留。packed
(欄位選項):預設在基本數值類型的重複欄位上為true
,導致使用更精簡的編碼。若要使用未封裝的連線格式,可以將其設定為false
。這會提供與 2.3.0 版之前剖析器的相容性(很少需要),如下列範例所示:repeated int32 samples = 4 [packed = false];
deprecated
(欄位選項):如果設定為true
,表示此欄位已遭淘汰,新程式碼不應使用。在大多數語言中,這沒有實際效果。在 Java 中,這會變成@Deprecated
註解。對於 C++,只要使用已淘汰的欄位,clang-tidy 就會產生警告。未來,其他特定於語言的程式碼產生器可能會在欄位的存取子產生淘汰註解,這反過來會導致在編譯嘗試使用欄位的程式碼時發出警告。如果沒有人使用該欄位,而且您想阻止新使用者使用該欄位,請考慮將欄位宣告取代為保留陳述式。int32 old_field = 6 [deprecated = true];
列舉值選項
支援列舉值選項。您可以使用 deprecated
選項來表示不應再使用值。您也可以使用擴充功能建立自訂選項。
以下範例顯示新增這些選項的語法:
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Data {
DATA_UNSPECIFIED = 0;
DATA_SEARCH = 1 [deprecated = true];
DATA_DISPLAY = 2 [
(string_name) = "display_value"
];
}
讀取 string_name
選項的 C++ 程式碼可能如下所示:
const absl::string_view foo = proto2::GetEnumDescriptor<Data>()
->FindValueByName("DATA_DISPLAY")->options().GetExtension(string_name);
請參閱自訂選項,以了解如何將自訂選項套用至列舉值和欄位。
自訂選項
協定緩衝區也允許您定義和使用自己的選項。請注意,這是一項進階功能,大多數人都不需要。如果您確實認為需要建立自己的選項,請參閱Proto2 語言指南以取得詳細資訊。請注意,建立自訂選項會使用擴充功能,這些擴充功能僅適用於 proto3 中的自訂選項。
選項保留
選項具有保留的概念,可控制選項是否保留在產生的程式碼中。選項預設具有執行階段保留,這表示它們保留在產生的程式碼中,因此在產生的描述元集區中於執行階段可見。不過,您可以設定 retention = RETENTION_SOURCE
,以指定選項(或選項內的欄位)不得在執行階段保留。這稱為來源保留。
選項保留是一項進階功能,大多數使用者不需要擔心,但如果您想使用某些選項而又不想支付將它們保留在二進位檔中的程式碼大小成本,則它可能會很有用。具有來源保留的選項對 protoc
和 protoc
外掛程式仍然可見,因此程式碼產生器可以使用它們來自訂其行為。
可以直接在選項上設定保留,如下所示:
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 1234
[retention = RETENTION_SOURCE];
}
也可以在一般欄位上設定保留,在這種情況下,保留只在該欄位出現在選項內部時才會生效:
message OptionsMessage {
int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}
如果您願意,可以設定 retention = RETENTION_RUNTIME
,但這沒有任何作用,因為這是預設行為。當訊息欄位標記為 RETENTION_SOURCE
時,其所有內容都會被捨棄;其中的欄位無法嘗試設定 RETENTION_RUNTIME
來覆寫該設定。
注意
自協定緩衝區 22.0 起,對選項保留的支援仍在進行中,且僅支援 C++ 和 Java。Go 從 1.29.0 開始支援。Python 支援已完成,但尚未發佈。選項目標
欄位具有 targets
選項,可控制欄位在用作選項時可能套用至的實體類型。例如,如果欄位具有 targets = TARGET_TYPE_MESSAGE
,則無法在列舉(或任何其他非訊息實體)的自訂選項中設定該欄位。Protoc 會強制執行此操作,如果違反目標限制,則會引發錯誤。
乍看之下,這個功能可能看起來是不必要的,因為每個自訂選項都是特定實體選項訊息的擴充功能,這已將選項限制為該一個實體。不過,當您有多個實體類型套用的共用選項訊息,且您想控制該訊息中個別欄位的使用時,選項目標會很有用。例如:
message MyOptions {
string file_only_option = 1 [targets = TARGET_TYPE_FILE];
int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_ENUM];
}
extend google.protobuf.FileOptions {
optional MyOptions file_options = 50000;
}
extend google.protobuf.MessageOptions {
optional MyOptions message_options = 50000;
}
extend google.protobuf.EnumOptions {
optional MyOptions enum_options = 50000;
}
// OK: this field is allowed on file options
option (file_options).file_only_option = "abc";
message MyMessage {
// OK: this field is allowed on both message and enum options
option (message_options).message_and_enum_option = 42;
}
enum MyEnum {
MY_ENUM_UNSPECIFIED = 0;
// Error: file_only_option cannot be set on an enum.
option (enum_options).file_only_option = "xyz";
}
產生您的類別
若要產生您在 .proto
檔案中定義的訊息類型所需的 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C# 程式碼,您需要在 .proto
檔案上執行 Protocol Buffer 編譯器 protoc
。 如果您尚未安裝編譯器,請下載套件並按照 README 中的說明進行操作。對於 Go,您還需要為編譯器安裝特殊的程式碼產生器外掛程式;您可以在 GitHub 上的 golang/protobuf 儲存庫中找到此外掛程式和安裝說明。
Protocol 編譯器的呼叫方式如下:
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 --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 產生程式碼參考以了解更多資訊。--kotlin_out
在DST_DIR
中產生額外的 Kotlin 程式碼。請參閱Kotlin 產生程式碼參考以了解更多資訊。--python_out
在DST_DIR
中產生 Python 程式碼。請參閱Python 產生程式碼參考以了解更多資訊。--go_out
在DST_DIR
中產生 Go 程式碼。請參閱Go 產生程式碼參考以了解更多資訊。--ruby_out
在DST_DIR
中產生 Ruby 程式碼。請參閱Ruby 產生程式碼參考以了解更多資訊。--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
輸出也會按照 Java JAR 規範的要求提供 manifest 檔案。請注意,如果輸出封存檔案已存在,則會被覆寫。您必須提供一個或多個
.proto
檔案作為輸入。可以一次指定多個.proto
檔案。雖然檔案是相對於目前目錄命名的,但每個檔案都必須位於其中一個IMPORT_PATH
中,以便編譯器可以確定其規範名稱。
檔案位置
最好不要將 .proto
檔案放在與其他語言來源程式碼相同的目錄中。考慮在專案的根套件下為 .proto
檔案建立一個子套件 proto
。
位置應與語言無關
當使用 Java 程式碼時,將相關的 .proto
檔案放在與 Java 來源程式碼相同的目錄中很方便。但是,如果任何非 Java 程式碼使用相同的 proto,則路徑前綴將不再有意義。因此,一般而言,請將 proto 放在相關的語言獨立目錄中,例如 //myteam/mypackage
。
此規則的例外情況是當 proto 明確僅在 Java 環境中使用時,例如用於測試。
支援的平台
有關以下資訊:
- 支援的作業系統、編譯器、建置系統和 C++ 版本,請參閱基礎 C++ 支援政策。
- 支援的 PHP 版本,請參閱支援的 PHP 版本。