前言
前一段时间在工作的时候,遇到了如下的问题。后端传给我的 JSON,其中 id 字段应用的 number 的格局,然而 id 的大小超过了 2^53 – 1 ~ 2^53 – 1 的范畴。导致 JSON.parse 解析的过程中数字溢出。后端又不违心批改接口。最初应用了 json-bigint 这个库解析 JSON,代替了 JSON.parse
。将大数间接解析为字符串。
我很好奇,json-bigint 的工作原理,于是浏览了 json-bigint 的源码,发现原理并不简单,于是写下这篇文章,供大家参考。
JsonBigint 的应用
首先介绍下 JsonBigint 的根本应用
// 装置
npm install json-bigint
import JSONbig from 'json-bigint';
const json = '{"value": 9223372036854775807,"v2": 123}';
// 9223372036854776000 产生了溢出
JSON.parse(json).value
// '9223372036854775807' 将大数转为了字符串
JSONbig.parse(json).value
JsonBigint 的原理
JsonBigint 的原理,次要是逐个解析 JSON 中的每一个字符,并依据不同的规定将 value,解析为 object,array,number,,string,boolean 值等。
JsonBigint 的目录构造
JsonBigint 次要裸露了两个 API, JSONbig.parse
和 JSONbig.stringify
,咱们次要看下 JSONbig.parse
办法。JSONbig.parse 办法的代码,次要在 parse.js 文件中。
index.js
通过入口文件 index.js 可得悉 parse 函数,本身会返回一个函数。并将返回函数裸露到 API 上。
var json_parse = require('./lib/parse.js');
// 调用 json_parse,并将返回值裸露给 parse 属性
module.exports.parse = json_parse();
parse.js
parse.js 中是 JSONbig.parse 的外围代码所在的地位,我删除了局部对非凡状况的判断,只保留了源码的外围局部,以不便大家了解。接下来,咱们就来解读下外围源码吧。
入口函数
先来解释下,入口函数的参数和变量
source
参数, 是咱们须要解析的 json 字符串at
,索引。咱们须要从头到尾一一字符解析 json,所以索引初始等于 0。ch
, 是以后正则解析的字符串,默认等于空字符串。text
,source 参数的正本
function (source) {
var result;
text = source + '';
at = 0;
ch = ' ';
result = value();
white();
if (ch) {error('Syntax error');
}
return result;
};
接着入口参数调用了 value 函数,value 函数会开始解析 text 变量,并返回解析后内容。在解析实现后,如果还有多余的非空格字符没有被解析,阐明 json 是不非法的。抛出谬误,否则返回 value 返回的后果。
value
因为 json 在没有解析前,都是字符串的格局,所以咱们能够依据字符串的第一个字符,判读 json 到底是什么类型的。
- 如果是
{
, 阐明 json 解析后应该是 object - 如果是
[
, 阐明 json 解析后应该是 array - 如果是 ”,阐明解析后应该是字符串(JSON 的规范是应用“)
- 如果是 -,阐明是数字,只不过是正数
- 如果结尾的字符串是 0~9 内,阐明是字符串。如果不是,依照 boolean 或者 null 解决
value = function () {white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
return string();
case '-':
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word();
}
};
value 函数中,第一句话调用的是 white 函数,white 函数是做什么的呢?
white 函数会逐字读取 json 字符串(并排除空格字符),并将读取的字符串赋予 ch 变量。咱们依据 ch 变量,并联合下面的规定,开始应用不同函数开始解析
white & next
white 函数的作用次要是用来,删除 json 中多余的空格字符。white 函数中会开启一个 while 循环,如果 ch 是空格字符串,ch && ch <= ' '
的循环条件,就会返回 true,while 循环就会继续下去,直到 ch 不在是空格字符串。
// white
white = function () {while (ch && ch <= ' ') {next();
}
},
next 函数,会依据索引 at,取出 json 字符串中对应地位的字符。at 索引会自增。next 函数还有一个作用是判读参数是否和 ch 相等,如果不相等会抛出谬误。
// next 函数
next = function (c) {if (c && c !== ch) {error("Expected'" + c + "'instead of'" + ch + "'");
}
ch = text.charAt(at);
at += 1;
return ch;
},
object
咱们首先看下 object 类型的 json 张什么样子 ”{“key”: value}” 或 ”{}”。咱们能够看到第一个字符是 ”{“,。而第二个非空字符必须且应该是 ”, 或者是}。否则 json 就是不非法的
如果是 }, 阐明是 json 一个空对象,间接返回空对象就行。
如果是 ”, 阐明 json 是有属性的。” 和 ” 之间的,应该是一个字符串,这个字符串是 object 的第一个属性。
如果这两个都不是,阐明这个 object json 是非法的。须要抛出谬误。
对于 value,value 的类型可能是 objcet,array,boolean,string,number 是不确定的
object = function () {
var key,
object = Object.create(null);
if (ch === '{') {
// 判读 ch 是否等于 "{", 并读取第二个字符
next('{');
// 如果第二个字符是空格字符,white 函数会尝试读取到第一个非空格的字符
// 并将第二个非空格字符赋值给 ch
white();
// 如果第二个字符是 "}",阐明这个 object 是一个空对象,间接返回空对象即可
if (ch === '}') {next('}');
return object;
}
// 如果不是 }, 并且 json 非法的状况下,第二个非空格字符应该是 "// 在 { 和 冒号 之间,在 json 是非法状况下,是 object 的 key,格局为"key"
while (ch) {
// string 读取两个 "" 之间的内容
key = string();
// 读取完 key 后,接着向后读取
white();
// key 之后的第一个非空字符串,应该是:, 否则就是不非法的。next(':');
// : 之后就应该是 key 对应的属性值
// 属性值的类型不固定,咱们还须要借助 value 函数,尝试判断中属性的类型,做不同的解决
// value 会返回解析后的属性值,并返回。// 咱们将 key 和 value 增加到空对象上
object[key] = value();
// 在获取完 value 之后,接着向后读取
// 如果读取到 }, 阐明 ojbect 解析实现,返回 object 即可
white();
if (ch === '}') {next('}');
return object;
}
// 如果读取到,阐明还有其余属性,进入下一次的迭代
next(',');
white();}
}
error('Bad object');
};
string
在 json 中字符串内容必须应用两个双引号抱起来,举一个例子 {“key”:”value”}。
string 函数会读取两个双引号之间的内容,并返回。如果读取到最初,没有读取到下一个 ”, 阐明字符串没有闭合,不非法抛出谬误
string = function () {
var string = '';
// 如果是 ch 是 "// while 循环会始终尝试读取到第二个"
// 并且将两个 " 之间的内容赋予 string
// 最初将 string 返回
if (ch === '"') {
var startAt = at;
while (next()) {if (ch === '"') {if (at - 1 > startAt) string += text.substring(startAt, at - 1);
next();
return string;
}
}
}
error('Bad string');
},
array
咱们首先看下 array 类型的 json 张什么样子 ”[value, value]”, 在第一个字符 [之后。要么是数组的第一个内容,要么是]。
如果是 ], 阐明数组是一个空数组。返回空数组即可。
如果不是,阐明数组不是空数组。因为数组中内容的类型不固定,咱们还须要借助 value 函数,尝试判断数组中内容的类型。而后做不同的解决。直到读取到 ] 字符,而后返回整个数组。
array = function () {var array = [];
// 如果是数组类型,第一个字符必须是 [, 如果不是阐明是不非法的 array
if (ch === '[') {next('[');
// 尝试读取到第二个非空格的字符串
white();
// 如果第二个非空格的字符是 ], 阐明是空字符串,间接返回空数组
if (ch === ']') {next(']');
return array;
}
// 如果第二个非空格字符,不是 ]
// 因为数组中的内容的类型,不确定,咱们须要应用 value 函数,读取内容并返回。while (ch) {array.push(value());
white();
// 在读取完第一个内容,如果之后的字符是 ], 阐明数组读取结束,返回数组即可
if (ch === ']') {next(']');
return array;
}
// 在读取完第一个内容,如果之后的字符是逗号,阐明数组还有其余内容,进入下一次循环
next(',');
white();}
}
error('Bad array');
},
number
number = function () {
var number,
string = '';
// 如果第一个字符串是 -,阐明 number 可能会是正数,持续向后查找
if (ch === '-') {
string = '-';
next('-');
}
// 如果是 0~9 之间的字符,string 进行累加
while (ch >= '0' && ch <= '9') {
string += ch;
next();}
// 如果是小数点的解决
if (ch === '.') {
string += '.';
while (next() && ch >= '0' && ch <= '9') {string += ch;}
}
// 如果是迷信计数法的解决
if (ch === 'e' || ch === 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();}
while (ch >= '0' && ch <= '9') {
string += ch;
next();}
}
// 将 string 转为数字,并将数字赋予 number 变量
number = +string;
// 如果 number 是 nan,或者政府无穷大 isFinite 返回 false
// 比方,isFinite('-'),返回 false
// 如果返回 false,抛出谬误
if (!isFinite(number)) {error('Bad number');
} else {
// 如果 string 的长度大于 15, 阐明 number 的大小曾经溢出,咱们返回字符串
if (string.length > 15)
return string
else
// 如果 string 的长度小于 15, 咱们返回数字类型
return number
}
word
word 函数次要是用来解决 boolean 类型和 null 的。
咱们首先看下,boolean 类型和 null 在 json 中张什么样。”{“key1″:true,”key2″:false,”key3″:null}”, 在 json 中它们就是没用应用双引号包裹的一般字符。
word = function () {switch (ch) {
// 如果第一个字符是 t,那么接下来的字符必须顺次是 t r u e,否则会抛出谬误
case 't':
next('t');
next('r');
next('u');
next('e');
// 返回 true
return true;
// 如果第一个字符是 f,那么接下来的字符必须顺次是 f a l s e,否则会抛出谬误
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
// 返回 false
return false;
// 如果第一个字符是 n,那么接下来的字符必须顺次是 n u l l,否则会抛出谬误
case 'n':
next('n');
next('u');
next('l');
next('l');
// 返回 null
return null;
}
error("Unexpected'" + ch + "'");
},