go語言用八百行代碼實現(xiàn)一個JSON解析器_第1頁
go語言用八百行代碼實現(xiàn)一個JSON解析器_第2頁
go語言用八百行代碼實現(xiàn)一個JSON解析器_第3頁
go語言用八百行代碼實現(xiàn)一個JSON解析器_第4頁
go語言用八百行代碼實現(xiàn)一個JSON解析器_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

第go語言用八百行代碼實現(xiàn)一個JSON解析器目錄前言實現(xiàn)原理詞法分析提前檢查生成JSONObject樹總結(jié)

前言

之前在寫gscript時我就在想有沒有利用編譯原理實現(xiàn)一個更實際工具?畢竟真寫一個語言的難度不低,并且也很難真的應(yīng)用起來。

一次無意間看到有人提起JSON解析器,這類工具充斥著我們的日常開發(fā),運用非常廣泛。

以前我也有思考過它是如何實現(xiàn)的,過程中一旦和編譯原理扯上關(guān)系就不由自主的勸退了;但經(jīng)過這段時間的實踐我發(fā)現(xiàn)實現(xiàn)一個JSON解析器似乎也不困難,只是運用到了編譯原理前端的部分知識就完全足夠了。

得益于JSON的輕量級,同時語法也很簡單,所以核心代碼大概只用了800行便實現(xiàn)了一個語法完善的JSON解析器。

首先還是來看看效果:

import"/crossoverJie/xjson"

funcTestJson(t*testing.T){

str:=`{

"glossary":{

"title":"exampleglossary",

"age":1,

"long":99.99,

"GlossDiv":{

"title":"S",

"GlossList":{

"GlossEntry":{

"ID":"SGML",

"SortAs":"SGML",

"GlossTerm":"StandardGeneralizedMarkupLanguage",

"Acronym":"SGML",

"Abbrev":"ISO8879:1986",

"GlossDef":{

"para":"Ameta-markuplanguage,usedtocreatemarkuplanguagessuchasDocBook.",

"GlossSeeAlso":["GML","XML",true,null]

"GlossSee":"markup"

decode,err:=xjson.Decode(str)

assert.Nil(t,err)

fmt.Println(decode)

v:=decode.(map[string]interface{})

glossary:=v["glossary"].(map[string]interface{})

assert.Equal(t,glossary["title"],"exampleglossary")

assert.Equal(t,glossary["age"],1)

assert.Equal(t,glossary["long"],99.99)

glossDiv:=glossary["GlossDiv"].(map[string]interface{})

assert.Equal(t,glossDiv["title"],"S")

glossList:=glossDiv["GlossList"].(map[string]interface{})

glossEntry:=glossList["GlossEntry"].(map[string]interface{})

assert.Equal(t,glossEntry["ID"],"SGML")

assert.Equal(t,glossEntry["SortAs"],"SGML")

assert.Equal(t,glossEntry["GlossTerm"],"StandardGeneralizedMarkupLanguage")

assert.Equal(t,glossEntry["Acronym"],"SGML")

assert.Equal(t,glossEntry["Abbrev"],"ISO8879:1986")

glossDef:=glossEntry["GlossDef"].(map[string]interface{})

assert.Equal(t,glossDef["para"],"Ameta-markuplanguage,usedtocreatemarkuplanguagessuchasDocBook.")

glossSeeAlso:=glossDef["GlossSeeAlso"].(*[]interface{})

assert.Equal(t,(*glossSeeAlso)[0],"GML")

assert.Equal(t,(*glossSeeAlso)[1],"XML")

assert.Equal(t,(*glossSeeAlso)[2],true)

assert.Equal(t,(*glossSeeAlso)[3],"")

assert.Equal(t,glossEntry["GlossSee"],"markup")

從這個用例中可以看到支持字符串、布爾值、浮點、整形、數(shù)組以及各種嵌套關(guān)系。

實現(xiàn)原理

這里簡要說明一下實現(xiàn)原理,本質(zhì)上就是兩步:

詞法解析:根據(jù)原始輸入的JSON字符串解析出token,也就是類似于{objage1[]這樣的標(biāo)識符,只是要給這類標(biāo)識符分類。根據(jù)生成的一組token集合,以流的方式進行讀取,最終可以生成圖中的樹狀結(jié)構(gòu),也就是一個JSONObject。

下面來重點看看這兩個步驟具體做了哪些事情。

詞法分析

BeginObject{

String"name"

SepColon:

String"cj"

SepComma,

String"object"

SepColon:

BeginObject{

String"age"

SepColon:

Number10

SepComma,

String"sex"

SepColon:

String"girl"

EndObject}

SepComma,

String"list"

SepColon:

BeginArray[

其實詞法解析就是構(gòu)建一個有限自動機的過程(DFA),目的是可以生成這樣的集合(token),只是我們需要將這些token進行分類以便后續(xù)做語法分析的時候進行處理。

比如{這樣的左花括號就是一個BeginObject代表一個對象聲明的開始,而}則是EndObject代表一個對象的結(jié)束。

其中name這樣的就被認(rèn)為是String字符串,以此類推[代表BeginArray

這里我一共定義以下幾種token類型:

typeTokenstring

const(

InitToken="Init"

BeginObject="BeginObject"

EndObject="EndObject"

BeginArray="BeginArray"

EndArray="EndArray"

Null="Null"

Null1="Null1"

Null2="Null2"

Null3="Null3"

Number="Number"

Float="Float"

BeginString="BeginString"

EndString="EndString"

String="String"

True="True"

True1="True1"

True2="True2"

True3="True3"

False="False"

False1="False1"

False2="False2"

False3="False3"

False4="False4"

//SepColon:

SepColon="SepColon"

//SepComma,

SepComma="SepComma"

EndJson="EndJson"

其中可以看到true/false/null會有多個類型,這點先忽略,后續(xù)會解釋。

以這段JSON為例:{age:1},它的狀態(tài)扭轉(zhuǎn)如下圖:

總的來說就是依次遍歷字符串,然后更新一個全局狀態(tài),根據(jù)該狀態(tài)的值進行不同的操作。

部分代碼如下:

感興趣的朋友可以跑跑單例debug一下就很容易理解:

以這段JSON為例:

funcTestInitStatus(t*testing.T){

str:=`{"name":"cj","age":10}`

tokenize,err:=Tokenize(str)

assert.Nil(t,err)

for_,tokenType:=rangetokenize{

fmt.Printf("%s%s\n",tokenType.T,tokenType.Value)

最終生成的token集合如下:

BeginObject{

String"name"

SepColon:

String"cj"

SepComma,

String"age"

SepColon:

Number10

EndObject}

提前檢查

由于JSON的語法簡單,一些規(guī)則甚至在詞法規(guī)則中就能校驗。

舉個例子:JSON中允許null值,當(dāng)我們字符串中存在nunul這類不匹配null的值時,就可以提前拋出異常。

比如當(dāng)檢測到第一個字符串為n時,那后續(xù)的必須為u-l-l不然就拋出異常。

浮點數(shù)同理,當(dāng)一個數(shù)值中存在多個.點時,依然需要拋出異常。

這也是前文提到true/false/null這些類型需要有多個中間狀態(tài)的原因。

生成JSONObject樹

在討論生成JSONObject樹之前我們先來看這么一個問題,給定一個括號集合,判斷是否合法。

[()]這樣是合法的。[())而這樣是不合法的。

如何實現(xiàn)呢?其實也很簡單,只需要利用棧就能完成,如下圖所示:

利用棧的特性,依次遍歷數(shù)據(jù),遇到是左邊的符號就入棧,當(dāng)遇到是右符號時就與棧頂數(shù)據(jù)匹配,能匹配上就出棧。

當(dāng)匹配不上時則說明格式錯誤,數(shù)據(jù)遍歷完畢后如果棧為空時說明數(shù)據(jù)合法。

其實仔細(xì)觀察JSON的語法也是類似的:

{

"name":"cj",

"object":{

"age":10,

"sex":"girl"

"list":[

"1":"a"

"2":"b"

BeginObject:{與EndObject:}一定是成對出現(xiàn)的,中間如論怎么嵌套也是成對的。而對于age:10這樣的數(shù)據(jù),:冒號后也得有數(shù)據(jù)進行匹配,不然就是非法格式。

所以基于剛才的括號匹配原理,我們也能用類似的方法來解析token集合。

我們也需要創(chuàng)建一個棧,當(dāng)遇到BeginObject時就入棧一個Map,當(dāng)遇到一個String鍵時也將該值入棧。

當(dāng)遇到value時,就將出棧一個key,同時將數(shù)據(jù)寫入當(dāng)前棧頂?shù)膍ap中。

當(dāng)然在遍歷token的過程中也需要一個全局狀態(tài),所以這里也是一個有限狀態(tài)機。

舉個例子:當(dāng)我們遍歷到Token類型為String,值為name時,預(yù)期下一個token應(yīng)當(dāng)是:冒號;

所以我們得將當(dāng)前的status記錄為StatusColon,一旦后續(xù)解析到token為SepColon時,就需要判斷當(dāng)前的status是否為StatusColon,如果不是則說明語法錯誤,就可以拋出異常。

同時值得注意的是這里的status其實是一個集合,因為下一個狀態(tài)可能是多種情況。

{e:[1,[2,3],{d:{f:f}}]}比如當(dāng)我們解析到一個SepColon冒號時,后續(xù)的狀態(tài)可能是value或BeginObject{或BeginArray[

因此這里就得把這三種情況都考慮到,其他的以此類推。

具體解析過程可以參考源碼:

/crossoverJie/xjson/blob/main/parse.go

雖然是借助一個棧結(jié)構(gòu)就能將JSON解析完畢,不知道大家發(fā)現(xiàn)一個問題沒有:這樣非常容易遺漏規(guī)則,比如剛才提到的一個冒號后面就有三種情況,而一個BeginArray后甚至有四種情況

StatusArrayValue,StatusBeginArray,StatusBeginObject,StatusEndArray

這樣的代碼讀起來也不是很直觀,同時容易遺漏語法,只能出現(xiàn)問題再進行修復(fù)。

既然提到了問題那自然也有相應(yīng)的解決方案,其實就是語法分析中常見的遞歸下降算法。

我們只需要根據(jù)JSON的文法定義,遞歸的寫出算法即可,這樣代碼閱讀起來非常清晰,同時也不會遺漏規(guī)則。

完整的JSON語法查看這里:

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論