js正則分析案例——以JSON格式校驗(yàn)為例
[TOC]
緣起
最近在研究javascript中對(duì)各種數(shù)據(jù)類(lèi)型與格式的判斷,以及各種第三方庫(kù)提供的字符串處理方法,發(fā)現(xiàn)有大量的地方運(yùn)用了正則,并且有些正則及其復(fù)雜。對(duì)于上層應(yīng)用開(kāi)發(fā)人員來(lái)說(shuō),正則可能用到的地方并不是太多,最常用的無(wú)非就是表單驗(yàn)證,而那些常見(jiàn)的表單驗(yàn)證正則網(wǎng)絡(luò)上也是一搜一大堆,自然不需要自己再去構(gòu)建。
但是,作為一名開(kāi)發(fā)人員,肯定不甘于一直做一個(gè)“工具小子”,而希望自己也能寫(xiě)出一些更底層的代碼,甚至是開(kāi)發(fā)一些底層的庫(kù)來(lái)供別人使用,所以正則就成為了一個(gè)繞不過(guò)去的坎。
當(dāng)我們決定自己寫(xiě)正則的時(shí)候,總是會(huì)先去看看前輩們是如何寫(xiě)的,畢竟站在巨人的肩膀上才能站得更高。所以解讀正則就是一個(gè)不可獲取的技能。
今天,我們就拿一個(gè)校驗(yàn)JSON格式的正則來(lái)示范一下如何拆解復(fù)雜的正則表達(dá)式,以方便我們更好地閱讀和理解大神們的源碼。BTW,也可以借機(jī)更深入的了解下JSON規(guī)范。
JSON
JSON是一種被廣泛應(yīng)用于各種編程語(yǔ)言的數(shù)據(jù)交換格式,下面是JSON格式的官方說(shuō)明,閱讀它有助于更好地理解本文內(nèi)容:
建議對(duì)照這份說(shuō)明食用本文,會(huì)更加香甜。
另外,也可以同時(shí)對(duì)照javascript正則表達(dá)官方說(shuō)明來(lái)閱讀本文:
javascript判斷一個(gè)字符串是否為JSON格式
下面這段代碼來(lái)自于網(wǎng)絡(luò),是使用正則判斷一個(gè)字符串是否為
JSON字符串:
var isJSON = function (str){
if (/^[\],:{}\s]*$/.test(str.replace(/\\["\\\/bfnrtu]/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
return true;
}else{
return false;
}
}
解讀過(guò)程
為了更直觀(guān)地理解上面這段代碼是如何判斷JSON格式字符串的,我們準(zhǔn)備了一段復(fù)雜的但合法的JSON字符串,一步一步來(lái)理解上面這段代碼的正則替換與校驗(yàn)過(guò)程。我們準(zhǔn)備的JSON數(shù)據(jù)如下:
{
"array": [
1,
-2,
2.22,
3e12,
-32.1e-12,
[1,2,3],
{
"abc":123.34
}
],
"boolean": true,
"null": null,
"number": 123,
"object": {
"a": "b",
"c": "d",
"e": "f",
"subarray":[1,2,3,4]
},
"string": "Hello World",
"string2":"\\babc\\"def\\fh\\rjkl\\nmn\sss\\tie\\uABCF"
}
我們將它格式化為字符串:
var str = '{"array":[1,-2,2.22,3e12,-32.1e-12,[1,2,3],{"abc":123.34}],"boolean":true,"null":null,"number":123,"object":{"a":"b","c":"d","e":"f","subarray":[1,2,3,4]},"string":"Hello World","string2":"\\babc\\"def\\fh\\rjkl\\nmn\sss\\tie\\uABCF"}';
第一步 整體分析
上面的isJson() 方法中,實(shí)際上執(zhí)行了以下幾個(gè)步驟:
對(duì)字符串執(zhí)行了正則替換,將一部分內(nèi)容替換為
@符號(hào):str.replace(/\\["\\\/bfnrtu]/g, '@')-
繼續(xù)對(duì)字符串執(zhí)行正則替換,將上一步結(jié)果的一部分內(nèi)容替換為
]號(hào):replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 繼續(xù)對(duì)上一步結(jié)果執(zhí)行正則替換,將部分內(nèi)容替換為空白符
對(duì)上一步的結(jié)果執(zhí)行了正則校驗(yàn):
/^[\],:{}\s]*$/.test()上一步
test()方法返回一個(gè)布爾值,作為if判斷的最終條件,得到結(jié)果
第二步 分步驟解析
1. 替換控制字符為@符號(hào)
我們看這個(gè)正則:str.replace(/\\["\\\/bfnrtu]/g, '@')
在上文的鏈接的JSON介紹頁(yè)面中,介紹了JSON合法字符串值可接受的控制字符,如下圖:
/\\["\\\/bfnrtu]/g
這條正則代表JSON標(biāo)準(zhǔn)中規(guī)定的可接受的特殊字符,由于反斜杠(\)為轉(zhuǎn)義字符,所以我們先將正則首尾的斜杠和特殊符號(hào)之前的轉(zhuǎn)義去掉,得到:
* \["\/bfnrtu],即 “\” 后面加上 【"\/bfnrtu】中的其中一個(gè)字符([]內(nèi)的為字符組,在正則中代表其中一個(gè)):
* \" 雙引號(hào)
* \/ 斜杠
* \\ 反斜杠
* \b --backspace 后退符號(hào)
* \f--formfeed 換頁(yè)符號(hào)
* \n--linefeed 換行符號(hào)
* \r--carriage return 回車(chē)符號(hào)
* \t--horizontal tab 垂直制表符
* \u--unicode編碼
我們對(duì)我們準(zhǔn)備的那一段JSON字符串做同樣的操作:
var str1 = str.replace(/\\["\\\/bfnrtu]/g, '@');
console.log(str1);
得到:
{"array":[1,-2,2.22,3e12,-32.1e-12,[1,2,3],{"abc":123.34}],"boolean":true,"null":null,"number":123,"object":{"a":"b","c":"d","e":"f","subarray":[1,2,3,4]4]},"string":"Hello World","string2":"@abc@def@h@jkl@mnsss@ie@ABCF"}
可以發(fā)現(xiàn),這段正則替換將原始JSON中 string2的值進(jìn)行了替換,因?yàn)樗闹抵虚g包含了控制字符:
"string2":"\\babc\\"def\\fh\\rjkl\\nmn\sss\\tie\\uABCF"
// =>
"string2":"@abc@def@h@jkl@mnsss@ie@ABCF"
大家一定注意到了,上面正則分析中是一個(gè)反斜杠加上一個(gè)控制字符, 而我么原始字符串中缺失兩個(gè)斜杠加上控制字符,這是因?yàn)槲覀冊(cè)?code>JSON字符串中依然需要對(duì)控制字符前面的反斜杠進(jìn)行轉(zhuǎn)義,所以必須在前面再加一個(gè)反斜杠來(lái)承擔(dān)轉(zhuǎn)義的工作,否則將不會(huì)通過(guò)校驗(yàn)。
而\s前面為什么沒(méi)有轉(zhuǎn)義呢,因?yàn)樗粚儆?code>JSON規(guī)范中的控制字符,它只單純地表示空格。
2. 替換鍵名和值為右中括號(hào)
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
我們將正則部分拆解如下:
1.去掉正則格式符(/,/g):
? "[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?
- 分支結(jié)構(gòu)(即使用|隔開(kāi)的部分),有以下分支:
* "[^"\\n\r]"
* 雙引號(hào)(")后面跟上除了(^在正則中代表“非”,即“不是xxx”)【",,\n,\r】之外的任意字符任意次(量詞在正則中代表任意次),后面再跟一個(gè)雙引號(hào)(")這一步其實(shí)匹配雙引號(hào)包含的所有內(nèi)容,而我們知道,在合法JSON字符串中,鍵名是必須用雙引號(hào)包含的,而合法值里的 string類(lèi)型也是必須用雙引號(hào)包含起來(lái)的。- true
- false
- null
-
-? \d+ (?: \.\d*)? (?: [eE] [+\-]? \d+)?【匹配number】- 0個(gè)【即正數(shù)】或者1個(gè)負(fù)號(hào)(-)【即負(fù)數(shù)】,(?在正則中代表0個(gè)或1個(gè))
- 后面跟上1個(gè)以上的數(shù)字(\d),
- 再跟上零組【有可能沒(méi)有小數(shù)部分】或一組(括號(hào)為分組,?:代表非捕獲分組)一個(gè)點(diǎn)后面跟上任意個(gè)數(shù)字【數(shù)字的小數(shù)部分】,
- 再跟上指數(shù)部分(e或E后面跟上數(shù)字[可正可負(fù)])
- 所以這一步實(shí)際上就是把合法的
json鍵名和值(string/true/false/null/number)替換為右中括號(hào)
來(lái)看看我們準(zhǔn)備的JSON字符在這一步后被替換為什么樣:
var str2 = str1.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
console.log(str2);
{]:[],],],],],[],],]],{]:]}],]:],]:],]:],]:{]:],]:],]:],]:[],],],]]},]:],]:]}
3. 替換行首位置、冒號(hào)、逗號(hào)為空白符
replace(/(?:^|:|,)(?:\s*\[)+/g, '')
去掉正則格式符,得到:
(?: ^|:|, )(?: \s* \[ )+
- 有兩個(gè)非捕獲分組
- 第一組:
^|:|,即匹配行首位置(^)或冒號(hào)(:)或逗號(hào)(,) - 第二組:
\s*\[即匹配任意個(gè)(*)空格(\s)后面跟左中括號(hào)(\[),這種組合可能出現(xiàn)1到多次【應(yīng)對(duì)多層嵌套的情況】 - 最后匹配的就是第一組中的其中一個(gè)符號(hào)后面跟上第二組格式的字符串,比如
:[或者,[這樣的
- 第一組:
看看我們的JSON字符串在這一步后變成了什么樣:
var str3 = str2.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
console.log(str3);
{]],],],],]],],]],{]:]}],]:],]:],]:],]:{]:],]:],]:],]],],],]]},]:],]:]}
4. 使用test()方法校驗(yàn)
/^ [\],:{}\s]* $/.test(str3)
看正則部分: /^ [ \] , : { } \s ]* $/
即行首后面跟上【右中括號(hào)(注意轉(zhuǎn)義),逗號(hào),冒號(hào),左大括號(hào),右大括號(hào),空格】中的任意一個(gè)字符任意次,然后是行尾
根據(jù)這個(gè)規(guī)則,我們的JSON字符串成功通過(guò)了校驗(yàn)。
本文就到這里, 希望可以對(duì)您有所幫助。
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!