序列化、二进制与版本兼容
理解 JSON、二进制和消息版本演进,建立跨进程数据的兼容意识。
序列化、二进制与版本兼容
JS/TS 很容易把对象 JSON.stringify 后发送出去,但 Python 和 C++ 消费者可能有不同的整数、空值、时间和精度语义。格式需要版本和兼容策略。序列化是把内存模型变成长期协议:字段名、数字范围、时区、编码、字节序、精度和缺失值都必须被定义。JSON 便于调试,二进制可能节省带宽和 CPU,却会增加 schema 工具与版本排错成本。
跨语言尤其要警惕时间戳和大整数。JavaScript 的安全整数范围有限,Python 整数可以更大,C++ 的 int64_t 也不等于任意 JSON number 的精确表示;AI 分数通常可以用明确的小数约束,设备 id 可能应作为字符串传输。不要把“解析成功”当作“协议有效”。
学习目标
- 能为消息定义版本、时间单位、数值范围、缺失值和编码方式。
- 能说明新增字段、字段改名和单位变化分别如何兼容或升级。
- 能使用固定 golden message 对比 JS/TS、Python、C++ 的解析结果。
数据格式是团队之间的长期协议
下面这段代码只保留同一个意图,重点观察输入边界、数据流和失败语义,而不是逐字符翻译。
const message = JSON.stringify({
version: 1, id, score, createdAt: new Date().toISOString()
}); # Python
message = {"version": 1, "id": item_id,
"score": score, "created_at": created_at.isoformat()}
payload = json.dumps(message)
// C++
Message message{1, id, score, created_at};
auto payload = encode_json(message); JSON 与二进制的取舍
JSON 适合 HTTP、CLI 和早期跨语言协作,字段可读且容易抓包;二进制适合高频传感器、点云和带宽紧张的设备,但需要明确 schema、编码器版本和兼容测试。无论格式是什么,外部字节进入内部模型前都要限大小、校验版本和验证必填字段。把协议转换集中在 adapter,不让业务代码直接依赖 JSON 库的动态对象。
import json
def decode_event(payload: bytes) -> dict:
if len(payload) > 1_000_000:
raise ValueError("payload too large")
event = json.loads(payload)
if event.get("version") not in (1, 2):
raise ValueError("unsupported event version")
if not isinstance(event.get("timestamp_ms"), int):
raise ValueError("timestamp_ms must be an integer")
return event
版本演进策略
新增可选字段通常比重命名或改变类型更兼容;消费者应忽略它不认识但不影响核心逻辑的字段。新消费者遇到旧消息时提供安全默认值,旧消费者遇到新消息时不能被未知字段击穿。若单位或语义改变,增加新字段或新版本,不要复用同一个字段名表达两种含义。保留生产者和消费者的契约测试,并记录当前 schema 版本。
常见错误与排错思路
常见错误是 null、缺失和 0 被当成同一状态,或把 ISO 时间字符串和本地时间混用。排错时保存脱敏后的 payload、版本、编码器版本和字节长度,使用一份固定 golden message 在 JS、Python、C++ 各解析一次;若数值不同,检查浮点精度、时区和整数安全范围,而不是先怀疑网络。
从原始 JSON 到版本化消息
序列化前先定义一个所有消费者都能解释的契约。数字字段需要明确有限性和范围,时间戳要明确时区与单位;null、字段缺失和数值零不能默认视为同一含义。解析后将外部格式转换为内部模型,避免让 JSON 细节扩散到业务代码。
function decode(text) {
const message = JSON.parse(text);
if (message.version !== 1) return { ok: false, code: "unsupported_version" };
if (!Number.isSafeInteger(message.timestampMs)) return { ok: false, code: "invalid_timestamp" };
if (message.unit !== "celsius" || !Number.isFinite(message.value)) return { ok: false, code: "invalid_measurement" };
return { ok: true, value: { timestampMs: message.timestampMs, unit: message.unit, value: message.value } };
}
console.log(decode('{"version":1,"timestampMs":1760000000000,"unit":"celsius","value":21.5}').ok);
合法样例应返回 true;未知版本、毫秒/秒错用、NaN 语义或单位不匹配都应在边界失败。JSON 本身不支持 NaN 标准数值,不能因为某语言解析器接受扩展值就把它纳入跨语言协议。
版本升级与兼容验证
新增可选字段时,旧消费者通常可忽略、新消费者可给出安全默认值;改变现有字段含义或单位则应升级版本并提供显式转换。为每个支持版本保留 golden message,运行三种语言的解析器并比较规范化后的字段、时间和精度。超出 JavaScript 安全整数范围的数值不要直接跨 JSON 当普通整数传输,可改用字符串或明确的二进制表示。
排查不一致时先比较原始字节、字符编码、字段缺失策略、整数精度与浮点舍入,再检查网络。若只保存当前版本样例,旧消息回归会无声丢失,因此 fixture 应与 schema 版本共同维护。
迁移练习
请完成:为传感器消息设计 version、timestamp、unit 和 value 字段,并写出旧消费者遇到新消息、新消费者遇到旧消息时的行为;再选择 JSON 或二进制并说明理由。
序列化、二进制与版本兼容练习
为传感器消息设计 version、timestamp、unit 和 value 字段,并写出旧消费者遇到新消息、新消费者遇到旧消息时的行为;再选择 JSON 或二进制并说明理由。
给我一点提示
考虑旧消费者遇到新字段,以及新消费者遇到旧消息;对时间单位、数值精度和消息大小写清约束。
查看参考答案
version=1、timestamp_ms 为 UTC epoch 毫秒、unit 为固定枚举、value 为有限 double。新字段提供安全默认值并允许旧消费者忽略;无法安全转换的版本或单位拒绝。先用 JSON 便于跨语言调试,高频或带宽受限后再引入带 schema 的二进制格式。 本节结论
把数据协议当产品来维护,才能让多语言组件持续演进。完成后,请用固定消息在 JS/TS、Python 和 C++ 之间往返一次,比较字段、时间和数值是否保持一致。
小结
序列化格式是长期协议,版本、单位、范围和缺失语义必须写明并用跨语言样例锁定。传输格式可以更换,只有显式版本转换和回放测试能保护已有消费者。
阶段共 8 节课,按顺序完成更容易建立完整的迁移模型。