在平常工做中,每一名開發者,不論是前端仍是後端,都常用 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
提供的不少方法會改變當前節點,稍後部分咱們會更清楚的看到。後端
除了從字符串中加載,jsonq
還容許從文件和io.Reader
中讀取內容。分別使用JSONQ
對象的File
和Reader
方法:api
func main() {
gq := gojsonq.New().File("./data.json")
fmt.Println(gq.Find("items.[1].price"))
}
複製代碼
和下面程序的效果是同樣的:數組
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 發佈!