天天看點

大家都懂的 JSON 解析器原理(一)簡介 & 低配版入門目标低配版,一個函數搞定解析過程

沒學過編譯原理,做一個 JSON 解析器難嗎?——難!是不是就不能“迎難而上”呢?——不是!越是難的越是一個挑戰!——筆者這裡嘗試通過通俗易懂的行文為大家介紹一下 JSON 解析器,——那一串串長長的 JSON 文本到底是如何被解析成為 Java 裡面“可以了解的”對象的。前面的鋪墊可能比較長,但請盡量不要跳過,因為那都是基礎,尤其對于我們非科班來說,應要惡補。當然,為照顧大家的了解程度(包括我自己,我也會以後回看自己的代碼,以此反複了解、反複消化),我會把代碼寫多點注釋,把代碼可讀性提高那麼一點點,因為網上很多寫解析器的大神都是從 C 語言高手過來的,明顯帶有過程式的風格。是以我會重構這些代碼,使得代碼更 OO 一些,這樣看起來也會緊湊一些,可讀性高一些。

輸入 JSON 字元串,對象或數組互相嵌套着,如:

可以 {} 包含 [],也可以 [] 包含 {},總之互相嵌套,最後到 Java 傳回 Map 或 List 就可以了——當然 Java 裡的 Map or List 也是可以互相嵌套着的。

要求知識

讀者應當對 JSON 結構是怎麼一回事要了然于胸。

好吧,正式開始!

可以說這是一個超簡單 JSON 解析器,它是一個函數。一個函數就能搞定嗎?——如果隻考慮 JSON 簡單情況(此種情況固然是不能放在生産環境的)是可以的,而且代碼行數少,正好适合我們初學了解。下面是該函數的完整代碼。

該函數輸入一個 String 類型的參數,傳回一個 Object 類型結果。Object 類型隻有兩種真實類型,要麼是 Map,要麼是 List,分别對應最外層的 JSON 類型。

怎麼了解這個函數呢?首先方法輸入的是字元串,我們把字元串“打散”,也就是 char[] cs=jsonstring.toCharArray(); 這句把字元串轉換為字元數組。變成數組的目的是要周遊也就是把數組中的每一個字元都讀出來。讀了一個字元,并進行解析。解析完畢了,我們叫“消耗”。把這個字元消耗了,接着就讀取下一個字元重複上述過程。如此 JSON 裡面每一個字元都會被讀取、解析、消耗。

将字元串變為字元數組,實際上很多 JSON 解析庫都會那麼做,是為第一步之工序。得到 char[] 然後周遊它,其中的周遊過程就是具體的一個解析 JSON 的過程。

至于周遊 for 裡面具體怎麼個解析法?此固然是要重點探讨的話題。

大家都懂的 JSON 解析器原理(一)簡介 & 低配版入門目标低配版,一個函數搞定解析過程

函數中一口氣聲明了 4個 Stack:

我們知道 JSON 乃樹狀結構。樹樁結構的特點是父親節點擁有子節點,子節點的上一級是父節點,形成了這種關系。變量 maps 用于記住周遊字元的時候,字元所在在父級對象有哪些。父級節點 maps 是一個集合的概念,因為可能不止一個父級節點,而且可能有 n 個,那個 n 就代表樹的層數。且 maps 裡面的順序不能打亂(不過可以放心,在 Stack 裡面并不允許“打亂”順序)。

同理,遇到數組的方式也可以這樣去了解,儲存在 lists 變量中。

當然,必須先有父級節點,才會有子節點,否則子節點就沒有容身的“場所”。故而第一個欲消耗的字元永遠要麼是 {,永遠要麼是 [,才會 new 第一個 map 對象或者 list 對象。第一個 { 或 [ 可以稱為“根節點”或“頂級節點”。

回到函數中,分别是如下進行字元的消耗的:

我們忽略 switch 中不相關的部分,用省略号表示。可見,一遇到 { 字元,就表示要建立 map 對象,而且要将 map 進棧到 maps 中;一遇到 [ 字元,就表示要建立 list 對象,而且要将 list 進棧到 lists 中。進棧的意思就是在棧頂部添加新的元素。

光有進棧不夠,應該還有“退棧”的那麼一個操作。不過這裡權且埋下伏筆,回過頭來我們再看退棧。

上述過程就是比對 JSON 字元串中的兩種括号:尖括号和方括号,如 [ { }, [ ], [ ] ] 或 { [ ], [ ] } 等為正确格式,[ { ] } 或 { [ } } 為不合法格式。我們把 JSON 字元串抽象成這個格式去了解,有助于我們了解怎麼比對成對出現的結構。

例如考慮下面的括号序列。

當消耗了第 1 個括号 [ 之後,期待與它比對的第 8 個括号 ] 出現,然而等來的卻是第 2 括号 {,此時第 1 個括号隻能靠邊站,不過沒關系,因為我們消耗過程中已經把它儲存起來,進行過“入棧”了;好,接着第 2 個括号要比對的是 },但是很遺憾,第 3 個括号并不是期待的 },而是 [。不過同樣沒關系,因為第 2 個括号已經儲存起來,先記着;現在輪到第 3 個括号,就要看看第 4 個括号怎麼樣?第 4 個括号正好是 ],完成比對!期待得到了滿足!但是不要忘記剛才第 3 個括号已經入過棧,是以現在滿足之後,目前就不是原來的位置——需要執行什麼操作?就是要“退棧”的操作。

執行完退棧之後,目前位置是第 5 個括号,而目前所期待的括号理應是第 2 個括号的期待,這個期待最為迫切。不過很遺憾,第 2 個括号還必須“忍一忍”,因為第 5 個括号是 [,說明又有新的期待進來,迫切性更高,第 2 個括号必須“讓位于”第 5 個括号。——這裡我們假設是故意弄錯,第 6 個括号進入的是一個右尖括号 },明顯這樣不能構成結對,是非法字元,于是應中止周遊,立刻報錯。回到正确的例子上,我們看到第 6 個括号是合法的括号,完成比對,接下來期待第 2 個括号的比對,或者是 [ or { 新開一級的比對——這都是可以、合法的。

由此可見,這過程與棧的結構相吻合。“一進一退”是必須完成的結對,否則是不合法的過程。

隻有掌握了這個比對過程,我們才能進入下一步的 JSON 解析。今天先說到這兒,裡面的内容有不少地方是需要好好消化的。如果沒有幫到讀者了解,或者有進一步的問題,都可以跟在下溝通。歡迎交流!