JSON / CBOR / Protobuf区别

一、 多维度对比

对比维度 JSON CBOR Protobuf3
底层编码本质 UTF-8 文本格式; 使用标点{}:,"分隔结构 二进制格式; 每一个独立元素依靠首字节描述:首字节 = 高 3bit 主类型 MT + 低 5bit 附加信息 AI; key-value 保留字符串 二进制格式; 每个字段头部 1 字节 = (tag_number << 3)|wire_type;高 5 位 = tag 编号,低 3 位 = wire_type 线类型; 报文不带字段名称
Schema 依赖 无原生 schema,自描述;可以额外配套 JSON Schema 做校验 无 schema,自描述;报文中携带字符串 key 强依赖外部 schema(.proto 文件);tag 编号是契约,报文只有数字 tag
字段标识方式 字符串 key,例如 "cellId" 字符串 key,例如 "cellId" 数字 tag,例如 1、2、3
可读性 ✅ 明文,直接打开查看、抓包调试 ❌ 二进制,需要工具解码后才能看懂 key ❌ 二进制;必须匹配对应的 proto 文件才能解析,脱离 schema 完全无法识别含义
包体积(同一样例) ~70 字节(最大) ~42 字节(中等) ~14 字节(最小)
解析性能 最慢,需要文本分词、字符串转类型、处理符号 中等,二进制直接读取,但 key 字符串需要匹配比对 最快;按 wire_type 固定偏移读取,无字符串 key 比对开销
默认值处理 默认值(0/false)必须显式写出 默认值正常编码写入报文 proto3 默认值(0、false、空串)不序列化,不会出现在报文中,节省流量
类型约束能力 弱类型,无强制约束;数值和字符串容易混淆,拼写、大小写错误不会硬报错 弱类型;key 拼写 / 大小写错误会匹配失败,不会强校验结构 强类型约束,proto 预先定义类型;tag 固定,新增 tag 旧程序自动跳过,结构稳定
版本向前向后兼容 差;增减字段容易出现解析异常,key 写错直接失效 一般;和 JSON 类似,靠 key 匹配,容易因为 key 变更出问题 优秀;tag 一旦分配不再修改,新增 tag 老版本程序自动忽略未知字段
数组 / 批量测点场景开销 极高,每个元素伴随文本符号 较高,每个 key 字符串持续占用字节 极低,repeated 数组只传数值,没有 key 重复开销(96 串电芯上报优势巨大)
Qt 原生支持 ✅ QJsonDocument 原生自带 ❌ Qt5.12 无原生;Qt5.15+/Qt6 自带 QCbor;低版本依赖 tinycbor ❌ 无原生;依赖 libprotobuf 或者轻量 nanopb(ESP32 常用)
典型适用场景 HTTP 鉴权接口、配置文件、日志、调试报文、后台交互 NB-IoT/LoRa 低带宽、需要 COSE 签名、协议频繁改动、不想维护 proto MQTT 高频实时采集、电芯电压 / 温度批量上报、CAN 转发、OTA 分包、大数据流
主要短板 冗余符号多、包大、解析慢 key 字符串依然占用流量,大批量测点不划算 需要维护 proto 契约,两端 tag 严格对齐;抓包调试不方便

二、核心规则

1.CBOR 核心规则:每个元素由【首字节】控制

首字节 = 3bit 主类型 + 5bit 附加信息

复制代码
bit:  7 6 5 | 4 3 2 1 0
      主类型 | 附加信息(ai)

7 个主类型(MT,Major Type)

MT 含义
0 无符号整数(正数)
1 负数
2 字节串 byte string
3 UTF-8 字符串 text string(我们的 key 都是这个)
4 数组 array
5 映射 map(就是 JSON 的 {},key-value 对)
6 标签 tag(扩展,比如时间、二进制标签)
7 简单值 / 浮点数:true/false/null、half/float/double 常用 MT7(类型 7)速查表

附加信息 ai:

  • 0~23:值直接就是 ai 本身,不再额外字节
  • 24:后面跟 1 字节 uint8
  • 25:后面跟 2 字节 uint16
  • 26:后面跟 4 字节 uint32
  • 27:后面跟 8 字节 uint64
  • 28~30:保留
  • 31:无限长度流(indefinite)

1.1例子

复制代码
{"cellId":5,"voltage":3.242,"temperature":28.6}

对应的 CBOR 十六进制: a3 66 63 65 6c 6c 49 64 05 67 76 6f 6c 74 61 67 65 fa 41 4f 8f 2d 6b 74 65 6d 70 65 72 61 74 75 72 65 fa 41 e4 33 33

a3 是第一个字节 0xa3 二进制:101 00011

  • MT=5(map,JSON 对象)
  • ai=3 → 这个 map 里面有 3 组 key-value
  • 👉 对应 JSON:3 个键值对:cellId /voltage/temperature

第 1 组:"cellId"

66 63 65 6c 6c 49 64 05

  • 0x66:二进制 011 00110 MT=3(text string,字符串 key),ai=6 → 字符串长度 = 6 字节 后面 6 字节:63 65 6c 6c 49 64 → ASCII:c e l l I d → "cellId"
  • 0x05:二进制 000 00101 MT=0(无符号整数),ai=5 → 值 = 5

✅ "cellId" = 5

第 2 组:"voltage"

67 76 6f 6c 74 61 67 65 fa 41 4f 8f 2d

  • 0x67:MT=3,ai=7 → 字符串长度 7 后续 7 字节:76 6f 6c 74 61 67 65 → "voltage"
  • 0xfa:二进制 111 11010 MT=7(简单 / 浮点数),ai=26 → 单精度 float(4 字节 IEEE754) 紧跟着 4 字节:41 4f 8f 2d → float=3.242

✅ "voltage" = 3.242

第 3 组:"temperature"

6b 74 65 6d 70 65 72 61 74 75 72 65 fa 41 e4 33 33

  • 0x6b:MT=3,ai=11 → 字符串长度 11 后面 11 字节:74 65 6d 70 65 72 61 74 75 72 65 → "temperature"
  • 0xfa:ai=26,单精度 float 4 字节 41 e4 33 33 → float=28.6

✅ "temperature" = 28.6

2. Protobuf 的 Wire Format(线编码规则)

Protobuf 每个字段头部是 (tag_number << 3) | wire_type

1 字节,高 5 位是 tag 编号,低 3 位是 wire_type(线类型)

wire_type 对照表(proto3 常用):

wire_type 含义
0 Varint(int32/uint32/bool 变长整数)
1 64-bit 固定 8 字节
2 Length-delimited(字符串、bytes、嵌套 message)
5 32-bit 固定 4 字节(float!)

公式: byte = (tag << 3) | wire_type

2.1 例子

复制代码
{"cellId":5,"voltage":3.242,"temperature":28.6}

对应的 CBOR 十六进制:08 05 15 2D 8F 4F 41 1D 33 33 E4 41

① 0x08
  • 0x08 = 十进制 8
  • 8 >> 3 = 1 → tag = 1
  • 8 & 0b111 = 0 → wire_type = 0(varint)
  • ✅ tag=1,类型 varint,对应你 proto 里:int32 cellId = 1; 后面跟着的 0x05 就是值 5
② 0x15
  • 0x15 = 十进制 21
  • 21 >> 3 = 2 → tag = 2
  • 21 & 0b111 = 5 → wire_type =5(固定 4 字节 float)
  • ✅ tag=2,float,对应 float voltage = 2; 后面 4 字节:2d 8f 4f 41 就是 3.242 的 IEEE754 单精度浮点数
③ 0x1d
  • 0x1d = 十进制 29
  • 29 >> 3 = 3 → tag = 3
  • 29 & 0b111 =5 → wire_type=5(float)
  • ✅ tag=3,float,对应 float temperature =3; 后面 4 字节:33 33 e4 41 是 28.6 的 float

2.2 汇总

首字节 十进制 tag wire_type 类型 proto 定义
0x08 8 1 0 varint(int32) int32 cellId = 1
0x15 21 2 5 32bit float float voltage = 2
0x1d 29 3 5 32bit float float temperature =3

首字节(16进制) = tag * 8 + wire_type(10进制)

三、对比 Protobuf 编码的核心差异

CBOR:

  • map (0xa3) → 里面放【字符串 key + value】
  • 报文里面保留 "cellId"、"voltage" 文本
  • 解析器读到字符串 key,再做业务匹配
  • 不需要提前 schema

Protobuf:

  • 没有 key 字符串!只有 (tag<<3)|wiretype 头
  • 依赖外部 .proto 约定 tag 1=cellId

一句话: CBOR 是【带名字】;Protobuf 是【带编号】。

四、CBOR 优点 & 坑

✅ 优点

  1. 和 JSON 思维一致,key-value,自描述,不用维护 proto
  2. 比 JSON 体积小,去掉大量标点引号
  3. 支持整数、float、bool、bytes、数组、map
  4. 支持 COSE 签名(物联网安全,很多 NB-IoT 设备用)

❌ 缺点

  1. key 字符串依然占流量!大批量 96 串电芯 repeated 上报时,开销远大于 protobuf
  2. 没有强 schema 校验:key 写错大小写、拼写错,默默读不到,不会报错
  3. Qt 无原生支持,需要 tinycbor / QCbor
相关推荐
江屿风6 小时前
【Plain Language Large Model】【大语言模型底层原理全解析:从Token生成到智能体演进】流食般投喂
人工智能·笔记·语言模型·llm·json·token·聊天模板
情 九18 小时前
Dify HTTP 请求节点实战:发送 JSON、解析响应与处理异常
网络协议·http·json·dify
qq_白羊座1 天前
口头说的 JSON 和 MySQL 里的 JSON
数据库·mysql·json
love静思冥想1 天前
CLOB中JSON查询agent字段值(Oracle11g)
sql·oracle·json
Spatial3Ddddd1 天前
博物馆做VR全景好看之外还要能留档
json·vr
大鳥1 天前
JSON 解析最佳实践:从入门到生产级优化
hive·sql·spark·json
镜子AI2 天前
镜子AI工程实践:用 Python 做 GEO 实体一致性审计,并自动生成 JSON-LD 结构化数据
人工智能·python·json
syagain_zsx2 天前
jsoncpp 使用指南:编译链接、Json::Value 取值与高频坑
c++·json·库·http格式