js处理Json中的长整数
在前端页面展示数据的时候,通常都需要处理来自后端的json数据。通常这个过程都是非常简单的,比如通过jQuery的ajax(或者更暴力的getJSON)。但是如果服务器传来的json中包含一个很大的整数,如 {"v": 123456789123456789123} ,那么接受后会发现变成了 {v: 123456789123456800000} 。
问题原因
js是弱类型语言,所有的数字类型统称为Number类型,不区分int、long、double等。而Number是根据IEEE 754的double来实现的,即所有的Number类型都是64位双精度实型。sf上提供了一个对IEEE 574非常友好的讲解,这里不再赘述。在文章中也提到了NUMBER.MAX_SAFE_INTEGER的计算方法,是9007199254740991,所有大于这个数的整数都会损失精度。
可行的解决方案
- 最直接的,让服务端传数据的时候,把超长的字段强转成string。但是如果服务端类型敏感,且比较复杂的时候,通常不太想这么搞。
- 前端实现一个json parser。
制作json parser
在服务端开发中,通常使用bison可以定制json parser(在fp中则更简单),js中有没有类似bison的工具呢?有——jison,号称“bison in javascript”,通过npm可以安装,不过他的文档地址似乎不太对,需要访问这里。好在功能上用起来还是没啥问题的。
npm install jison 之后,在 lib 目录下提供了 cli.js 来生成我们的parser。参数有两个(通常情况,有很多我们不太需要,详见cli.js代码), cli.js grammaFile lexFile ,grammaFile是语法文件,lexFile是词表文件。
下面就是怎么写这两个文件了,可以通过文档来学习一下这类文件的语法。如果不想这么麻烦,还可以在jison作者的另一个项目——jsonlint里面找到,在该项目github中的src目录下提供了jsonlint.y(grammaFile)和jsonlint.l(lexFile)。使用这两个文件可以直接生成jsonlint.js,放到网页中当json parser来使用。
但是我们的目的是让一些会丢失精度的整数被保留下来,最好的方法是:当整数超过了安全范围的时候,使用字符串表示。我们可以通过修改jsonlint.y来达到这个目的。
原本对JSONNumber的定义是
1 2 3 4 5 6 |
JSONNumber : NUMBER { $$ = Number(yytext) } ; |
在这里yytext是要进行解析的原始数据,$$是结果。我们可以修改成
1 2 3 4 5 6 |
JSONNumber : NUMBER { // If integer is too long, use string to store it $$ = yytext == String(Number(yytext))? Number(yytext): yytext; } ; |
就OK了。同理的,如果想把传来的string类型的数字还原成Number类型,也可以在JSONString中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 |
JSONString : STRING { // replace escaped characters with actual character $$ = yytext.replace(/\\(\\|")/g, "$"+"1") .replace(/\\n/g,'\n') .replace(/\\r/g,'\r') .replace(/\\t/g,'\t') .replace(/\\v/g,'\v') .replace(/\\f/g,'\f') .replace(/\\b/g,'\b'); $$ = String(Number($$)) == $$ ? Number($$): $$; } ; |
BTW,如果想把以String方式传来的实数也还原成Number的形式,要使用类似ABS(a-b)<epsilion的方式,否则如果传过来一个”0.00000″就没办法被转换了。。。
然后我们在需要使用的网页上加上
1 2 3 4 5 6 7 8 9 10 11 |
<script src="static/jsonlint.js"></script> <script> function func() { $.get(url, function (data) { data = jsonlint.parse(data); $ = jQuery; // do something }); } </script> |
就可以使用了。但是注意这句 $ = jQuery ,因为jison生成的代码中,将 $ 赋值成了其他东西,暂时还没找到无害的修改方法(jison里面代码有点难读。。),暂时我是这么用的。