1. 程式人生 > 其它 >go 列印json 轉義_Go 每日一庫之 gjson

go 列印json 轉義_Go 每日一庫之 gjson

技術標籤:go 列印json 轉義

簡介

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

快速使用

先安裝:

$ go get github.com/tidwall/gjson

後使用:

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()方法。

如果是直接列印輸出,其實可以省略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 個比較簡單,就不贅述了。看後面幾個:

  • 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":"[email protected]"}]}`
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":"[email protected]"}

修飾符引數

修飾符還可以有引數,通過在修飾符後加:後跟引數。如果我們在格式化 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": "[email protected]"
}
}`

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": "[email protected]"
}
}`

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】,共同學習,一起進步~

323738f8ed21918fe0dce00e5183c81f.png