serde_json 升级后,1e400 输出变成 1e+400
开启 arbitrary_precision 后,大指数数字仍能读取,文本快照却多了正号。先区分数值约定与原始字节约定,避免用 f64 丢掉范围。
开启 serde_json 的 arbitrary_precision 后,"1e400" 能读入 Value,但转回字符串变成了 "1e+400"。原先要求逐字相等的测试、缓存键或签名因此改变,不能只看它仍是有效 JSON 就忽略差异。
这项变化可见于 1.0.148 升级到 1.0.149 的路径,正式 1.0.154 的 arbitrary_precision 解析器也仍会为没有显式符号的指数补 +。它在扫描指数时就发生,不能简单归因于一个浮点格式化依赖升级。
Value 不承诺保留数字的原始拼写
1e400 和 1e+400 表达相同的十进制指数,但它们是不同字节。Value 用于结构化处理,不是原始 JSON 文本容器。开启 arbitrary_precision 解决大数字的表示范围,并不额外承诺原样回写每一种词法。
也不要为了比较而把它转成 f64:1e400 超过普通有限 f64 的范围,会引入另一项溢出或精度问题。直接使用 Value 或 Number 的相等运算,也不能未经核实就当成任意十进制表示的数学等价判定。
必须保留原始数字时,在读取入口保留 RawValue
若协议需要原样转发这个数字,可以从原始输入直接读取 RawValue,而不是先读成 Value 再转换:
use serde_json::value::RawValue;
fn preserve_number(input: &str) -> serde_json::Result<String> {
let raw: &RawValue = serde_json::from_str(input)?;
Ok(raw.get().to_owned())
}
项目需启用 serde_json 的 raw_value feature。这个函数验证输入是一个完整 JSON 值,但不额外保证它是数字;业务字段只允许数字时,应同时保留对应的类型与范围校验。不能因为 RawValue 接受了字符串、对象或 null,就把它们也作为有效金额或计数。
RawValue 保留的是值片段,不是整个 HTTP 消息的字节。若签名覆盖消息外层空白、字段顺序或其他原始字节,应直接保留被签名的原始输入,并按协议验证签名,不能用重新序列化后的内容替代。
若输入还需经过 flatten 收集未知字段,注意flatten 与 RawValue 的读取边界;换成原始值类型并不保证所有中间缓冲路径都能保留它。
只关心数值时,修改比较规则要有协议依据
文本快照测试可以改为检查项目真正承诺的结构和数值,但大数字需要支持对应范围、精度和指数的比较方式。签名或内容寻址协议若有既定规范化算法,按那个算法处理,不能自行删掉所有正号、指数零或小数点来“统一”。
回查无符号正指数、显式正负指数、大小写 E、零与业务边界的大数,同时比较原始字节用途和业务运算用途。保留旧输出样本,确认哪些下游依赖逐字表示后,再决定更新快照、调整存储格式或迁移签名约定。


