先给你一段字节:
55 个字节。你知道它是一段 protobuf,但 .proto 文件不在你手上。
可能是上游团队没给,可能是抓包抓下来的 gRPC body,也可能是哪个老服务的日志里打了一串 byte array。反正现在的局面是:你得搞清楚这堆字节里到底装了什么,但你没有生成类,没有 schema。
很多人到这一步就卡住了。平时用 protobuf,都是先写 .proto,再 protoc 生成类,然后 parseFrom(bytes) 一把梭。生成类没了,好像就没招了。
先说结论:不用。protobuf 的二进制流自带结构,没有 .proto 一样能解,甚至能反过来自己造一条出来。
notion image

1. 为什么没有 .proto 也能解

上篇【gRPC 请求变成字节后,到底长什么样】讲了 protobuf 的 wire format:每个字段在字节流里是 [tag] [value],tag 里压着字段号和 wire type。
关键在于 wire type 自带"接下来这个值该读几个字节"的规则
  • VARINT(0):一直读到某个字节最高位是 0。
  • I64(1):固定 8 字节。
  • LEN(2):先读一个 varint 当长度 N,再读 N 字节。
  • I32(5):固定 4 字节。
每个字段的边界都能算出来。所以哪怕完全不知道 schema,也能像切香肠一样,把字节流切成一段段独立的字段。
notion image
当然,没有 .proto 不是没代价。你丢了两样东西:
字段名。 你只知道这是"字段 4",不知道它叫 score_delta
精确的语义类型。 同样是 VARINT 的 9,它可能是 int 的 9,也可能是 sint ZigZag 后的 -5,还可能是 enum 的第 9 项。光看字节分不出来。同理一个 LEN,可能是字符串、bytes、嵌套 message 或 packed 数组。
所以没有 schema 时,正确做法不是硬猜一个唯一答案,而是把原始值留着,把几种可能的解释都列出来

2. Java 落地:UnknownFieldSet

先排除一个容易想到的方案:DynamicMessage。它能不依赖生成类,但需要喂一个 Descriptor——本质还是 schema。我们的前提是没有 schema,所以它出局。
真正合适的是:
它本来干嘛的?protobuf 为了向前兼容,解析时碰到不认识的字段不会报错,而是塞进 UnknownFieldSet。它内部按字段号,把值分到四个桶里:varint、fixed32、fixed64、length-delimited——正好对应 4 种 wire type。
我们可以直接拿它当「无 schema 编解码器」用:
  • 编码:往四个桶里塞值,toByteArray() 序列化。tag 怎么写、长度怎么算,库全包了。
  • 解码UnknownFieldSet.parseFrom(bytes),然后按字段号取各桶里的原始值。
notion image

2.1 编码:手搓一条 SearchRequest

直接用 UnknownFieldSet 有个麻烦:它只认 wire 表示。你不能直接丢个 -5 进去,得先 ZigZag;不能直接丢个 0.85,得先转位模式。
所以我写了个小工具类 ProtobufWireUtils,把这些预处理包掉,代码见
protobuf-codec-demo
Github
protobuf-codec-demo
Owner
yibiner
Updated
Jun 29, 2026
核心方法:
有了它,组装那条 SearchRequest 就很直白:

2.2 验证:和官方生成类逐字节比

你可能会问:手搓出来的字节,真的和 protoc 正经生成类编出来的一样吗?
这个得验。
protobuf-java 自带了 Int32ValueStringValueDoubleValueFloatValueBoolValue 这些 wrapper 类型,都是 protoc 生成的,里面就一个字段 value = 1
用 UnknownFieldSet 手搓字段 1,再跟官方类的 toByteArray() 逐字节比。真实运行结果:
五个类型,逐字节一致。UnknownFieldSet 写出来的,就是标准 protobuf,谁来解都认。
我又用 gRPC 的 marshaller 和 DynamicMessage 做了三方对比,字节也完全一致(详见protobuf-codec-demo)。

2.3 无 schema 解码

反过来。把那 55 字节当成"天上掉下来的"数据,用 parseFrom 解,遍历每个字段,把 wire type 和候选解释都打出来:
没有 schema,整棵结构都出来了。嵌套 message 也自动递归进去了。
field 4 同时给了 uint=9 和 sint=-5。它到底是 int 还是 sint?光看字节分不出来,两种都摆出来让你判断。
notion image

2.4 一个必须说的局限:packed

field 8 本来是 repeated int32 [10, 20, 30],但解码输出里是:
被当成 bytes 了,不是数组。
这不是 bug,是没 schema 时的本质局限。0a 14 1e 这三个字节,可以解释成 packed 的 [10, 20, 30],也可以就是三个原始字节。光看 wire 分不出来。有 schema 才知道字段 8 是 repeated int32,才敢按 packed 解。
很多解码工具会用启发式去猜:能不能整齐拆成连续 varint、是不是不像可打印字符串……猜对的概率不低,但终究是猜。

2.5 有 schema 就能完整还原

补上类型信息后,所有值精确还原:
塞进去什么,原样拿回来什么。一条 .proto 没写,编解码闭环跑通。

3. 手解太累了

回头看,55 字节我画了张表才讲明白。真实业务里随便一条消息几百字节、嵌套四五层,靠肉眼对着 hex 数字节,谁顶得住。
而且 packed 到底是不是数组、那个 LEN 到底是字符串还是子消息,全得人去判断。联调 protobuf 协议、定位中间件问题的时候,你真没工夫一个字节一个字节去数。
所以我在github上找到了可以在浏览器中解码pb数据流的工具: , 并在其基础上做了优化
ProtoDecodeX
Github
ProtoDecodeX
Owner
yibiner
Updated
Mar 30, 2026

4. ProtoDecodeX:浏览器里解 protobuf

ProtoDecodeX 是一个跑在浏览器里的 protobuf 解码工具。贴一段数据进去,它帮你识别格式、切字段、递归嵌套、给候选解释。
它想解决的问题很具体:
  • 这段 protobuf 二进制里到底有哪些字段?
  • 这份数据到底是 hex、Base64 还是 Java 字节数组?
  • 某个 bytes 字段是不是嵌套了另一层 message?
  • 能不能贴一小段 .proto,把字段号映射成名字?

4.1 支持的输入格式

做联调的时候,日志里拷出来的字节格式五花八门。有的是 hex,有的是 base64,有的是 Java 打出来的十进制数组。ProtoDecodeX 自动识别格式,不需要你手动转:
格式
示例
十六进制
08 96 01
Base64
CJYB
Java 十进制字节数组
8, 150, 1
Java 转义字符串
\010\226\001
本地文件上传
任意二进制文件
粘进去就行,它自动转成规范化 Hex 再解码。

4.2 本地解码,没有数据安全风险

这点是硬需求。很多场景你解的是线上业务数据,不可能随便贴到某个在线工具的后端去。
ProtoDecodeX 的所有解码逻辑都在浏览器端用 JavaScript 完成,不依赖远程 API,不上传数据到服务端。本地文件也是浏览器直接读取。
你在公司内网或敏感环境使用,数据不会离开你的浏览器。

4.3 其他功能

gRPC 帧头识别。 如果输入带 gRPC 的 5 字节 framing header,会自动跳过再解析。省一步手工操作。
多消息流解析。 一段数据里串着多条 protobuf 消息,每条前面有 varint 长度前缀的那种,它能按消息边界拆开。这种场景在流式传输和批量消息里挺常见。
轻量 schema 支持。 可以在高级面板贴一小段 .proto 定义,把字段号映射成字段名和类型。不用完整编译,能辅助阅读就够了。
Smart Decode / XOR。 有些业务载荷会把数据塞在 bytes 字段里再做一层 XOR。工具支持条件 XOR 和手动 XOR,解完还能直接切回主输入继续往下看。

5. AI Skill 版本:让 Agent 帮你解

除了浏览器版,我还做了一个面向 AI 编程工具的版本:proto-decode-x-skill
它是一个 Cursor / Claude Code 的 Skill 包。装上以后,你可以直接在 IDE 里让 Agent 帮你解 protobuf 二进制数据。
Skill 定义了完整的解码流程:
  • 自动识别输入格式(hex、base64、Java 字节数组、Java 转义串)
  • 按 protobuf wire format 解码,支持 gRPC 5 字节头识别
  • 嵌套 message 递归展开
  • VARINT / FIXED32 / FIXED64 / LEN 每个字段都给出多种候选解释
  • 用固定的树形格式输出,方便阅读和对照
使用场景很直接:联调 protobuf 协议、定位中间件问题时,直接在编辑器里让 Agent 解一段字节。比切到浏览器里贴数据更顺手。
输出长这样:
每行都有字段号、wire type、原始值和候选解释。不需要你再自己去翻 wire format 规则。

6. 小结

没有 .proto 不可怕。protobuf 的 wire format 自带结构,靠 tag 里的字段号和 wire type 就能切出字段边界。Java 里用官方的 UnknownFieldSetparseFrom 解、toByteArray 编,不用自己撸 varint。
手解小段数据够用,但真实业务场景用工具更高效:
文中所有编解码代码都在配套工程里,mvn test 可以直接跑通编解码闭环。
如果这两个工具对你有帮助,欢迎去 GitHub 点个 star。
 
JXCache:一个 JetCache 工具集,从可观测开始gRPC 请求变成字节后,到底长什么样
Loading...