最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何手写一个JSON解析器?

    正文概述 掘金(dyhtps)   2020-12-21   582

    前言

    前一段时间在工作的时候,遇到了如下的问题。后端传给我的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的目录结构

    如何手写一个JSON解析器?

    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不在是空格字符串。

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

    起源地下载网 » 如何手写一个JSON解析器?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元