当前位置: 首页 > 后端技术 > Node.js

如何手写一个JSON解析器?

时间:2023-04-03 11:01:04 Node.js

前言前段时间在工作的时候遇到了如下问题。后端发给我的json中,id字段使用了number的格式,但是id的size超出了2^53-1到2^53-1的范围,造成JSON.parse解析时number溢出.后台不愿意修改接口。最后使用json-bigint库解析JSON,替代JSON.parse。将大数字直接解析为字符串。我对json-bigint的工作原理很好奇,于是阅读了json-bigint的源码,发现原理并不复杂,所以写了这篇文章供大家参考。JsonBigint的使用首先介绍JsonBigint的基本使用//Installnpminstalljson-bigintimportJSONbigfrom'json-bigint';constjson='{“值”:9223372036854775807,“v2”:123}';//9223372036854776000发生溢出JSON.parse(json).value//'9223372036854775807'将一个大数转换成字符串JSONbig.parse(json).valueJsonBigintJsonBigint的原理是将JSON中的每一个字符一一解析,并根据to规则将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。varjson_parse=require('./lib/parse.js');//调用json_parse并将返回值暴露给parse属性module.exports.parse=json_parse();JSONbig.parse在parse.jsparse.js核心代码所在的地方,我删除了一些特殊情况的判断,只保留了核心部分的源码,方便大家使用。下面我们来解读一下核心源码。先解释一下入口函数。入口函数的参数和可变source参数就是我们需要解析的json字符串at和index。我们需要从头到尾逐个字符地解析json,所以index初始等于0。ch,是当前regex解析的字符串,默认等于空字符串。文本,源参数的副本function(source){varresult;文本=来源+'';在=0;ch='';结果=值();白色的();if(ch){error('语法错误');}返回结果;};然后入口参数调用值函数,值函数会开始解析文本变量,并返回解析后的内容。解析完成后,如果有多余的非空格字符没有解析出来,说明这个json不合法。抛出错误,否则返回值返回的结果。value因为json在解析之前是字符串的格式,所以我们可以根据字符串的第一个字符来判断json的类型。如果是{,说明这个json解析后应该是一个对象;如果是[,说明json解析后应该是一个数组;如果是",说明解析后应该是一个字符串(JSON标准是用");但是,如果是负数,如果开头的字符串在0到9的范围内,就说明它是一个字符串。如果不是,则将其处理为布尔值或空值=function(){white();switch(ch){case'{':返回对象();案例'[':返回数组();case'"':returnstring();case'-':returnnumber();default:returnch>='0'&&ch<='9'?number():word();}};在value函数,第一句调用whatisthewhitefunction,whitefunctiondoeswhatdoeswhatdoeswhitefunctionwhitefunction逐字读取json字符串(并排除空格字符),并将读取到的字符串赋值给ch变量。我们根据ch变量并结合以上规则,开始使用不同的函数开始解析white&nextwhite函数主要用于删除json中多余的空格字符。white函数中会开启一个while循环,如果ch是空格字符串,ch&&ch<=''循环条件将返回true,while循环将继续,直到ch不再是空格字符串。//whitewhite=function(){while(ch&&ch<=''){next();}},next函数会根据索引at取出json字符串中对应位置的字符,at索引会自己增加,next函数的另一个作用是判断参数是否等于ch,以及如果不相等,将抛出错误。//nextfunctionnext=function(c){if(c&&c!==ch){error("Expected'"+c+"'insteadof'"+ch+"'");}ch=文本。字符(在);在+=1;returnch;},object我们先来看下object类型的jsonsheet是什么样子的"{"key":value}"或者"{}"。我们可以看到第一个字符是“{”。而第二个非空字符必须也应该是",或者}。否则json是非法的。如果是},说明是json的空对象,直接返回空对象即可。如果是",这意味着json具有属性。“和”之间应该是一个字符串,这是对象的第一个属性。如果这两个都不成立,则对象json是非法的。需要抛出一个错误。对于value,value的类型可以是objcet,array,boolean,string,number是不确定的object=function(){varkey,object=Object.create(null);if(ch==='{'){//判断ch是否等于“{”,读取第二个字符next('{');//如果第二个字符是空格字符,white函数将尝试读取第一个非空格字符//并将第二个非空格字符分配给chwhite();//如果第二个字符是“}”,表示该对象是空对象,直接返回空对象if(ch==='}'){next('}');返回对象;}//如果不是},且json有效,则第二个非空格字符应该是"//{和冒号之间,在json中是合法的接下来是对象的key,格式为"key"while(ch){//string读取两个之间的内容""key=string();//读取key后,向后读取white();//key之后的第一个非空字符串应该是:,否则不合法next(':');//:之后应该是属性值的key对应的属性值//类型不固定,我们还需要使用value函数来中间尝试判断属性的类型,做不同的处理//value会返回解析出的属性值并返回。//我们将键和值添加到空对象object[key]=value();//拿到值后,再向后读//如果读到},说明ojbect解析完成,返回对象Canwhite();如果(ch==='}'){下一个('}');返回对象;}//如果读取到,说明还有其他属性,进入下一次迭代next(',');白色的();}}error('坏对象');};string在json中,字符串内容必须用两个双引号括起来,例如{"key":"value"}。字符串函数读取两个双引号之间的内容并返回。如果读到最后没有读到下一个",说明字符串没有关闭,不合法则抛出错误string=function(){varstring='';//如果ch是"//while循环将始终尝试读取到第二个"//并将两者之间的内容分配给字符串//最后返回字符串if(ch==='"'){varstartAt=at;while(next()){if(ch==='"'){if(at-1>startAt)字符串+=文本。子串(起始点,在-1);下一个();返回字符串;}}}error('坏字符串');},array先来看看array类型的json在第一个字符[.数组的第一个内容,或]。如果是],则该数组为空数组。只返回一个空数组。如果不是,则该数组不是空数组。由于数组中内容的类型不固定,我们还需要使用值函数来尝试判断数组中内容的类型。然后做不同的事情。直到读取到一个]字符,然后返回整个数组。数组=函数(){变量数组=[];//如果是数组类型,第一个字符必须是[,否则就是非法数组if(ch==='['){next('[');//尝试读取第二个非空格字符串white();//如果第二个非空格字符是],表示它是一个空字符串,直接返回一个空数组if(ch===']'){next(']');返回数组;}//如果第二个非空格字符不是]//由于数组中内容的类型是不确定的,我们需要使用值函数读取内容并返回。while(ch){array.push(value());白色的();//读取完第一个内容后,如果后面的字符是],则表示读取到数组,可以返回数组if(ch==']'){next(']');返回数组;}//读取完第一个内容后,如果后面的字符是逗号,说明数组还有其他内容,进入下一个循环next(',');白色的();}}error('坏数组');},numbernumber=function(){varnumber,string='';//如果第一个字符串是-,那么这个数可能是负数,继续往后看if(ch==='-'){string='-';下一个('-');}//如果是0到9之间的字符,string会累加while(ch>='0'&&ch<='9'){string+=ch;下一个();}//如果是小数点处理if(ch==='.'){string+='.';while(next()&&ch>='0'&&ch<='9'){string+=ch;}}//如果是科学计数法处理if(ch==='e'||ch==='E'){string+=ch;下一个();如果(ch==='-'||ch==='+'){string+=ch;下一个();}while(ch>='0'&&ch<='9'){字符串+=ch;下一个();}}//将字符串转换为数字,并将数字赋值给数字变量number=+string;//如果数字是nan,或者政府是无限的isFinite返回false//例如,isFinite('-'),返回false//如果返回false,则抛出错误if(!isFinite(number)){error('坏号');}else{//如果字符串的长度大于15,则指定number的大小已经溢出,我们返回字符串if(string.length>15)returnstringelse//如果字符串的长度小于15,wereturnthenumbertypereturnnumber}wordword函数主要是用来处理boolean类型和null的我们先来看看boolean类型和null在json中是什么样子的。"{"key1":true,"key2":false,"key3":null}",在json中是没有双引号的普通字符。word=function(){switch(ch){//如果第一个字符是t,那么接下来的字符必须为真,否则会抛出错误case't':next('t');接下来('r');接下来('你');接下来('e');//返回真返回真;//如果第一个字符是f,那么下一个字符必须是false,否则抛出Errorcase'f':next('f');下一个('一个');接下来('l');下一个('');接下来('e');//返回假返回假;//如果第一个字符是n,那么接下来的字符必须是null,否则会抛出错误case'n':next('n');接下来('你');接下来('l');接下来('l');//返回空值返回空值;}error("意外'"+ch+"'");},