正則表達式學習:JAVA使用正則表達式遞歸校驗JSON格式數據

由於工作中用到正則表達式不多,一直沒有好好學習正則表達式。在網上找到了原版的精通正則表達式(第三版)電子版,抽時間詳細學習,下面對最近學到的做個總結。

最近在進行安全檢查漏洞修補,在做XSS攻擊過濾器時,在後臺對單雙引號等字符進行了轉義,這樣影響到了JSON字符串的傳遞。爲了解決這個問題,想在過濾時把JSON參數專門過濾出來不轉義雙引號。於是就開啓了正則表達式的學習之路。

首先先附上JAVA配合正則遞歸校驗JSON的源碼

/**
     * <B>方法名稱:</B>校驗是否是有效JSON數據<BR>
     * <B>概要說明:</B>由於JAVA正則表達式沒法遞歸,不能一個表達式進行匹配,只能用JAVA進行遞歸
     * 字符串傳來後進行匹配,普通類型數據僅匹配格式不捕獲,將可能的JSON類型([] {})進行捕獲,
     * 遞歸進行校驗,共設置四個捕獲組,爲了保證逗號分隔的格式是嚴格正確的,沒有想到好的方法簡化正則表達式
     * 只能把數據分成兩類,一類帶逗號一類不帶分別進行匹配.由於捕獲組僅能匹配最後一個捕獲結果,所以需要手動 進行字符串截取進行遞歸驗證。
     * 
     * 嚴格按照JSON官網給出的數據格式 雙引號引起來的字符串 數字 JSONOBJECT JSONARRAY 波爾值和JSONNull
     * 在[]{}以及逗號前後可以有任意空字符。 <BR>
     * 
     * @param value 數據
     * @return boolean 是/不是
     */
    public boolean isJSON(String value) {
        try {
            boolean result = false;
            String jsonRegexp = "^(?:(?:\\s*\\[\\s*(?:(?:"
                    + "(?:\"[^\"]*?\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?<json1>(?:\\[.*?\\])|(?:\\{.*?\\})))\\s*,\\s*)*(?:"
                    + "(?:\"[^\"]*?\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?<json2>(?:\\[.*?\\])|(?:\\{.*?\\})))\\s*\\]\\s*)"
                    + "|(?:\\s*\\{\\s*"
                    + "(?:\"[^\"]*?\"\\s*:\\s*(?:(?:\"[^\"]*?\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?<json3>(?:\\[.*?\\])|(?:\\{.*?\\})))\\s*,\\s*)*"
                    + "(?:\"[^\"]*?\"\\s*:\\s*(?:(?:\"[^\"]*?\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?<json4>(?:\\[.*?\\])|(?:\\{.*?\\}))))\\s*\\}\\s*))$";

        Pattern jsonPattern = Pattern.compile(jsonRegexp);

        Matcher jsonMatcher = jsonPattern.matcher(value);

        if (jsonMatcher.matches()) {
            result = true;
            for (int i = 4; i >= 1; i--) {
                if (!StringUtils.isEmpty(jsonMatcher.group("json" + i))) {
                    result = this.isJSON(jsonMatcher.group("json" + i));
                    if (!result) {
                        break;
                    }
                    if (i == 3 || i == 1) {
                        result = this.isJSON(value.substring(0, jsonMatcher.start("json" + i))
                                + (i == 3 ? "\"JSON\"}" : "\"JSON\"]"));
                        if (!result) {
                            break;
                        }
                    }
                }
            }

        }
        return result;
    } catch (Exception e) {
        return false;
    }
}

JSON官方給出JSON數據格式
http://www.json.org/json-zh.html
JSONARRAY [value,value,…,value]
JSONOBJECT {“string”:value,…,“string”:value}

正則表達式無法去引用規則,只能引用捕獲組捕獲的內容。所以遞歸由JAVA完成。
首先字符串自身必須滿足上述JSON格式,其中只有嵌套的JSON數據不好判斷,於是將這部分value進行捕獲,捕獲後內容也要符合JSON格式,再調用本身去判斷。

又由於捕獲組只能捕獲最後一個內容,相當於每次校驗只能捕獲到最後兩個JSON格式數據。但是JAVA中可以獲取他們的位置。於是將倒數第二個JSON往後的部分截去,再去校驗截去後的字符串,直到字符串中沒有JSON格式的數據且符合JSON基本數據格式。

 (?:\"[^\"]*?\")   -->  雙引號引起的任意字符串,不可包含雙引號
 本身還有反斜槓,但是項目功能需要所以去掉了
(?:true|false|null) --> 波爾值和JSONNULL
(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?) 數字 浮點數+科學計數法
(?<json2>(?:\\[.*?\\])|(?:\\{.*?\\}))  JSON數據捕獲後迭代進行判斷

正則表達式如同名字所說,是一個規則表達式(Regular Expression)。他表達的是一種規則,我們要學習的就是如何去書寫規則。

最基礎的就是如何去匹配各種字符以及控制字符所出現的順序與個數。
網上到處都有介紹的。
百度百科:https://baike.baidu.com/item/正則表達式/1700215?fr=aladdin#7

我們用一個正則表達式匹配時,不論正則表達式如何寫,如何分組,首先會在內容中去完整整個正則表達式,整個正則表達式匹配是其他操作成立的前提。所以理解書寫一個正則表達式首先去關注整個正則表達式的匹配。

書寫正則表達式可以拆分成很多最基礎的元素進行疊加,就像是疊加積木一樣。
1.字符
正則表達式在校驗字符串時,最基礎的是每一個字符位。每個字符位可以使用字符本身進行匹配比如 regExp=“a” 就匹配字符a,也可以使用方括號+表達式 [表達式] 進行匹配。表達式就是一個集合,可以進行交、並、非等運算去限定可以匹配字符的範圍。比如regExp="[^a]"就匹配除a以外的字符。
在每一個字符後可以限定字符出現的次數,默認不帶匹配符號就是1次。具體符號可以在上面百度百科介紹。

2.字符串
使用括號()將多個字符按順序組合起來就可以成爲一個可操作的字符串。在括號後就可以使用數量符號,連續匹配多次字符串。比如regExp="(aaa)?" 就可以匹配字符串 aaa一次或者0次。

會寫正則表達式就需要三點
1.明確要匹配字符串的格式,具有規律。
2.知道字符用什麼去匹配,比如空格\n 反斜槓\ 得雙寫匹配原字符\等等。
3.知道如何控制字符的數量,比如大括號{5}代表五次,{1-5}代表一次到五次之間都可以。
知道這三個就可以寫出絕大多數的正則表達式了。

比如郵箱校驗
用戶名 + “ @” + 郵箱名 + “.” + “com/cn” (規則可能不準確)
1.用戶名可以是多位字母、數字、下劃線
[a-z|A-Z|0-9|] 匹配一個字符 可以是a-z的小寫字母,A-Z大寫字母,或者是0-9數字,或者是下劃線。
用戶名不能爲空至少有一位。 於是字符出現次數>=1,即 [a-z|A-Z|0-9|
]+
2.@就用原字符匹配就可以 必須出現一次
3.郵箱名比如限定只能是163、126郵箱 (163|126) 必須出現一次
或者和用戶名一樣字母數字下劃線組成的名字
4. 點在正則表達式中代表任意字符,匹配原字符要用 . 必須出現一次
5. com/cn 就是(com|cn)必須出現一次
組合起來就是
[a-z|A-Z|0-9|_]+@(163|126).(com|cn)
這就是最後的正則表達式。

非常簡單 就是把規則拆分成各個小部分,每個部分的字符和次數指定好,拼接起來就OK。需要注意的一點是寫正則不要隨便打空格,空格也是需要去匹配的。

能夠寫最基礎的正則表表達式後就可以繼續學習分組和捕獲。

分組

在上面使用括號()就是所謂的分組。
而捕獲的意思就是能夠把其中的一部分匹配到的內容記錄下來。
就以 [a-z|A-Z|0-9|_]+@(163|126).(com|cn) 爲例。
我匹配的郵箱是[email protected]
這個正則表達式在匹配時,會記錄三組匹配內容。
第一組 group0 :組0是默認的,不需要使用括號,這是所有正則表達式整體表達式匹配結果。匹配成功就記錄整個表達式。所以值就是 [email protected]
表達式中有還有兩對括號,所以還有組1 組2
第二組 group1:第二組表達式是 (163|126) 所以他記錄的就是郵箱名的部分,在我匹配的裏面就是163 ,所以group1的值就是 163
同理第三組group2:就是最後的尾綴即 com
所以次正則表達式我們捕獲到了三組匹配的內容。

捕獲組

捕獲的作用既可以在當前正則表達式中引用,也可以在相應的程序裏使用,比如在JAVA中使用matcher.group()方法獲取內容,進行其他處理。
分組的名稱如果沒有命名,就是默認的數組分組。分組數量好像有上限是9個。
如果想自己給分組命名,就在括號中加入(?<組名>表達式)組名尖括號後一定不要順手打空格!

以上兩種就是 捕獲組。會將匹配內容記錄下來供我們使用。
如果我們不需要讓他記錄,可能會浪費資源,比如內存等。
我們就有了 非捕獲組。

非捕獲組

(?:表達式)
只要在括號中起始加入?:就可以不捕獲該分組。
這時默認的數字分組名稱會統計所有有效的 捕獲組,而不會去管非捕獲組。

還有其他非捕獲組
1.表達式(?=表達式) 2.(?<=表達式)表達式 3.表達式(?!表達式)4.(?<!表達式)表達式
仔細觀察上方四個捕獲組寫法,
1.括號中以?開頭
2. ! 代表 非/否定 ,= 代表 是/肯定,所以?= 就代表着 右邊部分匹配這個表達式, ?! 就代表右邊部分不能匹配這個表達式。他們寫在主表達式的右方,代表 後面的表達式必須出現在主表達式的右邊。
3. 如果要在左邊進行限定則 使用 <號,代表表達式在左邊。

除了基本非捕獲組(?:)以外的四個非捕獲組的匹配部分是不計算在整個表達式匹配結果中的,相當於需要匹配兩次
不僅要帶上該非捕獲組表達式能夠匹配成功
還要去掉該非捕獲組的表達式也能成功匹配!

否則是無法匹配的。
regExp=“abc(?=aa)abc” 匹配字符串 abcaaabc
上述匹配時失敗的。 (?=)就像一個關卡一樣,通過了,關卡就會消失,接着之前的地方繼續匹配。

上述匹配過程實際上是
1.abc 匹配到 abc ,
2.匹配(?=aa) 通過關卡。
3.這是正則表達式變成了 “abcabc”,匹配字符串 abcaaabc ,結果是失敗了。

以之前郵箱例子

  1. [a-z|A-Z|0-9|_]+@(163|126).(com|cn)
  2. [a-z|A-Z|0-9|_]+@(<mailName>163|126).(?<suff>com|cn)
  3. [a-z|A-Z|0-9|_]+@(?:163|126).(?:com|cn)
  4. [a-z|A-Z|0-9|_]+@(?:163|126).(?=com|cn)
    匹配字符串爲 [email protected]
    三個正則表達式都可以成功匹配
    第一個匹配得到三個組 組0:[email protected] 組1:163 組2:126
    第二個匹配和第一個匹配相同,三個組,但是組1 可以使用group(mailName)進行獲取,組2可以用group(suff)進行獲取結果。
    第三個匹配得到一個組 組0:[email protected]
    第四個匹配得到一個組 組0:xjwjy002@163.

在寫正則表達式時
軟件 RegexBuddy 非常的好用。上方紅框寫正則表達式,下方寫匹配的字符串。
debug可以查看正則表達式匹配的過程,每一步。還可以查看每個捕獲組的結果和位置。使用非常簡便。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章