來自公眾號:新世界雜貨鋪
這兩天翻了翻以前的項目,發現不同項目中關於Protobuf 3缺失值和默認值的區分居然有好幾種實現。今天筆者冷飯新炒,結合項目中的實現以及切身經驗共總結出如下六種方案。
增加標識欄位
眾所周知,在Go中數字類型的默認值為0(這裡僅以數字類型舉例),這在某些場景下往往會引起一定的歧義。
以is_show欄位為例,如果沒有該欄位表示不更新DB中的數據,如果有該欄位且值為0則表示更新DB中的數據為不可見,如果有該欄位且值為1則表示更新DB中的數據為可見。
上述場景中,實際要解決的問題是如何區分默認值和缺失欄位。增加標識欄位是通過額外增加一個欄位來達到區分的目的。
例如:增加一個has_show_field欄位標識is_show是否為有效值。如果has_show_field為true則is_show為有效值,否則認為is_show未設置值。
此方案雖然直白,但每次設置is_show的值時還需設置has_show_field的值,甚是麻煩故筆者十分不推薦。
欄位含義和默認值區分
欄位含義和默認值區分即不使用對應類型的默認值作為該欄位的有效值。接著前面的例子繼續描述,is_show為1時表示展示,is_show為2時表示不展示,其他情況則認為is_show未設置值。
此方案筆者還是比較認可的,唯一問題就是和開發者的默認習慣略微不符。
使用oneof
oneof 的用意是達到 C 語言 union 數據類型的效果,但是諸多大佬還是發現它可以標識缺失欄位。
上述proto文件生成對應go文件後,Test.St為Status的指針類型,故通過此方案可以區分默認值和缺失欄位。但是筆者認為此方案做json序列化時十分不友好,下面是筆者的例子:
上述輸出結果如下:
通過上述輸出知,oneof的json.Marshal輸出結果會額外多一層,而json.Unmarshal還會失敗,因此使用oneof時需謹慎。
使用wrapper類型
這應該是google官方提出的解決方案,我們看看下面的例子:
使用此方案需要引入google/protobuf/wrappers.proto。此方案生成對應go文件後,Test.St也是Status的指針類型。同樣,我們也看一下它的json序列化效果:
上述輸出結果如下:
和oneof方案相比wrapper方案的json反序列化是沒問題的,但是json.Marshal的輸出結果也會額外多一層。另外,經筆者在本地試驗,此方案無法和gogoproto一起使用。
允許proto3使用optional標籤
前面幾個方案估計在實踐中還是不夠盡善盡美。於是2020年5月16日protoc v3.12.0發布,該編譯器允許proto3的欄位也可使用 optional修飾。
下面看看例子:
此方案需要使用新版本的protoc且必須使用--experimental_allow_proto3_optional開啟此特性。protoc升級教程見https://github.com/protocolbuffers/protobuf#protocol-compiler-installation。下面繼續看看該方案的json序列化效果
上述輸出結果如下:
據上述結果知,此方案與oneof以及wrapper方案的json序列化相比更加符合預期,同樣,經筆者在本地試驗,此方案無法和gogoproto一起使用。
proto2和proto3結合使用
作為一個gogoproto的忠實用戶,筆者希望在能區分默認值和缺失值的同時還可以繼續使用gogoproto的特性。於是便產生了proto2和proto3結合使用的野路子。
需要區分缺失欄位和默認值的message定義在語法為proto2的文件中,proto3通過import導入proto2的message以達區分目的。
optional修飾的欄位在Go中會生成指針類型,因此區分缺失值和默認值就變的十分容易了。下面看看此方案的json序列化效果:
上述輸出結果如下:
根據上述結果知,此方案不僅能夠活用gogoproto的各種tag,其結果也和在proto3中直接使用optional效果一致。雖然筆者已經在自己的項目中使用了此方案,但是仍然要提醒一句:「寫本篇文章時,筆者特意去github看了gogoproto的發布日誌,gogoproto最新一個版本發布時間為2019年10月14日,筆者大膽預言gogoproto以後不會再更新了,所以此方案還請大家酌情使用」。
最後,衷心希望本文能夠對各位讀者有一定的幫助。
註:文中筆者所用go版本為:go1.15.2文中筆者所用protoc版本為:3.14.0文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/pbjson/main.go