Go 每日一庫之 gjson

簡介

以前咱們介紹過gojsonq,能夠方便地從一個 JSON 串中讀取值。同時它也支持各類查詢、彙總統計等功能。今天咱們再介紹一個相似的庫gjson。在上一篇文章Go 每日一庫之 buntdb中咱們介紹過 JSON 索引,內部實現其實就是使用gjson這個庫。gjson其實是get + json的縮寫,用於讀取 JSON 串,一樣的還有一個sjsonset + json)庫用來設置 JSON 串。git

快速使用

先安裝:github

$ go get github.com/tidwall/gjson

後使用:golang

package main

import (
  "fmt"

  "github.com/tidwall/gjson"
)

func main() {
  json := `{"name":{"first":"li","last":"dj"},"age":18}`
  lastName := gjson.Get(json, "name.last")
  fmt.Println("last name:", lastName.String())

  age := gjson.Get(json, "age")
  fmt.Println("age:", age.Int())
}

使用很簡單,只須要傳入 JSON 串和要讀取的鍵路徑便可。注意一點細節,由於gjson.Get()函數實際上返回的是gjson.Result類型,咱們要調用其相應的方法進行轉換對應的類型。如上面的String()Int()方法。json

若是是直接打印輸出,其實能夠省略String()fmt包的大部分函數均可以對實現fmt.Stringer接口的類型調用String()方法。數組

鍵路徑

鍵路徑其實是以.分隔的一系列鍵。gjson支持在鍵中包含通配符*?*匹配任意多個字符,?匹配單個字符,例如ca*能夠匹配cat/cate/cake等以ca開頭的鍵,ca?只能匹配cat/cap等以ca開頭且後面只有一個字符的鍵。微信

數組使用鍵名 + . + 索引(索引從 0 開始)的方式讀取元素,若是鍵pets對應的值是一個數組,那麼pets.0讀取數組的第一個元素,pets.1讀取第二個元素。函數

數組長度使用鍵名 + . + #獲取,例如pets.#返回數組pets的長度。性能

若是鍵名中出現.,那麼須要使用\進行轉義。學習

package main

const json = `
{
  "name":{"first":"Tom", "last": "Anderson"},
  "age": 37,
  "children": ["Sara", "Alex", "Jack"],
  "fav.movie": "Dear Hunter",
  "friends": [
    {"first": "Dale", "last":"Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
    {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
    {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
  ]
}
`

func main() {
  fmt.Println("last name:", gjson.Get(json, "name.last"))
  fmt.Println("age:", gjson.Get(json, "age"))
  fmt.Println("children:", gjson.Get(json, "children"))
  fmt.Println("children count:", gjson.Get(json, "children.#"))
  fmt.Println("second child:", gjson.Get(json, "children.1"))
  fmt.Println("third child*:", gjson.Get(json, "child*.2"))
  fmt.Println("first c?ild:", gjson.Get(json, "c?ildren.0"))
  fmt.Println("fav.moive", gjson.Get(json, `fav.\moive`))
  fmt.Println("first name of friends:", gjson.Get(json, "friends.#.first"))
  fmt.Println("last name of second friend:", gjson.Get(json, "friends.1.last"))
}

前 3 個比較簡單,就不贅述了。看後面幾個:this

  • children.#:返回數組children的長度;
  • children.1:讀取數組children的第 2 個元素(注意索引從 0 開始);
  • child*.2:首先child*匹配children.2讀取第 3 個元素;
  • c?ildren.0c?ildren匹配到children.0讀取第一個元素;
  • fav.\moive:由於鍵名中含有.,故須要\轉義;
  • friends.#.first:若是數組後#後還有內容,則之後面的路徑讀取數組中的每一個元素,返回一個新的數組。因此該查詢返回的數組全部friendsfirst字段組成;
  • friends.1.last:讀取friends第 2 個元素的last字段。

運行結果:

last name: Anderson
age: 37
children: ["Sara", "Alex", "Jack"]
children count: 3
second child: Alex
third child*: Jack
first c?ild: Sara
fave.moive 
first name of friends: ["Dale","Roger","Jane"]
last name of second friend: Craig

對於數組,gjson還支持按條件查詢元素,#(條件)返回第一個知足條件的元素,#(條件)#返回全部知足條件的元素。括號內的條件能夠有==!=<<=>>=,還有簡單的模式匹配%(符合某個模式),!%(不符合某個模式):

fmt.Println(gjson.Get(json, `friends.#(last="Murphy").first`))
fmt.Println(gjson.Get(json, `friends.#(last="Murphy")#.first`))
fmt.Println(gjson.Get(json, "friends.#(age>45)#.last"))
fmt.Println(gjson.Get(json, `friends.#(first%"D*").last`))
fmt.Println(gjson.Get(json, `friends.#(first!%"D*").last`))
fmt.Println(gjson.Get(json, `friends.#(nets.#(=="fb"))#.first`))

仍是使用上面的 JSON 串。

  • friends.#(last="Murphy").firstfriends.#(last="Murphy")返回數組friends中第一個lastMurphy的元素,.first表示取出該元素的first字段返回;
  • friends.#(last="Murphy")#.firstfriends.#(last="Murphy")#返回數組friends中全部的lastMurphy的元素,而後讀取它們的first字段放在一個數組中返回。注意與上面一個的區別;
  • friends.#(age>45)#.lastfriends.#(age>45)#返回數組friends中全部年齡大於 45 的元素,而後讀取它們的last字段返回;
  • friends.#(first%"D*").lastfriends.#(first%"D*")返回數組friends中第一個first字段知足模式D*的元素,取出其last字段返回;
  • friends.#(first!%"D*").last`friends.#(first!%"D*")返回數組friends中第一個first字段知足模式D*的元素,讀取其last字段返回;
  • friends.#(nets.#(=="fb"))#.first:這是個嵌套條件,friends.#(nets.#(=="fb"))#返回數組friends的元素的nets字段中有fb的全部元素,而後取出first字段返回。

運行結果:

Dale
["Dale","Jane"]
["Craig","Murphy"]
Murphy
Craig
["Dale","Roger"]

修飾符

修飾符gjson提供的很是強大的功能,和鍵路徑搭配使用。gjson提供了一些內置的修飾符:

  • @reverse:翻轉一個數組;
  • @ugly:移除 JSON 中的全部空白符;
  • @pretty:使 JSON 更易用閱讀;
  • @this:返回當前的元素,能夠用來返回根元素;
  • @valid:校驗 JSON 的合法性;
  • @flatten:數組平坦化,即將["a", ["b", "c"]]轉爲["a","b","c"]
  • @join:將多個對象合併到一個對象中。

修飾符的語法和管道相似,以|分隔鍵路徑和分隔符。

const json = `{
  "name":{"first":"Tom", "last": "Anderson"},
  "age": 37,
  "children": ["Sara", "Alex", "Jack"],
  "fav.movie": "Dear Hunter",
  "friends": [
    {"first": "Dale", "last":"Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
    {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
    {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
  ]
}`

func main() {
  fmt.Println(gjson.Get(json, "children|@reverse"))
  fmt.Println(gjson.Get(json, "children|@reverse|0"))
  fmt.Println(gjson.Get(json, "friends|@ugly"))
  fmt.Println(gjson.Get(json, "friends|@pretty"))
  fmt.Println(gjson.Get(json, "@this"))

  nestedJSON := `{"nested": ["one", "two", ["three", "four"]]}`
  fmt.Println(gjson.Get(nestedJSON, "nested|@flatten"))

  userJSON := `{"info":[{"name":"dj", "age":18},{"phone":"123456789","email":"dj@example.com"}]}`
  fmt.Println(gjson.Get(userJSON, "info|@join"))
}

children|@reverse先讀取數組children,而後使用修飾符@reverse翻轉以後返回,輸出:

["Jack","Alex","Sara"]

children|@reverse|0在上面翻轉的基礎上讀取第一個元素,即原數組的最後一個元素,輸出:

Jack

friends|@ugly移除friends數組中的全部空白字符,返回一行長長的字符串:

[{"first":"Dale","last":"Murphy","age":44,"nets":["ig","fb","tw"]},{"first":"Roger","last":"Craig","age":68,"nets":["fb","tw"]},{"first":"Jane","last":"Murphy","age":47,"nets":["ig","tw"]}]

friends|@pretty格式化friends數組,使之更易讀:

[
  {
    "first": "Dale",
    "last": "Murphy",
    "age": 44,
    "nets": ["ig", "fb", "tw"]
  }, 
  {
    "first": "Roger",
    "last": "Craig",
    "age": 68,
    "nets": ["fb", "tw"]
  }, 
  {
    "first": "Jane",
    "last": "Murphy",
    "age": 47,
    "nets": ["ig", "tw"]
  }
]

@this返回原始的 JSON 串。

@flatten將數組nested的內層數組平坦到外層後返回,即將全部內層數組的元素依次添加到外層數組後面並移除內層數組,輸出:

["one","two","three", "four"]

@join將一個數組中的各個對象合併到一箇中,例子中將數組中存放的部分我的信息合併成一個對象返回:

{"name":"dj","age":18,"phone":"123456789","email":"dj@example.com"}

修飾符參數

修飾符還能夠有參數,經過在修飾符後加:後跟參數。若是咱們在格式化 JSON 串時,想要對鍵進行排序,那麼可使用@pretty修飾符的sortKeys參數。咱們仍是拿上面的 JSON 數據舉例:

fmt.Println(gjson.Get(json, `friends|@pretty:{"sortKeys":true}`))

最終按鍵名順序輸出 JSON 串:

[
  {
    "age": 44,
    "first": "Dale",
    "last": "Murphy",
    "nets": ["ig", "fb", "tw"]
  }, 
  {
    "age": 68,
    "first": "Roger",
    "last": "Craig",
    "nets": ["fb", "tw"]
  }, 
  {
    "age": 47,
    "first": "Jane",
    "last": "Murphy",
    "nets": ["ig", "tw"]
  }
]

固然還能夠指定每行縮進indent(默認兩個空格),每行開頭字符串prefix(默認爲空串)和一行最多顯示字符數width(默認 80 字符)。下面在每行前增長兩個空格:

fmt.Println(gjson.Get(json, `friends|@pretty:{"sortKeys":true,"prefix":"  "}`))

自定義修飾符

如此強大的功固然要支持自定義!gjson使用AddModifier()添加一個修飾符,傳入一個名字和類型爲func(json arg string) string的處理函數。處理函數接受待處理的 JSON 值和修飾符參數,返回處理後的結果。下面編寫一個轉換大小寫的修飾符:

func main() {
  gjson.AddModifier("case", func(json, arg string) string {
    if arg == "upper" {
      return strings.ToUpper(json)
    }

    if arg == "lower" {
      return strings.ToLower(json)
    }

    return json
  })

  const json = `{"children": ["Sara", "Alex", "Jack"]}`
  fmt.Println(gjson.Get(json, "children|@case:upper"))
  fmt.Println(gjson.Get(json, "children|@case:lower"))
}

輸出:

["SARA", "ALEX", "JACK"]
["sara", "alex", "jack"]

JSON 行

gjson提供..語法能夠將多行數據當作一個數組,每行數據是一個元素:

const json = `
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}`

func main() {
  fmt.Println(gjson.Get(json, "..#"))
  fmt.Println(gjson.Get(json, "..1"))
  fmt.Println(gjson.Get(json, "..#.name"))
  fmt.Println(gjson.Get(json, `..#(name="May").age`))
}
  • ..#:返回有多少行 JSON 數據;
  • ..1:返回第一行,即{"name": "Gilbert", "age": 61}
  • ..#.name#後再接路徑,表示對數組中每一個元素讀取後面的路徑,將讀取到的值組成一個新數組返回;..#.name表示讀取每一行中的name字段,最終返回["Gilbert","Alexa","May","Deloise"]
  • ..#(name="May").age:括號中的內容(name="May")表示條件,因此該條含義爲取name"May"的行中的age字段。

gjson還提供了遍歷 JSON 行的方法:gjson.ForEachLine(),參數爲 JSON 串和類型爲func(line gjson.Result) bool的回調函數。回調返回false時遍歷中止。下面代碼讀取輸出每一行的name字段:

gjson.ForEachLine(json, func(line gjson.Result) bool {
  fmt.Println("name:", gjson.Get(line.String(), "name"))
  return true
})

遍歷

上面咱們介紹了遍歷 JSON 行的方式,實際上gjson還提供了通用的遍歷數組和對象的方式。gjson.Get()方法返回一個gjson.Result類型的對象,json.Result提供了ForEach()方法用於遍歷。該方法接受一個類型爲func (key, value gjson.Result) bool的回調函數。遍歷對象時keyvalue分別爲對象的鍵和值;遍歷數組時,value爲數組元素,key爲空(不是索引)。回調返回false時,遍歷中止。

const json = `
{
  "name":"dj",
  "age":18,
  "pets": ["cat", "dog"],
  "contact": {
    "phone": "123456789",
    "email": "dj@example.com"
  }
}`

func main() {
  pets := gjson.Get(json, "pets")
  pets.ForEach(func(_, pet gjson.Result) bool {
    fmt.Println(pet)
    return true
  })

  contact := gjson.Get(json, "contact")
  contact.ForEach(func(key, value gjson.Result) bool {
    fmt.Println(key, value)
    return true
  })
}

校驗 JSON

調用gjson.Get()時,gjson假設咱們傳入的 JSON 串是合法的。若是 JSON 非法也不會panic,這時會返回不肯定的結果:

func main() {
  const json = `{"name":dj,age:18}`
  fmt.Println(gjson.Get(json, "name"))
}

上面 JSON 串是非法的,djage都沒有加上雙引號(實際上習慣了 Go 語言map的寫法,很容易把 JSON 寫成這樣😭)。上面代碼輸出18,顯然是錯誤的。咱們可使用gjson.Valid()檢測 JSON 串是否合法:

if !gjson.Valid(json) {
  fmt.Println("error")
} else {
  fmt.Println("ok")
}

一次獲取多個值

調用gjson.Get()一次只能讀取一個值,屢次調用又比較麻煩,gjson提供了GetMany()能夠一次讀取多個值,返回一個數組[]gjson.Result

const json = `
{
  "name":"dj",
  "age":18,
  "pets": ["cat", "dog"],
  "contact": {
    "phone": "123456789",
    "email": "dj@example.com"
  }
}`

func main() {
  results := gjson.GetMany(json, "name", "age", "pets.#", "contact.phone")
  for _, result := range results {
    fmt.Println(result)
  }
}

上面代碼返回字段nameage、數組pets的長度和contact.phone字段。

總結

gjson使用比較方便,功能強大,性能可觀,值得一學。

你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄

參考

  1. gjson GitHub:https://github.com/tidwall/gjson
  2. Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib

個人博客:https://darjun.github.io

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

相關文章
相關標籤/搜索