javascript
用 Go 解析复杂 JSON 的思路
Go語言自帶的encode/json包提供了對JSON數(shù)據(jù)格式的編碼和解碼能力。之前的文章《如何控制Go編碼JSON數(shù)據(jù)格式的行為》已經(jīng)介紹了編碼JSON時常見的幾個問題,如何使用encode/json來解決。解碼JSON時encode/json包使用UnMarshall或者Decode方法根據(jù)開發(fā)者提供的存放解碼后數(shù)據(jù)的變量的類型聲明來解析JSON并把解碼后的數(shù)據(jù)填充到Go變量里。所以解析JSON的關(guān)鍵其實是如何聲明存放解析后數(shù)據(jù)的變量的類型。
由于JSON格式的自由組合的特點,對新手來說通過觀察JSON數(shù)據(jù)的內(nèi)容,聲明解析后數(shù)據(jù)的類型還是挺困難的。反正我剛用Go開始做項目時面對數(shù)據(jù)庫之前的一個復(fù)雜的JSON研究了一天才解析出來(也有我那會太菜的原因,被逼無奈看了兩天語法,就直接開始用Go寫項目了)。所以我花時間總結(jié)了一下常見的幾類JSON數(shù)據(jù)組合模式應(yīng)該如何聲明解析數(shù)據(jù)的類型,以及UnMarshal和Decode兩個解碼函數(shù)的用法。
文章主題內(nèi)容是很早以前發(fā)在思否上的一篇文章,后來授權(quán)給了Go語言中文網(wǎng)的站長。那會兒我還覺得公眾號不適合寫技術(shù)文章。回看之前那篇文章感覺有的地方文字表達的方式不太好,這跟自己對語言的熟悉程度也有關(guān)。
我們先從最簡單的JSON數(shù)據(jù)內(nèi)容開始介紹,一點點增加JSON數(shù)據(jù)內(nèi)容的復(fù)雜度。
解析簡單JSON
先觀察下這段JSON數(shù)據(jù)的組成,name,created是字符串。id是整型,fruit是一個字符串數(shù)組
{"name":?"Standard",?"fruit":?["Apple",?"Banana",?"Orange"],?"id":?999,?"created":?"2018-04-09T23:00:00Z" }那么對應(yīng)的在Go里面解析數(shù)據(jù)的類型應(yīng)該被聲明為:
type?FruitBasket?struct?{Name????string????`json:"name"`Fruit???[]string??`json:"fruit"`Id??????int64?????`json:"id"`Created?time.Time?`json:"created"` }完整的解析JSON的代碼如下:
package?mainimport?("fmt""encoding/json""time")func?main()?{type?FruitBasket?struct?{Name????string????`json:"name"`Fruit???[]string??`json:"fruit"`Id??????int64?????`json:"id"`Created?time.Time?`json:"created"`}jsonData?:=?[]byte(`{"name":?"Standard","fruit":?["Apple","Banana","Orange"],"id":?999,"created":?"2018-04-09T23:00:00Z"}`)var?basket?FruitBasketerr?:=?json.Unmarshal(jsonData,?&basket)if?err?!=?nil?{fmt.Println(err)}fmt.Println(basket.Name,?basket.Fruit,?basket.Id)fmt.Println(basket.Created) }說明:由于json.UnMarshal()方法接收的是字節(jié)切片,所以首先需要把JSON字符串轉(zhuǎn)換成字節(jié)切片c := []byte(s)
解析內(nèi)嵌對象的JSON
把上面的fruit鍵對應(yīng)的值如果改成字典 變成"fruit" : {"name":"Apple", "priceTag":"$1"}:
jsonData?:=?[]byte(`{"name":?"Standard","fruit"?:?{"name":?"Apple",?"priceTag":?"$1"},"def":?999,"created":?"2018-04-09T23:00:00Z"}`)那么Go語言里存放解析數(shù)據(jù)的類型應(yīng)該這么聲明
type?Fruit?struct?{Name?string?`json":name"`PriceTag?string?`json:"priceTag"` }type?FruitBasket?struct?{Name????string????`json:"name"`Fruit???Fruit?????`json:"fruit"`Id??????int64?????`json:"id"`Created?time.Time?`json:"created"` }解析內(nèi)嵌對象數(shù)組的JSON
如果上面JSON數(shù)據(jù)里的Fruit值現(xiàn)在變成了
"fruit"?:?[{"name":?"Apple","priceTag":?"$1"},{"name":?"Pear","priceTag":?"$1.5"} ]這種情況也簡單把存放解析后數(shù)據(jù)的類型其聲明做如下更改,把Fruit字段類型換為?[]Fruit即可
type?Fruit?struct?{Name?string?`json:"name"`PriceTag?string?`json:"priceTag"` }type?FruitBasket?struct?{Name????string????`json:"name"`Fruit???[]Fruit???`json:"fruit"`Id??????int64?????`json:"id"`Created?time.Time?`json:"created"` }解析具有動態(tài)Key的對象
下面再做一下復(fù)雜的變化,如果把上面的對象數(shù)組變?yōu)橐訤ruit的Id作為屬性名的復(fù)合對象(object of object)比如:
"Fruit"?:?{"1":?{"Name":?"Apple","PriceTag":?"$1"},"2":?{"Name":?"Pear","PriceTag":?"$1.5"} }每個Key的名字在聲明類型的時候是不知道值的,這樣該怎么聲明呢,答案是把Fruit字段的類型聲明為一個Key為string類型值為Fruit類型的map
type?Fruit?struct?{Name?string?`json:"name"`PriceTag?string?`json:"priceTag"` }type?FruitBasket?struct?{Name????string?????????????`json:"name"`Fruit???map[string]Fruit???`json:"fruit"`Id??????int64??????????????`json:"id"`Created?time.Time??????????`json:"created"` }可以運行下面完整的代碼段試一下。
package?mainimport?("fmt""encoding/json""time")func?main()?{type?Fruit?struct?{Name?string?`json:"name"`PriceTag?string?`json:"priceTag"`}type?FruitBasket?struct?{Name????string?????????????`json:"name"`Fruit???map[string]Fruit???`json:"fruit"`Id??????int64??????????????`json:"id"`Created?time.Time??????????`json:"created"`}??jsonData?:=?[]byte(`{"Name":?"Standard","Fruit"?:?{"1":?{"name":?"Apple","priceTag":?"$1"},"2":?{"name":?"Pear","priceTag":?"$1.5"}},"id":?999,"created":?"2018-04-09T23:00:00Z"}`)var?basket?FruitBasketerr?:=?json.Unmarshal(jsonData,?&basket)if?err?!=?nil?{fmt.Println(err)}for?_,?item?:=?range?basket.Fruit?{fmt.Println(item.Name,?item.PriceTag)} }解析包含任意層級的數(shù)組和對象的JSON數(shù)據(jù)
針對包含任意層級的JSON數(shù)據(jù),encoding/json包使用:
map[string]interface{}?存儲JSON對象
[]interface?存儲JSON數(shù)組
json.Unmarshl?將會把任何合法的JSON數(shù)據(jù)存儲到一個interface{}類型的值,通過使用空接口類型我們可以存儲任意值,但是使用這種類型作為值時需要先做一次類型斷言。
jsonData?:=?[]byte(`{"Name":"Eve","Age":6,"Parents":["Alice","Bob"]}`)var?v?interface{} json.Unmarshal(jsonData,?&v) data?:=?v.(map[string]interface{})for?k,?v?:=?range?data?{switch?v?:=?v.(type)?{case?string:fmt.Println(k,?v,?"(string)")case?float64:fmt.Println(k,?v,?"(float64)")case?[]interface{}:fmt.Println(k,?"(array):")for?i,?u?:=?range?v?{fmt.Println("????",?i,?u)}default:fmt.Println(k,?v,?"(unknown)")} }雖然將JSON數(shù)據(jù)存儲到空接口類型的值中可以用來解析任意結(jié)構(gòu)的JSON數(shù)據(jù),但是在實際應(yīng)用中發(fā)現(xiàn)還是有不可控的地方,比如將數(shù)字字符串的值轉(zhuǎn)換成了float類型的值,所以經(jīng)常會在運行時報類型斷言的錯誤,所以在JSON結(jié)構(gòu)確定的情況下還是優(yōu)先使用結(jié)構(gòu)體類型聲明,將JSON數(shù)據(jù)到結(jié)構(gòu)體中的方式來解析JSON。
用 Decoder解析數(shù)據(jù)流
上面都是使用的UnMarshall解析的JSON數(shù)據(jù),如果JSON數(shù)據(jù)的載體是打開的文件或者HTTP請求體這種數(shù)據(jù)流(他們都是io.Reader的實現(xiàn)),我們不必把JSON數(shù)據(jù)讀取出來后再去調(diào)用encode/json包的UnMarshall方法,包提供的Decode方法可以完成讀取數(shù)據(jù)流并解析JSON數(shù)據(jù)最后填充變量的操作。
//?This?example?uses?a?Decoder?to?decode?a?stream?of?distinct?JSON?values. func?ExampleDecoder()?{const?jsonStream?=?`{"Name":?"Ed",?"Text":?"Knock?knock."}{"Name":?"Sam",?"Text":?"Who's?there?"}{"Name":?"Ed",?"Text":?"Go?fmt."}{"Name":?"Sam",?"Text":?"Go?fmt?who?"}{"Name":?"Ed",?"Text":?"Go?fmt?yourself!"} `type?Message?struct?{Name,?Text?string}dec?:=?json.NewDecoder(strings.NewReader(jsonStream))for?{var?m?Messageif?err?:=?dec.Decode(&m);?err?==?io.EOF?{break}?else?if?err?!=?nil?{log.Fatal(err)}fmt.Printf("%s:?%s\n",?m.Name,?m.Text)}//?Output://?Ed:?Knock?knock.//?Sam:?Who's?there?//?Ed:?Go?fmt.//?Sam:?Go?fmt?who?//?Ed:?Go?fmt?yourself! }寫這篇文章的主要目的是和之前介紹Go語言如何編碼JSON的文章搭配上,盡量讓這個主題在公眾號里的文章完整些,這樣也更便于剛接觸Go語言的同學(xué)的學(xué)習(xí)。
推薦閱讀:
如何控制Go編碼JSON數(shù)據(jù)時的行為
總結(jié)
以上是生活随笔為你收集整理的用 Go 解析复杂 JSON 的思路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 飞哥:程序员完全没时间提升自己该怎么办?
 - 下一篇: 动手实现一个 localcache -