前言

前一段时间在工作的时候,遇到了如下的问题。后端传给我的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.parseJSONbig.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不在是空格字符串。

// whitewhite = 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 + "'");  },