與XML相比,JSON本身的結構非常簡單,只有幾種數據類型。以Java為例,對應的數據結構為:
“字符串”:字符串“:Java
數字:長型或雙型;Java的;
真/假:布爾型;Java的;
Null:空:Java
【數組】:Java列表
{“key“:“value“}:Java的映射
解析JSON類似於解析XML,最終它被解析成內存中的壹個對象。為了提高效率,使用stream幾乎是唯壹的選擇,即解析器只從頭掃描JSON字符串,並完全解析相應的數據結構。
從本質上講,解析器是壹個狀態機,只要能夠按照JSON定義的格式正確實現狀態轉換即可(參考http://www.json.org)。但是為了簡化代碼,我們不需要完全實現從角色到角色的狀態轉換。
解析器的輸入應該是壹個字符流,所以第壹步是獲得壹個讀取器,以便可以連續讀入下壹個字符。
在解析過程中,我們經常根據下壹個字符來決定狀態跳轉。這時候就涉及到回退的問題了,就是有時候我們不能用next()拿下壹個字符,而是用peek()拿下壹個字符,但是字符流的指針不動。因此,閱讀器接口不能滿足這壹要求,應進壹步封裝CharReader,可實現:
Char next():讀取下壹個字符並移動讀取器指針;
Char peek():讀取下壹個字符而不移動讀取器指針;
string next(int size):讀取指定的n個字符並移動指針;
Boolean hasMore():確定流是否結束。
JSON解析比其他文本解析簡單,因為任何JSON數據類型都只能通過下壹個字符來確定。仔細總結後可以發現,如果peek()返回的字符是某個字符,則可以預期讀取的數據類型為:
{:需要JSON對象;;
*需要壹個值;JSON對象的;
,:期望JSON對象的下壹組鍵值,或JSON數組的下壹個元素;
【:應為JSON數組;;
t:期待壹個真實;;
f:期待壹個假的;;
n:應為空值;;
“:應為字符串;;
0~9:需要壹個數字。
然而,對於單個字符來說,有太多的狀態需要匹配,因此有必要進壹步將字符流更改為令牌,這可以總結如下:
end _ document:JSON文檔的結尾;
BEGIN_OBJECT:啟動JSON對象;;
END_OBJECT:結束JSON對象;;
BEGIN_ARRAY:啟動JSON數組;;
END_ARRAY:結束JSON數組;;
SEP_COLON:讀壹個冒號;
SEP_COMMA:讀壹個逗號;
字符串:字符串;
布爾型:真或假;;
數字:壹個數字;
空值:空值。
然後,CharReader被進壹步封裝為TokenReader,它提供了以下接口:
Token readNextToken():讀取下壹個令牌;
Boolean readBoolean():讀取壹個布爾值;
Number readNumber():讀取壹個數字;
String readString():讀取壹個字符串;
Void readNull():讀取空值。
因為JSON對象和數組可以嵌套,所以在讀取時需要使用堆棧來存儲對象和數組。每當我們讀取壹個BEGIN_OBJECT時,我們都會創建壹個Map並對其進行堆棧;每當讀取BEGIN_ARRAY時,都會創建壹個列表並將其堆棧;每當讀取END_OBJECT和END_ARRAY時,就會彈出棧頂元素,根據新的棧頂元素判斷是否推棧。此外,對象的鍵也必須被推送到堆棧中,在讀取後面的值後,key-Value被推送到堆棧頂部的映射中。
如果讀取END_DOCUMENT時堆棧中只剩下壹個元素,則讀取是正確的,元素被返回,讀取結束。如果堆棧中還有多個元素,則JSON文檔格式不正確。
最後,JsonReader的核心解析代碼parse()負責不斷從TokenReader讀取令牌,根據當前狀態進行操作,然後設置下壹個令牌的預期狀態。如果它與預期狀態不匹配,則JSON格式無效。初始狀態設置為status _ expect _ single _ value | status _ expect _ begin _ object | status _ expect _ begin _ array,即預期讀取單個值{或【。循環的退出點是讀取END_DOCUMENT時。