在平常工做中,每一名開發者,不論是前端仍是後端,都常用 JSON。JSON 是一個很簡單的數據交換格式。相比於 XML,它靈活、輕巧、使用方便。JSON 也是RESTful API推薦的格式。有時,咱們只想讀取 JSON 中的某一些字段。若是本身手動解析、一層一層讀取,這就變得異常繁瑣了。特別是在嵌套層次很深的狀況下。今天咱們介紹gojsonq
。它能夠幫助咱們很方便的操做 JSON。html
先安裝:前端
$ go get github.com/thedevsaddam/gojsonq
後使用:node
package main import ( "fmt" "github.com/thedevsaddam/gojsonq" ) func main() { content := `{ "user": { "name": "dj", "age": 18, "address": { "provice": "shanghai", "district": "xuhui" }, "hobbies":["chess", "programming", "game"] } }` gq := gojsonq.New().FromString(content) district := gq.Find("user.address.district") fmt.Println(district) gq.Reset() hobby := gq.Find("user.hobbies.[0]") fmt.Println(hobby) }
操做很是簡單:git
gojsonq.New()
建立一個JSONQ
的對象;上面代碼咱們直接讀取位於最內層的district
值和hobbies
數組的第一個元素!層與層之間用.
隔開,若是是數組,則在屬性字段後經過.[index]
讀取下標爲index
的元素。這種方式能夠實現很靈活的讀取。github
注意到一個細節:在查詢以後,咱們手動調用了一次Reset()
方法。由於JSONQ
對象在調用Find
方法時,內部會記錄當前的節點,下一個查詢會從上次查找的節點開始。也就是說若是咱們註釋掉jq.Reset()
,第二個Find()
方法實際上查找的是user.address.district.user.hobbies.[0]
,天然就返回nil
了。除此以外,gojsonq
也提供了另一種方式。若是你想要保存當前查詢的一些狀態信息,能夠調用JSONQ
的Copy
方法返回一個初始狀態下的對象,它們會共用底層的 JSON 字符串和解析後的對象。上面的gq.Reset()
能夠由下面這行代碼代替:golang
gpCopy := gp.Copy()
後面就可使用gpCopy
查詢hobbies
了。json
這個算是gojsonq
庫的一個特色,但也是初學者帶來了不少困擾,須要特別注意。實際上,JSONQ
提供的不少方法會改變當前節點,稍後部分咱們會更清楚的看到。segmentfault
除了從字符串中加載,jsonq
還容許從文件和io.Reader
中讀取內容。分別使用JSONQ
對象的File
和Reader
方法:後端
func main() { gq := gojsonq.New().File("./data.json") fmt.Println(gq.Find("items.[1].price")) }
和下面程序的效果是同樣的:api
func main() { file, err := os.OpenFile("./data.json", os.O_RDONLY, 0666) if err != nil { log.Fatal(err) } gq := gojsonq.New().Reader(file) fmt.Println(gq.Find("items.[1].price")) }
爲了後面演示方便,我構造了一個data.json
文件:
{ "name": "shopping cart", "description": "List of items in your cart", "prices": ["2400", "2100", "1200", "400.87", "89.90", "150.10"], "items": [ { "id": 1, "name": "Apple", "count": 2, "price": 12 }, { "id": 2, "name": "Notebook", "count": 10, "price": 3 }, { "id": 3, "name": "Pencil", "count": 5, "price": 1 }, { "id": 4, "name": "Camera", "count": 1, "price": 1750 }, { "id": null, "name": "Invalid Item", "count": 1, "price": 12000 } ] }
gojsonq
的獨特之處在於,它能夠像 SQL 同樣進行條件查詢,能夠選擇返回哪些字段,能夠作一些聚合統計。
有時候,咱們只關心對象中的幾個字段,這時候就可使用Select
指定返回哪些字段,其他字段不返回:
func main() { r := gojsonq.New().File("./data.json").From("items").Select("id", "name").Get() data, _ := json.MarshalIndent(r, "", " ") fmt.Println(string(data)) }
只會輸出id
和name
字段:
$ go run main.go [ { "id": 1, "name": "Apple" }, { "id": 2, "name": "Notebook" }, { "id": 3, "name": "Pencil" }, { "id": 4, "name": "Camera" }, { "id": null, "name": "Invalid Item" } ]
爲了顯示更直觀一點,我這裏用json.MarshalIndent()
對輸出作了一些美化。
是否是和 SQL 有點像Select id,name From items
...
這裏介紹一下From
方法,這個方法的做用是將當前節點移動到指定位置。上面也說過當前節點的位置是記下來的。例如,上面的代碼中咱們先將當前節點移動到items
,後面的查詢和聚合操做都是針對這個數組。實際上Find
方法內部就調用了From
:
// src/github.com/thedevsaddam/gojsonq/jsonq.go func (j *JSONQ) Find(path string) interface{} { return j.From(path).Get() } func (j *JSONQ) From(node string) *JSONQ { j.node = node v, err := getNestedValue(j.jsonContent, node, j.option.separator) if err != nil { j.addError(err) } // ============= 注意這一行,記住當前節點位置 j.jsonContent = v return j }
最後必需要調用Get()
,它組合全部條件後執行這個查詢,返回結果。
有了Select
和From
,怎麼能沒有Where
呢?gojsonq
提供的Where
方法很是多,咱們大概看幾個就好了。
首先是,Where(key, op, val)
,這個是通用的Where
條件,表示key
和val
是否知足op
關係。op
內置的就有將近 20 種,還支持自定義。例如=
表示相等,!=
表示不等,startsWith
表示val
是不是key
字段的前綴等等等等;
其餘不少條件都是Where
的特例,例如WhereIn(key, val)
就等價於Where(key, "in", val)
,WhereStartsWith(key, val)
就等價於Where(key, "startsWith", val)
。
默認狀況下,Where
的條件都是And
鏈接的,咱們能夠經過OrWhere
讓其以Or
鏈接:
func main() { gq := gojsonq.New().File("./data.json") r := gq.From("items").Select("id", "name"). Where("id", "=", 1).OrWhere("id", "=", 2).Get() fmt.Println(r) gq.Reset() r = gq.From("items").Select("id", "name", "count"). Where("count", ">", 1).Where("price", "<", 100).Get() fmt.Println(r) }
上面第一個查詢,查找id
爲 1 或 2 的記錄。第二個查詢,查找count
大於 1 且 price
小於 100 的記錄。
有時咱們想要分頁顯示,第一次查詢時返回前 3 條內容,第二次查詢時返回接下來的 3 條記錄。咱們可使用JSONQ
對象的Offset
和Limit
方法來指定偏移和返回的條目數:
func main() { gq := gojsonq.New().File("./data.json") r1 := gq.From("items").Select("id", "name").Offset(0).Limit(3).Get() fmt.Println("First Page:", r1) gq.Reset() r2 := gq.From("items").Select("id", "name").Offset(3).Limit(3).Get() fmt.Println("Second Page:", r2) }
來看看運行結果:
$ go run main.go First Page: [map[id:1 name:Apple] map[id:2 name:Notebook] map[id:3 name:Pencil]] Second Page: [map[id:4 name:Camera] map[id:<nil> name:Invalid Item]]
咱們還能能夠對一些字段作簡單的統計,計算和、平均數、最大、最小值等:
func main() { gq := gojsonq.New().File("./data.json").From("items") fmt.Println("Total Count:", gq.Sum("count")) fmt.Println("Min Price:", gq.Min("price")) fmt.Println("Max Price:", gq.Max("price")) fmt.Println("Avg Price:", gq.Avg("price")) }
上面統計商品的總數量、最低價格、最高價格和平均價格。
聚合統計類的方法都不會修改當前節點的指向,因此JSONQ
對象能夠重複使用!
還能夠對數據進行分組和排序:
func main() { gq := gojsonq.New().File("./data.json") fmt.Println(gq.From("items").GroupBy("price").Get()) gq.Reset() fmt.Println(gq.From("items").SortBy("price", "desc").Get()) }
默認狀況下,gojsonq
使用 JSON 格式解析數據。咱們也能夠設置其餘格式解析器讓gojsonq
能夠處理其餘格式的數據:
func main() { jq := gojsonq.New(gojsonq.SetDecoder(&yamlDecoder{})).File("./data.yaml") jq.From("items").Where("price", "<=", 500) fmt.Printf("%v\n", jq.First()) } type yamlDecoder struct { } func (i *yamlDecoder) Decode(data []byte, v interface{}) error { bb, err := yaml.YAMLToJSON(data) if err != nil { return err } return json.Unmarshal(bb, &v) }
上面代碼用到了yaml
庫,須要額外安裝:
$ go get github.com/ghodss/yaml
解析器只要實現gojsonq.Decoder
接口,均可以做爲設置到gojsonq
中,這樣就能夠實現任何格式的處理:
// src/github.com/thedevsaddam/gojsonq/decoder.go type Decoder interface { Decode(data []byte, v interface{}) error }
gojsonq
還有一些高級特性,例如自定義Where
的操做類型,取第一個、最後一個、第 N 個值等。感興趣可自行研究~
你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~
本文由博客一文多發平臺 OpenWrite 發佈!