Proto3 三大高级类型:Any、Oneof、Map 灵活解决复杂业务场景

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

<<Git>><<MySQL>>

🌟心向往之行必能至

目录

[一、Any 类型:泛型字段,存储任意消息体](#一、Any 类型:泛型字段,存储任意消息体)

[1. Any 类型的核心规则](#1. Any 类型的核心规则)

[2. Any 类型的使用步骤](#2. Any 类型的使用步骤)

[步骤 1:导入 any.proto 文件(必选)](#步骤 1:导入 any.proto 文件(必选))

[步骤 2:定义需要存储的任意消息体](#步骤 2:定义需要存储的任意消息体)

[步骤 3:定义 Any 类型字段](#步骤 3:定义 Any 类型字段)

[3. 实战:为联系人添加任意类型的地址信息](#3. 实战:为联系人添加任意类型的地址信息)

[4. Any 类型的核心方法(C++ 为例)](#4. Any 类型的核心方法(C++ 为例))

[二、Oneof 类型:多选一字段,仅一个字段生效](#二、Oneof 类型:多选一字段,仅一个字段生效)

[1. Oneof 类型的核心规则](#1. Oneof 类型的核心规则)

[2. Oneof 类型的定义格式](#2. Oneof 类型的定义格式)

[3. 实战:为联系人添加二选一的其他联系方式](#3. 实战:为联系人添加二选一的其他联系方式)

[4. Oneof 类型的核心方法(C++ 为例)](#4. Oneof 类型的核心方法(C++ 为例))

[三、Map 类型:键值对字段,存储关联数据](#三、Map 类型:键值对字段,存储关联数据)

[1. Map 类型的核心规则](#1. Map 类型的核心规则)

[2. Map 类型的定义格式](#2. Map 类型的定义格式)

[3. 实战:为联系人添加键值对格式的备注信息](#3. 实战:为联系人添加键值对格式的备注信息)

[4. Map 类型的核心方法(C++ 为例)](#4. Map 类型的核心方法(C++ 为例))

四、三大高级类型的综合实战:完整的通讯录协议

五、高级类型注意事项


在前两篇博客中,我们掌握了 proto3 的基础语法和高级语法,能定义包含基础字段、重复字段、嵌套消息和枚举的结构化数据,但实际业务中还有更复杂的需求:

  • 字段需要存储任意类型的对象(如联系人的附加信息可能是地址、身份证、银行卡等);
  • 多个字段只能有一个生效(如联系人的其他联系方式:QQ / 微信二选一);
  • 字段需要键值对格式(如联系人的备注信息:key = 备注标题,value = 备注内容)。

proto3 提供了AnyOneofMap三大高级类型,专门解决以上场景,让协议定义更灵活、更贴合实际业务。本文将详细讲解这三种类型的定义、使用规则和实战场景,结合通讯录案例完成协议升级。

一、Any 类型:泛型字段,存储任意消息体

Any 类型 相当于编程语言中的泛型 ,可以在字段中存储任意类型的 Protobuf 消息体 ,无需提前指定具体类型,实现了字段的 "动态类型",适合存储不确定类型的附加信息

1. Any 类型的核心规则
  1. Any 类型是 Google 预定义的类型,需要导入官方的 any.proto 文件才能使用;
  2. Any 类型的字段可以存储任意自定义的消息体,支持嵌套和复杂结构;
  3. 编译后,会生成PackFrom()UnpackTo() 方法,用于消息体与 Any 类型的互转;
  4. 提供Is<T>() 方法,用于判断 Any 字段中存储的消息体类型。
2. Any 类型的使用步骤
步骤 1:导入 any.proto 文件(必选)

proto

复制代码
// 导入Google官方的any.proto,必须写在包名声明后
import "google/protobuf/any.proto";
步骤 2:定义需要存储的任意消息体

proto

复制代码
// 示例:定义地址消息体,作为Any字段的存储内容
message Address {
  string home_address = 1; // 家庭地址
  string unit_address = 2; // 单位地址
}
步骤 3:定义 Any 类型字段

proto

复制代码
message 消息体名称 {
  // 使用google.protobuf.Any作为字段类型
  google.protobuf.Any 字段名 = 唯一编号;
}
3. 实战:为联系人添加任意类型的地址信息

在通讯录的PeopleInfo消息体中,添加 Any 类型的data字段,用于存储联系人的地址信息(Address 消息体),实现 "附加信息的动态存储":

proto

复制代码
syntax = "proto3";
package contacts;

// 导入Any类型的官方proto文件
import "google/protobuf/any.proto";

// 地址消息体:可作为Any字段的存储内容
message Address {
  string home_address = 1; // 家庭地址
  string unit_address = 2; // 单位地址
}

// 联系人消息体
message PeopleInfo {
  string name = 1;    // 姓名
  sint32 age = 2;     // 年龄

  // 嵌套:电话号码(含枚举)
  message Phone {
    enum PhoneType {
      MP = 0;  // 移动电话
      TEL = 1; // 固定电话
    }
    string number = 1;
    PhoneType type = 2;
  }
  repeated Phone phone = 3; // 多个电话号码

  // Any类型:存储任意消息体(此处存储Address),编号4
  google.protobuf.Any data = 4;
}

// 通讯录消息体
message Contacts {
  repeated PeopleInfo contacts = 1;
}
4. Any 类型的核心方法(C++ 为例)

编译后,C++ 会生成以下核心方法,实现消息体与 Any 类型的互转:

  1. PackFrom() :将自定义消息体(如 Address)打包为 Any 类型;

    cpp

    运行

    复制代码
    // 将Address对象打包到Any字段中
    Address address;
    people_info.mutable_data()->PackFrom(address);
  2. UnpackTo() :将 Any 类型解包为自定义消息体;

    cpp

    运行

    复制代码
    // 将Any字段解包为Address对象
    Address address;
    people_info.data().UnpackTo(&address);
  3. Is<T>() :判断 Any 字段中存储的是否为指定类型的消息体;

    cpp

    运行

    复制代码
    // 判断data字段是否存储的是Address对象
    if (people_info.data().Is<Address>()) {
      // 解包并处理
    }
  4. has_data():判断 Any 字段是否被赋值。

二、Oneof 类型:多选一字段,仅一个字段生效

Oneof 类型 用于定义一组互斥的字段 ,即这组字段中只能有一个字段被赋值,赋值新的字段会自动清除之前赋值的字段,适合 "多选一" 的业务场景(如联系方式:QQ / 微信二选一、支付方式:微信 / 支付宝 / 银行卡三选一)。

1. Oneof 类型的核心规则
  1. Oneof 字段的定义格式为oneof 字段名 { 子字段1; 子字段2; ... }
  2. 子字段的编号不可重复,且不可与消息体中的其他字段编号冲突;
  3. 不能在 Oneof 中使用 repeated 字段(重复字段与互斥规则冲突);
  4. 赋值 Oneof 中的一个子字段,会自动清除其他子字段的值
  5. 编译后,会生成**_case()** 方法,用于判断当前哪个子字段被赋值。
2. Oneof 类型的定义格式

proto

复制代码
message 消息体名称 {
  // 其他普通字段
  字段类型 字段名 = 唯一编号;

  // Oneof类型:定义互斥的子字段
  oneof oneof字段名 {
    字段类型 子字段1 = 唯一编号;
    字段类型 子字段2 = 唯一编号;
    // ... 更多子字段
  }
}
3. 实战:为联系人添加二选一的其他联系方式

PeopleInfo消息体中,添加 Oneof 类型的other_contact字段,包含qqweixin两个子字段,实现 "QQ / 微信二选一" 的联系方式:

proto

复制代码
syntax = "proto3";
package contacts;
import "google/protobuf/any.proto";

message Address {
  string home_address = 1;
  string unit_address = 2;
}

message PeopleInfo {
  string name = 1;
  sint32 age = 2;

  message Phone {
    enum PhoneType { MP = 0; TEL = 1; }
    string number = 1;
    PhoneType type = 2;
  }
  repeated Phone phone = 3;
  google.protobuf.Any data = 4;

  // Oneof类型:其他联系方式(QQ/微信二选一),编号5、6
  oneof other_contact {
    string qq = 5;
    string weixin = 6;
  }
}

message Contacts {
  repeated PeopleInfo contacts = 1;
}
4. Oneof 类型的核心方法(C++ 为例)

编译后,C++ 会生成以下核心方法,用于 Oneof 字段的操作和判断:

  1. 普通字段操作方法set_qq()set_weixin()qq()weixin()

  2. 判断赋值状态other_contact_case(),返回当前生效的子字段枚举值;

    cpp

    运行

    复制代码
    // 判断哪个联系方式被赋值
    switch (people_info.other_contact_case()) {
      case PeopleInfo::kQq: // QQ被赋值
        cout << "QQ: " << people_info.qq() << endl;
        break;
      case PeopleInfo::kWeixin: // 微信被赋值
        cout << "微信: " << people_info.weixin() << endl;
        break;
      case PeopleInfo::OTHER_CONTACT_NOT_SET: // 未赋值
        break;
    }
  3. 清空 Oneof 字段clear_other_contact(),清除所有子字段的值。

三、Map 类型:键值对字段,存储关联数据

Map 类型 用于定义键值对格式的字段 ,相当于编程语言中的Map/Dictionary,适合存储备注信息、扩展属性、键值对配置等场景,无需定义额外的消息体。

1. Map 类型的核心规则
  1. Map 类型的定义格式为map<key_type, value_type> 字段名 = 唯一编号;
  2. key_type(键类型) :只能是除floatbytes外的标量类型(如 int32、string、uint64 等);
  3. value_type(值类型) :可以是任意类型(标量类型、消息体、枚举、重复字段等);
  4. 不能用 repeated 修饰 Map 字段(Map 本身支持多组键值对);
  5. Map 中存储的元素是无序的,序列化和反序列化后的顺序可能不一致;
  6. 编译后,会生成与普通字段类似的操作方法,支持键值对的增删改查。
2. Map 类型的定义格式

proto

复制代码
message 消息体名称 {
  // 其他字段
  字段类型 字段名 = 唯一编号;

  // Map类型:键类型key_type,值类型value_type
  map<key_type, value_type> 字段名 = 唯一编号;
}
3. 实战:为联系人添加键值对格式的备注信息

PeopleInfo消息体中,添加 Map 类型的remark字段,键类型为string(备注标题),值类型为string(备注内容),实现联系人备注的灵活存储:

proto

复制代码
syntax = "proto3";
package contacts;
import "google/protobuf/any.proto";

message Address {
  string home_address = 1;
  string unit_address = 2;
}

message PeopleInfo {
  string name = 1;
  sint32 age = 2;

  message Phone {
    enum PhoneType { MP = 0; TEL = 1; }
    string number = 1;
    PhoneType type = 2;
  }
  repeated Phone phone = 3;
  google.protobuf.Any data = 4;

  oneof other_contact {
    string qq = 5;
    string weixin = 6;
  }

  // Map类型:备注信息(键:备注标题,值:备注内容),编号7
  map<string, string> remark = 7;
}

message Contacts {
  repeated PeopleInfo contacts = 1;
}
4. Map 类型的核心方法(C++ 为例)

编译后,C++ 会生成以下核心方法,用于 Map 字段的键值对操作:

  1. 获取 Map 对象mutable_remark(),返回 Map 对象的指针,用于增删改查;

    cpp

    运行

    复制代码
    // 向备注中添加键值对
    people_info.mutable_remark()->insert({"日程", "10月1日出游"});
    people_info.mutable_remark()->insert({"备注", "重要联系人"});
  2. 获取 Map 大小remark_size(),返回键值对的个数;

  3. 遍历 Map :通过迭代器遍历所有键值对;

    cpp

    运行

    复制代码
    for (auto it = people_info.remark().cbegin(); it != people_info.remark().cend(); ++it) {
      cout << it->first << ": " << it->second << endl;
    }
  4. 清空 Mapclear_remark(),清除所有键值对。

四、三大高级类型的综合实战:完整的通讯录协议

结合 Any、Oneof、Map 三大高级类型,定义一个完整的通讯录.proto 文件contacts.proto),包含联系人的所有核心信息:姓名、年龄、多个电话号码(含类型)、任意类型的地址、二选一的联系方式、键值对备注,以及多个联系人的通讯录:

proto

复制代码
syntax = "proto3";
package contacts;

// 导入Any类型依赖
import "google/protobuf/any.proto";

// 地址消息体:供Any类型存储
message Address {
  string home_address = 1; // 家庭地址
  string unit_address = 2; // 单位地址
}

// 联系人消息体
message PeopleInfo {
  string name = 1;    // 姓名
  sint32 age = 2;     // 年龄

  // 电话号码(嵌套+枚举)
  message Phone {
    enum PhoneType {
      MP = 0;  // 移动电话
      TEL = 1; // 固定电话
    }
    string number = 1; // 号码
    PhoneType type = 2; // 类型
  }
  repeated Phone phone = 3; // 多个电话号码

  google.protobuf.Any data = 4; // 任意类型附加信息(地址)
  oneof other_contact { // 二选一联系方式
    string qq = 5;
    string weixin = 6;
  }
  map<string, string> remark = 7; // 键值对备注
}

// 通讯录消息体:多个联系人
message Contacts {
  repeated PeopleInfo contacts = 1;
}

五、高级类型注意事项

  1. Any 类型必须导入 any.proto,否则编译报错,且仅能存储 Protobuf 消息体;
  2. Oneof 的子字段不可重复、不可用 repeated,赋值互斥,适合多选一场景;
  3. Map 的键类型不支持 float 和 bytes,元素无序,适合键值对存储;
  4. 三大高级类型的字段编号仍需遵循唯一规则,为后续扩展预留;
  5. 编译后,所有高级类型都会生成对应的操作方法,无需手动编写解析代码。

下一篇博客,我们将讲解 Protobuf 的默认值规则消息体更新规则 ,这是保证 Protobuf版本兼容性的核心,也是分布式系统中协议迭代的关键,让大家能在项目迭代中无缝扩展协议,不破坏旧版本程序。

相关推荐
Leo1879 分钟前
分布式事务
java·分布式·分布式事务
RSTJ_162516 分钟前
PYTHON+AI LLM DAY SIXTY-FOUR
开发语言·python
AI_yangxi23 分钟前
短视频矩阵系统服务商
大数据·人工智能·矩阵
覆东流41 分钟前
Java开发环境搭建
java·开发语言·后端
阿洛学长1 小时前
VMware安装虚拟机教程(超详细)
java·linux·开发语言
rit84324991 小时前
链路预测(Link Prediction)MATLAB 实现
开发语言·matlab
jiayong231 小时前
01 检查 Python 版本与环境
开发语言·python
coder Ethan1 小时前
Spring AI 入门:(3)快速搭建一个简单的问答助手
java·人工智能·spring
屋外雨大,惊蛰出没1 小时前
starter的创建与引用
java·stater
小同志001 小时前
Spring Boot ⽇志概述(简单了解)
java·java-ee·日志