作者:omgleoo
鏈接:https://gyl-coder.top/JSONParser/

JSON

JSON(JavaScript Object Notation, JS 對象簡譜) 是一種輕量級的數據交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。採用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成爲理想的數據交換語言。

JSON與JS的區別以及和XML的區別具體請參考百度百科:

https://baike.baidu.com/item/JSON/2462549?fr=aladdin

JSON有兩種結構:

第一種:對象

“名稱/值”對的集合不同的語言中,它被理解爲對象(object),紀錄(record),結構(struct),字典(dictionary),哈希表(hash table),有鍵列表(keyed list),或者關聯數組 (associative array)。

對象是一個無序的“‘名稱/值’對”集合。一個對象以“{”(左括號)開始,“}”(右括號)結束。每個“名稱”後跟一個“:”(冒號);“‘名稱/值’ 對”之間使用“,”(逗號)分隔。

{"姓名": "張三", "年齡": "18"}


第二種:數組

值的有序列表(An ordered list of values)。在大部分語言中,它被理解爲數組(array)。

數組是值(value)的有序集合。一個數組以“[”(左中括號)開始,“]”(右中括號)結束。值之間使用“,”(逗號)分隔。

值(value)可以是雙引號括起來的字符串(string)、數值(number)、true、false、 null、對象(object)或者數組(array)。這些結構可以嵌套。

[
{
"姓名": "張三",
"年齡":"18"
},
{
"姓名": "里斯",
"年齡":"19"
}
]

通過上面的瞭解可以看出,JSON存在以下幾種數據類型(以Java做類比):

實現一個JSON解析器,有那麼難嗎?

解析JSON

JSON解析器的基本原理

輸入一串JSON字符串,輸出一個JSON對象。

步驟

JSON解析的過程主要分以下兩步:

第一步:對於輸入的一串JSON字符串我們需要將其解析成一組token流。

例如 JSON字符串{“姓名”: “張三”, “年齡”: “18”} 我們需要將它解析成

{、 姓名、 :、 張三、 ,、 年齡、 :、 18、 }


這樣一組token流

第二步:根據得到的token流將其解析成對應的JSON對象(JSONObject)或者JSON數組(JSONArray)

下面我們來詳細分析下這兩個步驟:

獲取token流

根據JSON格式的定義,token可以分爲以下幾種類型

實現一個JSON解析器,有那麼難嗎?

根據以上的JSON類型,我們可以將其封裝成enum類型的TokenType

package com.json.demo.tokenizer;
/**
BEGIN_OBJECT({)
END_OBJECT(})
BEGIN_ARRAY([)
END_ARRAY(])
NULL(null)
NUMBER(數字)
STRING(字符串)
BOOLEAN(true/false)
SEP_COLON(:)
SEP_COMMA(,)
END_DOCUMENT(表示JSON文檔結束)
*/
public enum TokenType {
BEGIN_OBJECT(1),
END_OBJECT(2),
BEGIN_ARRAY(4),
END_ARRAY(8),
NULL(16),
NUMBER(32),
STRING(64),
BOOLEAN(128),
SEP_COLON(256),
SEP_COMMA(512),
END_DOCUMENT(1024);
private int code; // 每個類型的編號
TokenType(int code) {
this.code = code;
}
public int getTokenCode() {
return code;
}
}

在TokenType中我們爲每一種類型都賦一個數字,目的是在Parser做一些優化操作(通過位運算來判斷是否是期望出現的類型)

在進行第一步之前JSON串對計算機來說只是一串沒有意義的字符而已。第一步的作用就是把這些無意義的字符串變成一個一個的token,上面我們已經爲每一種token定義了相應的類型和值。所以計算機能夠區分不同的token,並能以token爲單位解讀JSON數據。

下面我們封裝一個token類來存儲每一個token對應的值

實現一個JSON解析器,有那麼難嗎?

在解析的過程中我們通過字符流來不斷的讀取字符,並且需要經常根據相應的字符來判斷狀態的跳轉。所以我們需要自己封裝一個ReaderChar類,以便我們更好的操作字符流。

實現一個JSON解析器,有那麼難嗎?

實現一個JSON解析器,有那麼難嗎?

另外我們還需要一個TokenList來存儲解析出來的token流

實現一個JSON解析器,有那麼難嗎?

JSON解析比其他文本解析要簡單的地方在於,我們只需要根據下一個字符就可知道接下來它所期望讀取的到的內容是什麼樣的。如果滿足期望了,則返回 Token,否則返回錯誤。

爲了方便程序出錯時更好的debug,程序中自定義了兩個exception類來處理錯誤信息。(具體實現參考exception包:https://github.com/gyl-coder/JSON-Parser/tree/master/src/main/java/com/json/demo/exception)

下面就是第一步中的重頭戲(核心代碼):

實現一個JSON解析器,有那麼難嗎?

實現一個JSON解析器,有那麼難嗎?

在start方法中,我們將每個處理方法都封裝成了單獨的函數。主要思想就是通過一個死循環不停的讀取字符,然後再根據字符的期待值,執行不同的處理函數。

下面我們詳解分析幾個處理函數:

實現一個JSON解析器,有那麼難嗎?

該方法也是通過一個死循環來讀取字符,首先判斷的是JSON中的轉義字符。

JSON中允許出現的有以下幾種

實現一個JSON解析器,有那麼難嗎?

具體的處理方法封裝在了isEscape()方法中,處理Unicode 編碼時要特別注意一下u的後面會出現四位十六進制數。當讀取到一個雙引號或者讀取到了非法字符(’\r’或’、’\n’)循環退出。

判斷數字的時候也要特別小心,注意負數,frac,exp等等情況。

通過上面的解析,我們可以得到一組token,接下來我們需要以這組token作爲輸入,解析出相應的JSON對象

解析出JSON對象

解析之前我們需要定義出JSON對象(JSONObject)和JSON數組(JSONArray)的實體類。

實現一個JSON解析器,有那麼難嗎?

實現一個JSON解析器,有那麼難嗎?

之後我們就可以寫解析類了,由於代碼較長,這裏就不展示了。有興趣的可以去GitHub上下載。實現邏輯比較簡單,也易於理解。

解析類中的parse方法首先根據第一個token的類型選擇調用parseJsonObject()或者parseJsonArray(),進而返回JSON對象或者JSON數組。上面的解析方法中利用位運算來判斷字符的期待值既提高了程序的執行效率也有助於提高代碼的ke’du’xi

完成之後我們可以寫一個測試類來驗證下我們的解析器的運行情況。我們可以自己定義一組JSON串也可以通過HttpUtil工具類從網上獲取。最後通過FormatUtil類來規範我們輸出。

具體效果如下圖所示:

實現一個JSON解析器,有那麼難嗎?


實現一個JSON解析器,有那麼難嗎?


代碼地址

https://github.com/gyl-coder/JSON-Parser.git

擴展閱讀

Java轉JSON串的幾種方式

使用JSONObject生成和解析json

JSON是什麼?它能帶來什麼?它和XML比較?

使用JDOM方式解析XML

SpringBoot 快速整合Mybatis(去XML化+註解進階)

SpringBoot 配置文件詳解(告別XML)

相关文章