JavaScript 对含 BigInt 的对象调用 JSON.stringify 报 TypeError
默认 JSON 序列化遇到 BigInt 会抛出类型异常。用局部 replacer 按接口契约转换成十进制字符串,并在接收方按已知字段恢复,避免转 Number 丢失精度。
对象能够在控制台正常打印,但交给 JSON.stringify() 后抛出 TypeError: Do not know how to serialize a BigInt。检查字段时,经常会看到 0n 或更大的带 n 数值:它们是 BigInt,默认 JSON 序列化不会自动把它们转换成普通数字。
下面适用于支持 BigInt 的 JavaScript 运行环境,讨论普通对象和数组中的原始 BigInt 值。不同引擎的错误文字可能不同。
先确定字段类型和接口约定
const record = { id: 9007199254740993n, count: 2 };
console.log(typeof record.id);
预期类型是 bigint。控制台显示对象的能力不等于该对象可以直接编码为 JSON。嵌套对象或数组里的 BigInt 也会触发这个问题。
修复前先确定接收方怎样表示大整数。ID 通常可以按十进制字符串传输;如果接收方坚持 JSON 数字,需要另外保证它的解析器能够保留所需精度,不能只在发送方消除异常。
用局部 replacer 转成字符串
接收方接受字符串时,可在这次序列化中明确转换:
const record = { id: 9007199254740993n, count: 2 };
const json = JSON.stringify(record, (key, value) => {
return typeof value === "bigint" ? value.toString() : value;
});
console.log(json);
预期 JSON 为 {"id":"9007199254740993","count":2}。普通数值字段仍是数字,BigInt 字段变成带引号的字符串。原对象没有因此被改写。
转换后,接口的数据格式也随之改变。接收端解析后,id 的类型是字符串,不能继续假设它是数字。若某个接口只允许指定字段采用大整数字符串,应在该接口边界明确字段转换,避免把所有字段的类型变化隐藏在通用封装里。
需要恢复 BigInt 时,按已知字段解析
const data = JSON.parse(json);
if (typeof data.id !== "string" || !/^[0-9]+$/.test(data.id)) {
throw new TypeError("id must be a decimal digit string");
}
const id = BigInt(data.id);
console.log(id === record.id);
对于上述 ID 契约,预期比较结果为 true。示例只接受非负十进制整数字符串。需要负数或其他形式时,应另行定义格式;不要把 JSON 中所有看起来像数字的字符串都自动改成 BigInt,文本编号中的前导零等信息可能有意义。
不要通过 Number 转换掩盖精度问题
把 BigInt 转成 Number 可以绕过这种类型异常,但超出安全整数范围时可能改变整数值。Number.MAX_SAFE_INTEGER 是 9007199254740991,示例 ID 已超出这个范围。先转 Number 再转字符串,不能恢复转换时丢掉的信息。
也不要为了一个接口随手修改 BigInt.prototype.toJSON,那会改变同一运行环境中其他调用的序列化行为。局部 replacer 的影响范围更容易追踪。
修复后验证包含嵌套 BigInt 的数据、大于安全整数上限的 ID,以及普通数字字段。序列化成功后还要验证接收方的字段类型与值;这个转换不会解决循环引用等其他序列化错误。
发布接口变更前保留原契约并同步接收端。若要回退,发送与接收两端一起恢复约定,不能单边把字符串改回可能损失精度的 Number。


