Protobuf3 Any類型
Any消息類型允許您將消息作為嵌入類型,而不需要它們 .proto定義。Any包含任意序列化的消息(字節),以及一個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.
不同的語言實現將支持運行時庫助手以typesafe方式打包和解壓縮ANY類型的值——例如,在Java中,任何類型都有特殊的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類型會替換擴展名。
Protobuf3 Oneof
如果您有一條包含許多字段的消息,並且最多同時設置一個字段,您可以使用其中oneof功能來強制執行此行為並節省內存。
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定義中。您可以添加任何類型的字段,但不能使用重復字段。
在生成的代碼中,oneof字段具有與常規字段相同的setter和getter方法。您還可以獲得一種特殊的方法來檢查中的哪個值(如果有的話)被設置。你可以在相關的API參考中找到更多關於你選擇的語言的API。
Oneof功能
設置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不能重復。
反射APIs適用於oneof字段。
如果您正在使用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消息,每條消息將以另一條的oneof結尾:在下面的示例中,msg1將有一條子消息,msg2將有一個名字。
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的成員。
標簽重用問題
將字段移入或移出:在序列化和解析消息后,您可能會丟失一些信息(一些字段將被清除)。但是,您可以安全地將單個字段移動到新的字段中,如果已知只有一個字段被設置,您也可以移動多個字段。
刪除一個字段並將其添加回去:這可能會在消息序列化和解析后清除當前設置的一個字段。
拆分或合並其中一個:這與移動常規字段有類似的問題。
Protobuf3 Maps
如果要創建關聯映射作為數據定義的一部分,協議緩沖區提供了一種方便快捷的語法:
map<key_type, value_type> map_field = N;
其中key_type可以是任何整數或字符串類型(除浮點類型和字節以外的任何標量類型)。請注意,枚舉不是有效的key_type,value_type可以是除另一個映射之外的任何類型。
因此,例如,如果您想為一個項目創建map,其中每個項目消息都與一個字符串鍵相關聯,您可以這樣定義它:
map<string, Project> projects = 3;
映射字段不能重復。
map值線格式排序和map迭代排序未定義,因此您不能依賴於項目的map特定順序。
為生成文本格式時。proto,地圖按鍵排序。數字鍵按數字排序。
為.proto生成文本格式時,map按鍵排序。數字鍵按數字排序。
從線上解析或合並時,如果有重復的map鍵,則使用最后看到的鍵。從文本格式解析map時,如果存在重復的鍵,解析可能會失敗。
如果為映射字段提供了鍵但沒有值,則序列化該字段時的行為取決於語言。在c++、Java和Python中,該類型的默認值是序列化的,而在其他語言中,沒有任何值是序列化的。
生成的map API目前可用於所有受支持的proto3語言。你可以在相關的API參考中找到更多關於你選擇的語言的map API。
向后兼容性
map語法相當於線上的以下內容,因此不支持map的協議緩沖區實現仍然可以處理數據:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
任何支持映射的協議緩沖區實現都必須生成和接受可以被上述定義的數據。