字段缺失_区分Protobuf 3中缺失值和默认值
這兩天翻了翻以前的項目,發現不同項目中關于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 數據類型的效果,但是諸多大佬還是發現它可以標識缺失字段。
message Status {oneof show {int32 is_show = 1;} } message Test {int32 bar = 1;Status st = 2; }上述proto文件生成對應go文件后,Test.St為Status的指針類型,故通過此方案可以區分默認值和缺失字段。但是筆者認為此方案做json序列化時十分不友好,下面是筆者的例子:
// oneof to json ot1 := oneof.Test{Bar: 1,St: &oneof.Status{Show: &oneof.Status_IsShow{IsShow: 1,},}, } bts, err := json.Marshal(ot1) fmt.Println(string(bts), err) // json to oneof failed jsonStr := `{"bar":1,"st":{"Show":{"is_show":1}}}` var ot2 oneof.Test fmt.Println(json.Unmarshal([]byte(jsonStr), &ot2))上述輸出結果如下:
{"bar":1,"st":{"Show":{"is_show":1}}} <nil> json: cannot unmarshal object into Go struct field Status.st.Show of type oneof.isStatus_Show通過上述輸出知,oneof的json.Marshal輸出結果會額外多一層,而json.Unmarshal還會失敗,因此使用oneof時需謹慎。
使用wrapper類型
這應該是google官方提出的解決方案,我們看看下面的例子:
import "google/protobuf/wrappers.proto"; message Status {google.protobuf.Int32Value is_show = 1; } message Test {int32 bar = 1;Status st = 2; }使用此方案需要引入google/protobuf/wrappers.proto。此方案生成對應go文件后,Test.St也是Status的指針類型。同樣,我們也看一下它的json序列化效果:
wra1 := wrapper.Test{Bar: 1,St: &wrapper.Status{IsShow: wrapperspb.Int32(1),}, } bts, err = json.Marshal(wra1) fmt.Println(string(bts), err) jsonStr = `{"bar":1,"st":{"is_show":{"value":1}}}` // 可正常轉json var wra2 wrapper.Test fmt.Println(json.Unmarshal([]byte(jsonStr), &wra2))上述輸出結果如下:
{"bar":1,"st":{"is_show":{"value":1}}} <nil> <nil>和oneof方案相比wrapper方案的json反序列化是沒問題的,但是json.Marshal的輸出結果也會額外多一層。另外,經筆者在本地試驗,此方案無法和gogoproto一起使用。
允許proto3使用optional標簽
前面幾個方案估計在實踐中還是不夠盡善盡美。于是2020年5月16日protoc v3.12.0發布,該編譯器允許proto3的字段也可使用 optional修飾。
下面看看例子:
message Status {optional int32 is_show = 1; } message Test {int32 bar = 1;Status st = 2; }此方案需要使用新版本的protoc且必須使用--experimental_allow_proto3_optional開啟此特性。protoc升級教程見https://github.com/protocolbuffers/protobuf#protocol-compiler-installation。下面繼續看看該方案的json序列化效果
var isShow int32 = 1 p3o1 := p3optional.Test{Bar: 1,St: &p3optional.Status{IsShow: &isShow}, } bts, err = json.Marshal(p3o1) fmt.Println(string(bts), err) var p3o2 p3optional.Test jsonStr = `{"bar":1,"st":{"is_show":1}}` fmt.Println(json.Unmarshal([]byte(jsonStr), &p3o2))上述輸出結果如下:
{"bar":1,"st":{"is_show":1}} <nil> <nil>據上述結果知,此方案與oneof以及wrapper方案的json序列化相比更加符合預期,同樣,經筆者在本地試驗,此方案無法和gogoproto一起使用。
proto2和proto3結合使用
作為一個gogoproto的忠實用戶,筆者希望在能區分默認值和缺失值的同時還可以繼續使用gogoproto的特性。于是便產生了proto2和proto3結合使用的野路子。
// proto2 message Status {optional int32 is_show = 2; } // proto3 message Test {int32 bar = 1 [(gogoproto.moretags) = 'form:"more_bar"', (gogoproto.jsontag) = 'custom_tag'];p3p2.Status st = 2; }需要區分缺失字段和默認值的message定義在語法為proto2的文件中,proto3通過import導入proto2的message以達區分目的。
optional修飾的字段在Go中會生成指針類型,因此區分缺失值和默認值就變的十分容易了。下面看看此方案的json序列化效果:
// p3p2 to json p3p21 := p3p2.Test{Bar: 1,St: &p3p2.Status{IsShow: &isShow}, } bts, err = json.Marshal(p3p21) fmt.Println(string(bts), err) var p3p22 p3p2.Test jsonStr = `{"custom_tag":1,"st":{"is_show":1}}` fmt.Println(json.Unmarshal([]byte(jsonStr), &p3p22))上述輸出結果如下:
{"custom_tag":1,"st":{"is_show":1}} <nil> <nil>根據上述結果知,此方案不僅能夠活用gogoproto的各種tag,其結果也和在proto3中直接使用optional效果一致。雖然筆者已經在自己的項目中使用了此方案,但是仍然要提醒一句:“寫本篇文章時,筆者特意去github看了gogoproto的發布日志,gogoproto最新一個版本發布時間為2019年10月14日,筆者大膽預言gogoproto以后不會再更新了,所以此方案還請大家酌情使用”。
最后,衷心希望本文能夠對各位讀者有一定的幫助。
注:1. 文中筆者所用go版本為:go1.15.2
2. 文中筆者所用protoc版本為:3.14.0
3. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/pbjson/main.go
總結
以上是生活随笔為你收集整理的字段缺失_区分Protobuf 3中缺失值和默认值的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: keyshot卡住了还能保存吗_相机希望
- 下一篇: 充电提示音_iPhone如何自定义充电提