枚举与扩展信息
protobuf枚举字段的读写
使用protobuf时,我们可以在把枚举值定义在proto文件中,定义protobuf枚举的时候,需要注意以下几个事项:
- 不要把0定义成有业务语义的枚举
- proto2版本里,需要将0定义成一个无业务语义的枚举
这是因为proto2 与 proto3对于枚举的默认行为不同。举例来说,对于EnumType这个枚举,在程序A编译运行时,引用了版本A的proto文件。后续proto更新,EnumType这个枚举中,增加了第三个枚举值ENUM_THREE。并引用版本B的proto文件,编译运行了程序B。此时程序A和程序B使用protobuf传输数据时,就会发生异常。
syntax = "proto2";
package demo;
// 版本A:
enum EnumType {
ENUM_ONE = 1;
ENUM_TWO = 2;
}
// 版本B:
enum EnumType {
ENUM_ONE = 1;
ENUM_TWO = 2;
ENUM_THREE = 3;
}
message EnumMessage {
optional EnumType enum_type = 1;
}
此时程序A和程序B使用了两个不同版本的proto文件,当程序B向程序A发送一个EnumMessage,并且将enum_type字段赋值为:ENUM_THREE。当程序A收到程序B发送的二进制数据,并反序列化为EnumMessage时,由于程序A并不认识ENUM_THREE这个新增枚举,因此:
- 在proto2中,陌生枚举值会被反序列化对应枚举类型的第一个枚举。ENUM_THREE这个枚举值会被反序列化为EnumType的第一个枚举值——ENUM_ONE;
- 在proto3中,陌生枚举值会被反序列化0。ENUM_THREE这个枚举值会被反序列化为0
因此定义枚举时,最好把枚举值0定义成UNKOWN枚举
枚举的API
- 判断是否是有效枚举:
XXXX_IsValid
例如要判断EnumType的类型是不是有效,可以使用EnumType_IsValid进行判断:
demo::EnumMessage enum_message;
if (demo::EnumType_IsValid(enum_message.enum_type())) {
// do something
}
XXXX_IsValid函数将会根据输入的参数的值返回true或者false,如果输入的值在枚举中有定义,那么将返回true,否则返回false。在proto2中,由于通常要把枚举值0定义成未知枚举,因为XXXX_IsValid函数也会把0判断成一个有效的枚举值。
- 获取枚举的最大值和最小值:
XXXX_MAX``````XXXX_MIN
通过MAX和MIN函数可以获取到某个枚举类型定义的最大枚举和最小枚举值
给枚举添加扩展信息
有时候,我们需要在代码中把枚举值转义成文案,最简单的做法是像下面这样,把枚举值和文案硬编码到代码中:
std::string EnumToString(uint32_t enum_value) {
std::map<uint32_t, std::string> enum_map = {
{ 1, "第一个枚举值"},
{ 2, "第二个枚举值"},
}
if (enum_map.count(enum_value)) {
return enum_map.at(enum_value);
}
return "";
}
上面这样代码的可维护性比较差,在proto2中,我们可以在定义枚举值时添加扩展信息,就把对应的文案绑定到对应的枚举值中,最后使用protobuf的反射读取扩展信息中的枚举值。
我们可以这样定义一个protobuf枚举:
syntax = "proto2";
package demo;
import "google/protobuf/descriptor.proto";
// 定义扩展信息
extend google.protobuf.EnumValueOptions {
optional string EnumDesc = 50001; // 中文文案
}
enum EnumType {
ENUM_UNKOWN = 0;
ENUM_ONE = 1[(EnumDesc) = "第一个枚举值"];
ENUM_ONE = 1[(EnumDesc) = "第二个枚举值"];
}
message EnumMessage {
optional EnumType enum_type = 1;
}
protobuf的扩展
首先简单介绍一下protobuf扩展的使用方法:
- 字段,消息,枚举的拓展,各不相同:
extend google.protobuf.FileOptions {
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
optional MyMessage my_method_option = 50007;
}
- 枚举的字段编号范围:50000-99999,protobuf的官方文档中将该范围的字段编号保留给个别组织内部使用,如果要在公共应用程序中使用扩展,应该保证该扩展的编号全局唯一。并且每各类型的扩展编号是独立的,无需担心不同类型的扩展编号重合
读取枚举的拓展信息
给protobuf的枚举添加拓展信息后,可以使用反射直接读取枚举值对应的文案:
std::string EnumToString(uint32_t enum_value) {
auto enum_descriptor = demo::EnumType_descriptor();
auto label_descriptor = enum_descriptor->FindValueByNumber(enum_value);
if (label_descriptor != nullptr) {
return label_descriptor->options().GetExtension(mmpayqcmcharchiveinspectdto::EnumDesc);
}
return "";
}