JSON易读通用但性能差、无校验;Protobuf高性能强契约但需.proto生成代码;MessagePack兼顾二进制效率与零schema灵活性;选型取决于变更成本与协作场景。

JSON 适合调试和跨语言简单交互,但别用在性能敏感场景
JSON 是最易读、最通用的序列化格式,std::string 就能承载,配合 nlohmann/json 库几行就能完成序列化/反序列化。但它本质是文本格式,解析慢、体积大、无 schema 约束。
- 每次解析都要做字符串 token 化、类型推断和动态内存分配,
json::parse()在千条消息量级下可能比 Protobuf 慢 5–10 倍 - 数字被转成字符串再转回(如
42→"42"),浮点精度可能丢失(尤其double) - 字段名重复出现(每个对象都带
"id"、"name"),网络传输或磁盘存储时明显膨胀 - 没有编译期校验:字段拼错、类型不匹配只能到运行时报错,比如把
"status"写成"stauts"
适用场景:配置文件、HTTP API 响应、日志输出、前端联调接口。
Protobuf 要求提前定义 .proto 文件,换来高性能和强契约
Protobuf 不是“选一个库就行”,它依赖代码生成:你写 .proto 文件,用 protoc 编译出 C++ 类,再调用 SerializeToString() 或 ParseFromString()。这意味着所有字段名、类型、嵌套关系在编译期就固定了。
- 二进制编码紧凑,整数用 varint,重复字段可压缩,相同结构的消息体积通常只有 JSON 的 1/3~1/2
- 解析不依赖字符串匹配,直接按字节偏移 + wire type 解包,
ParseFromString()几乎是 memcpy 级别开销 - 支持向后兼容:新增 optional 字段不影响旧版本解析;删除字段需保留 tag 号并加
reserved - 缺点也很硬:必须维护
.proto文件、每次改结构要重新生成代码、不支持动态字段(如 map需用 google.protobuf.Any)
典型使用流程:
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
repeated string tags = 3;
}然后执行 protoc --cpp_out=. user.proto,生成 user.pb.h 和 user.pb.cc。
MessagePack 是 JSON 的二进制替代,零 schema 但仍有类型信息
MessagePack 语义上接近 JSON(支持 map、array、string、int、float、bool、null),但用二进制编码,不带字段名冗余。C++ 用 msgpack-c 库,通过宏或自定义 MSGPACK_DEFINE 注册结构体成员即可序列化。
立即学习“C++免费学习笔记(深入)”;
- 无需 IDL 文件,也不强制生成代码,对已有
struct改动小(加个宏就行) - 比 JSON 小且快,比 Protobuf 稍大稍慢(因仍需运行时遍历字段名字符串做映射)
- 字段名仍以字符串形式存在(虽然只存一次),不支持字段重命名透明迁移(改
name→full_name就断兼容) -
msgpack::object可用于弱类型场景(如配置解析),但失去编译期类型安全
示例结构体注册:
struct User {
int id;
std::string name;
MSGPACK_DEFINE(id, name);
};之后可直接用 msgpack::pack() 和 msgpack::unpack()。
选型关键不在“哪个好”,而在“谁承担变更成本”
如果通信双方都是你控制的 C++ 服务,且协议长期稳定,Protobuf 的编译期检查和极致性能值得前期投入;如果要和 Python/JS 快速对接又不愿写 IDL,MessagePack 是折中选择;如果只是临时导出数据给运维看、或者做 HTTP 调试,JSON 仍是最快路径。
最容易被忽略的是演进成本:Protobuf 的 tag 号一旦发布就不能改,MessagePack 字段名改了旧数据就无法识别,而 JSON 即使字段名错了也常靠默认值兜底——这看似灵活,实则把问题拖到线上才暴露。











