来自公众号:新世界杂货铺
这两天翻了翻以前的我的项目,发现不同我的项目中对于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 jsonot1 := 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 failedjsonStr := `{"bar":1,"st":{"Show":{"is_show":1}}}`var ot2 oneof.Testfmt.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}}}`// 可失常转jsonvar wra2 wrapper.Testfmt.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/protocolbu...。上面持续看看该计划的json序列化成果
var isShow int32 = 1p3o1 := p3optional.Test{ Bar: 1, St: &p3optional.Status{IsShow: &isShow},}bts, err = json.Marshal(p3o1)fmt.Println(string(bts), err)var p3o2 p3optional.TestjsonStr = `{"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联合应用的野路子。
// proto2message Status { optional int32 is_show = 2;}// proto3message 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 jsonp3p21 := p3p2.Test{ Bar: 1, St: &p3p2.Status{IsShow: &isShow},}bts, err = json.Marshal(p3p21)fmt.Println(string(bts), err)var p3p22 p3p2.TestjsonStr = `{"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当前不会再更新了,所以此计划还请大家酌情应用”。
最初,衷心希望本文可能对各位读者有肯定的帮忙。
注:
- 文中笔者所用go版本为:go1.15.2
- 文中笔者所用protoc版本为:3.14.0
- 文章中所用残缺例子:https://github.com/Isites/go-...