go面向?qū)ο蠓绞讲僮鱆SON庫實(shí)現(xiàn)四則運(yùn)算
前言
在之前實(shí)現(xiàn)的 JSON
解析器中當(dāng)時(shí)只實(shí)現(xiàn)了將一個(gè) JSON 字符串轉(zhuǎn)換為一個(gè) JSONObject
,并沒有將其映射為一個(gè)具體的 struct
;如果想要獲取值就需要先做斷言將其轉(zhuǎn)換為 map
或者是切片再來獲,會(huì)比較麻煩。
decode, err := xjson.Decode(`{"glossary":{"title":"example glossary","age":1}}`) assert.Nil(t, err) glossary := v["glossary"].(map[string]interface{}) assert.Equal(t, glossary["title"], "example glossary") assert.Equal(t, glossary["age"], 1)
但其實(shí)轉(zhuǎn)念一想,部分場景我們甚至我們只需要拿到 JSON
中的某個(gè)字段的值,這樣還需要先聲明一個(gè) struct
會(huì)略顯麻煩。
于是我也打算增加類似的功能,使用方式如下:
最后還加上了一個(gè)四則運(yùn)算的功能。
面向?qū)ο蟮姆绞讲僮?JSON
因?yàn)楣δ茴愃?,所以我參考?nbsp;tidwall
的 API
但去掉一些我覺得暫時(shí)用不上的特性,并調(diào)整了一點(diǎn)語法。
當(dāng)前這個(gè)版本只能通過確定的 key
加上 .
點(diǎn)符號訪問數(shù)據(jù),如果是數(shù)組則用 [index]
的方式訪問下標(biāo)。[]
符號訪問數(shù)組我覺得要更符合直覺一些。
以下是一個(gè)包含多重嵌套 JSON
的訪問示例:
str := ` { "name": "bob", "age": 20, "skill": { "lang": [ { "go": { "feature": [ "goroutine", "channel", "simple", true ] } } ] } }` name := xjson.Get(str, "name") assert.Equal(t, name.String(), "bob") age := xjson.Get(str, "age") assert.Equal(t, age.Int(), 20) assert.Equal(t, xjson.Get(str,"skill.lang[0].go.feature[0]").String(), "goroutine") assert.Equal(t, xjson.Get(str,"skill.lang[0].go.feature[1]").String(), "channel") assert.Equal(t, xjson.Get(str,"skill.lang[0].go.feature[2]").String(), "simple") assert.Equal(t, xjson.Get(str,"skill.lang[0].go.feature[3]").Bool(), true)
這樣的語法使用個(gè)人覺得還是滿符合直覺的,相信對使用者來說也比較簡單。
返回值參考了 tidwall
使用了一個(gè) Result
對象,它提供了多種方法可以方便的獲取各種類型的數(shù)據(jù)
func (r Result) String() string func (r Result) Bool() bool func (r Result) Int() int func (r Result) Float() float64 func (r Result) Map() map[string]interface{} func (r Result) Array() *[]interface{} func (r Result) Exists() bool
比如使用 Map()/Array()
這兩個(gè)函數(shù)可以將 JSON
數(shù)據(jù)映射到 map
和切片中,當(dāng)然前提是傳入的語法返回的是一個(gè)合法 JSONObject
或數(shù)組。
實(shí)現(xiàn)原理
在實(shí)現(xiàn)之前需要先定義一個(gè)基本語法,主要支持以下四種用法:
- 單個(gè)
key
的查詢:Get(json,"name")
- 嵌套查詢:
Get(json,"obj1.obj2.obj3.name")
- 數(shù)組查詢:
Get(json,"obj.array[0]")
- 數(shù)組嵌套查詢:
Get(json,"obj.array[0].obj2.obj3[1].name")
語法很簡單,符合我們?nèi)粘=佑|到語法規(guī)則,這樣便可以訪問到 JSON
數(shù)據(jù)中的任何一個(gè)值。
其實(shí)實(shí)現(xiàn)過程也不復(fù)雜,我們已經(jīng)在上一文中實(shí)現(xiàn)將 JSON
字符串轉(zhuǎn)換為一個(gè) JSONObject
了。
這次只是額外再解析剛才定義的語法為 token
,然后解析該 token
的同時(shí)再從生成好的 JSONObject
中獲取數(shù)據(jù)。
最后在解析完 token
時(shí)拿到的 JSONObject
數(shù)據(jù)返回即可。
我們以這段查詢代碼為例:
首先第一步是對查詢語法做詞法分析,最終得到下圖的 token
。
在詞法分析過程中也可以做簡單的語法校驗(yàn);比如如果包含數(shù)組查詢,并不是以 ]
符號結(jié)尾時(shí)就拋出語法錯(cuò)誤。
接著我們遍歷語法的 token。如下圖所示:
每當(dāng)遍歷到 token
類型為 Key
時(shí)便從當(dāng)前的 JSONObject 對象中獲取數(shù)據(jù),并用獲取到的值替覆蓋為當(dāng)前的 JSONObject。
其中每當(dāng)遇到 .
[
]
這樣的 token 時(shí)便消耗掉,直到我們將 token 遍歷完畢,這時(shí)將當(dāng)前 JSONObject
返回即可。
在遍歷過程中當(dāng)遇到非法格式時(shí),比如 obj_list[1.]
便會(huì)返回一個(gè)空的 JSONObject
。
語法校驗(yàn)這點(diǎn)其實(shí)也很容易辦到,因?yàn)楦鶕?jù)我們的語法規(guī)則,Array
中的 index
后一定緊接的是一個(gè) EndArray
,只要不是一個(gè) EndArray
便能知道語法不合法了。
有興趣的可以看下解析過程的源碼:
https://github.com/crossoverJie/xjson/blob/cfbca51cc9bc0c77e6cb9c9ad3f964b2054b3826/json.go#L46
對 JSON 做四則運(yùn)算
str := `{"name":"bob", "age":10,"magic":10.1, "score":{"math":[1,2]}}` result := GetWithArithmetic(str, "(age+age)*age+magic") assert.Equal(t, result.Float(), 210.1) result = GetWithArithmetic(str, "(age+age)*age") assert.Equal(t, result.Int(), 200) result = GetWithArithmetic(str, "(age+age) * age + score.math[0]") assert.Equal(t, result.Int(), 201) result = GetWithArithmetic(str, "(age+age) * age - score.math[0]") assert.Equal(t, result.Int(), 199) result = GetWithArithmetic(str, "score.math[1] / score.math[0]") assert.Equal(t, result.Int(), 2)
最后我還擴(kuò)展了一下語法,可以支持對 JSON
數(shù)據(jù)中的整形(int、float)
做四則運(yùn)算,雖然這是一個(gè)小眾需求,但做完我覺得還挺有意思的,目前在市面上我還沒發(fā)現(xiàn)有類似功能的庫,可能和小眾需求有關(guān)??。
其中核心的四則運(yùn)算邏輯是由之前寫的腳本解釋器提供的:
https://github.com/crossoverJie/gscript
單獨(dú)提供了一個(gè)函數(shù),傳入一個(gè)四則運(yùn)算表達(dá)式返回計(jì)算結(jié)果。
由于上一版本還不支持 float,所以這次專門適配了一下。
限于篇幅,更多關(guān)于這個(gè)四則運(yùn)算的實(shí)現(xiàn)邏輯會(huì)在后面繼續(xù)分享。
總結(jié)
至此算是我第一次利用編譯原理的知識解決了一點(diǎn)特定領(lǐng)域問題,在大學(xué)以及工作這些年一直覺得編譯原理比較高深,所以內(nèi)心一直是抗拒的,但經(jīng)過這段時(shí)間的學(xué)習(xí)和實(shí)踐慢慢的也掌握到了一點(diǎn)門道。
不過目前也只是冰山一角,后面的編譯原理后端更是要涉及到計(jì)算機(jī)底層知識,所以依然任重而道遠(yuǎn)。
已上都是題外話,針對于這個(gè)庫我也會(huì)長期維護(hù);為了能達(dá)到生產(chǎn)的使用要求,盡量提高了單測覆蓋率,目前是98%。
也歡迎大家使用,提 bug??。
后面會(huì)繼續(xù)優(yōu)化,比如支持轉(zhuǎn)義字符、提高性能等
https://github.com/crossoverJie/xjson
以上就是go面向?qū)ο蠓绞讲僮鱆SON庫實(shí)現(xiàn)四則運(yùn)算的詳細(xì)內(nèi)容,更多關(guān)于go面向?qū)ο驤SON庫四則運(yùn)算的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang中匿名組合實(shí)現(xiàn)偽繼承的方法
這篇文章主要介紹了Golang中匿名組合實(shí)現(xiàn)偽繼承的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08Go中g(shù)routine通信與context控制實(shí)例詳解
隨著context包的引入,標(biāo)準(zhǔn)庫中很多接口因此加上了context參數(shù),下面這篇文章主要給大家介紹了關(guān)于Go中g(shù)routine通信與context控制的相關(guān)資料,需要的朋友可以參考下2022-02-02Golang 如何判斷數(shù)組某個(gè)元素是否存在 (isset)
這篇文章主要介紹了Golang 如何判斷數(shù)組某個(gè)元素是否存在 (isset),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04Golang實(shí)現(xiàn)帶優(yōu)先級的select
這篇文章主要為大家詳細(xì)介紹了如何在Golang中實(shí)現(xiàn)帶優(yōu)先級的select,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下2023-04-04Golang?中的json.Marshal問題總結(jié)(推薦)
這篇文章主要介紹了Golang中的json.Marshal問題總結(jié),本文通過一個(gè)例子給大家詳細(xì)講解,本次提出的問題中,我們不難注意到其中的time.Time是一個(gè)匿名(Anonymous)字段,而這個(gè)就是答案的由來,需要的朋友可以參考下2022-06-06