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,则会报错!

参考