Go 每日一庫之 gojsonq

簡介

在平常工做中,每一名開發者,不論是前端仍是後端,都常用 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也提供了另一種方式。若是你想要保存當前查詢的一些狀態信息,能夠調用JSONQCopy方法返回一個初始狀態下的對象,它們會共用底層的 JSON 字符串和解析後的對象。上面的gq.Reset()能夠由下面這行代碼代替:golang

gpCopy := gp.Copy()
複製代碼

後面就可使用gpCopy查詢hobbies了。json

這個算是gojsonq庫的一個特色,但也是初學者帶來了不少困擾,須要特別注意。實際上,JSONQ提供的不少方法會改變當前節點,稍後部分咱們會更清楚的看到。後端

數據源

除了從字符串中加載,jsonq還容許從文件和io.Reader中讀取內容。分別使用JSONQ對象的FileReader方法: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))
}
複製代碼

只會輸出idname字段:

$ 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(),它組合全部條件後執行這個查詢,返回結果。

條件查詢

有了SelectFrom,怎麼能沒有Where呢?gojsonq提供的Where方法很是多,咱們大概看幾個就好了。

首先是,Where(key, op, val),這個是通用的Where條件,表示keyval是否知足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對象的OffsetLimit方法來指定偏移和返回的條目數:

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😄

參考

  1. gojsonq GitHub:github.com/thedevsadda…
  2. Go 每日一庫 GitHub:github.com/darjun/go-d…

個人博客

歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索