1 Preface
protobuf 提供了 oneof 语义,表示任选其一;类似于C语言的 union
关键字。
于是想了解下 oneof 语义在 golang 中是如何实现的,下面我们来一探究竟。 具体的用法如下:
1
2
3
4
5
6
7
8
9
10
11
|
message WechatPay {
int64 uuid = 1;
}
message HelloRequest {
string msg = 1;
oneof one_of_pay {
string noop = 2;
WechatPay wx = 3;
}
}
|
2 stub 代码分析
使用 protoc 工具会生成如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 定义一个接口,用于类型断言
type isHelloRequest_OneOfPay interface {
isHelloRequest_OneOfPay()
}
type HelloRequest_Noop struct {
Noop string `protobuf:"bytes,2,opt,name=noop,proto3,oneof"`
}
type HelloRequest_Wx struct {
Wx *WechatPay `protobuf:"bytes,3,opt,name=wx,proto3,oneof"`
}
// 接口实现 empty
// implements isHelloRequest_OneOfPay interface
func (*HelloRequest_Noop) isHelloRequest_OneOfPay() {}
// 接口实现 empty
// implements isHelloRequest_OneOfPay interface
func (*HelloRequest_Wx) isHelloRequest_OneOfPay() {}
|
3 总结
在 Golang 中, oneof 语义基于一个类型接口,oneof 的每个成员都实现这个接口,方便在GetXX() 方法中进行类型断言,向下转换(cast)拿到具体的对象。
4 更上一层楼
问题:oneof 对象在序列化 json时,外面会包一层 “oneof”:{…} 吗?
通过实践,。
方式一:go 标准库的 json.Marshal()
,会多一层 “oneof”:{…}
1
2
|
data, err := json.Marshal(req)
{"OneOfPay":{"Wx":{"id":100,"name":"wechat"}}}
|
方式二:protobuf 自带的 json 序列化,不会包一层 “oneof”:{…}
1
2
3
4
|
req.OneOfPay = &pb.HelloRequest_Noop{Noop: "Bob"}
// protojson.Marshal(req) => {"noop":"Bob"}
req.OneOfPay = &pb.HelloRequest_Ali{Ali: &pb.AliPay{Id: 100, Name: "ali"}}
// protojson.Marshal(req) => {"wx":{"id":"100", "name":"wechat"}}
|
同时: protobuf 在序列化 json 时,会将 int64 转为 string(为了兼容js)。
正是由于这个特性存在,序列化后的 json 在 Unmarshal()回 pb struct 时,如果包含int64,则会报错!
参考