js正則分析案例——以JSON格式校驗(yàn)為例

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)容:

JSON介紹

建議對(duì)照這份說(shuō)明食用本文,會(huì)更加香甜。

另外,也可以同時(shí)對(duì)照javascript正則表達(dá)官方說(shuō)明來(lái)閱讀本文:

javascript正則表達(dá)式

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è)步驟:

  1. 對(duì)字符串執(zhí)行了正則替換,將一部分內(nèi)容替換為@符號(hào):str.replace(/\\["\\\/bfnrtu]/g, '@')

  2. 繼續(xù)對(duì)字符串執(zhí)行正則替換,將上一步結(jié)果的一部分內(nèi)容替換為]號(hào):

    replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')

  3. 繼續(xù)對(duì)上一步結(jié)果執(zhí)行正則替換,將部分內(nèi)容替換為空白符

  4. 對(duì)上一步的結(jié)果執(zhí)行了正則校驗(yàn):/^[\],:{}\s]*$/.test()

  5. 上一步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),這段正則替換將原始JSONstring2的值進(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+)?

  1. 分支結(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ù)])
  2. 所以這一步實(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ā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容