前端处理长整数时失真问题的介绍&解法

423 阅读3分钟

背景

Javascript中可以用Number表示的最大整数为2^53 - 1 = 9007199254740991,最小整数为其负数-9007199254740991(即Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER ,超出这个范围的值无法被精确表示,也因此原生的JS语言并不适合严谨学科的精确计算),而后端语言如Python能支持的整数往往更大,这就导致了当后端主导业务设计(往往如此)的时候,有可能设计出范围超出MAX_SAFE_INTEGER的业务整数变量(比如ID)。如果业务功能还在方案阶段,我们可以建议将这类业务变量设计成字符串以规避MAX_SAFE_INTEGER将来可能带来的问题,但如果建议被不可抗理由驳回,或者业务功能早已上线,就得另辟蹊径去解决/预防前端数值失真问题了。

问题描述

可能遇到的最典型的问题就是,JSON.parse(str)时如果str包含长数值,解析出来的json对应数值失真:

业务场景包括:解析编辑器中用户输入的json格式字符串,或者后端返回的json数据等

JSON.parse('9007199254740992567') // 9007199254740993000 失真

巧妇难为无米之炊,解决这个问题的前提是,我们需要一个“数值容器”来存放和表示这类长整数,它可以是BigInt(详细用法点击链接学习),JS中用来表示任意大整数的内置对象;

BigInt('9007199254740992567') // 9007199254740992567n 保真

也可以是big-number,一个用于进行任意精度数学运算的npm包。

有了BigInt或者big-number,我们解析的长整数总算有个容器了,但是随之而来的问题是:对包含这两类容器的对象进行序列化时会出错,例如对任何 BigInt 值而言使用 JSON.stringify() 都会引发 TypeError,因为默认情况下 BigInt 值不会在 JSON 中序列化(即BigInt对象并不存在toJSON方法,除非自己实现)。这意味着,假如我们需要将包含BigInt的对象序列化(比如在POST请求中作为body)就会发生错误

解决方案 - json-bigint

这个npm包基于big-numberBigInt来进行json的序列化和反序列化,无论是解析长整数字符串,还是将解析后的对象序列化并在请求中发送,还是确保json格式响应被解析为保真的数值(例如在axios的transformRequest/transformResponse勾子中进行格式转换),这几种情况利用它的能力都可以妥善处理

var JSONbig = require('json-bigint');
 
var json = '{ "value" : 9223372036854775807, "v2": 123 }';
console.log('Input:', json);
console.log('');
 
console.log('node.js built-in JSON:');
var r = JSON.parse(json);
console.log('JSON.parse(input).value : ', r.value.toString());
console.log('JSON.stringify(JSON.parse(input)):'JSON.stringify(r));
 
console.log('\n\nbig number JSON:');
var r1 = JSONbig.parse(json);
console.log('JSONbig.parse(input).value : ', r1.value.toString());
console.log('JSONbig.stringify(JSONbig.parse(input)):'JSONbig.stringify(r1));

// Output:
/**
Input: { "value" : 9223372036854775807, "v2": 123 }

node.js built-in JSON:
JSON.parse(input).value :  9223372036854776000
JSON.stringify(JSON.parse(input)): {"value":9223372036854776000,"v2":123}


big number JSON:
JSONbig.parse(input).value :  9223372036854775807
JSONbig.stringify(JSONbig.parse(input)): {"value":9223372036854775807,"v2":123}
*/