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.0
:c?ildren
匹配到children
,.0
讀取第一個元素;fav.\moive
:因為鍵名中含有.
,故需要\
轉義;friends.#.first
:如果陣列後#
後還有內容,則以後面的路徑讀取陣列中的每個元素,返回一個新的陣列。所以該查詢返回的陣列所有friends
的first
欄位組成;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").first
:friends.#(last="Murphy")
返回陣列friends
中第一個last
為Murphy
的元素,.first
表示取出該元素的first
欄位返回;friends.#(last="Murphy")#.first
:friends.#(last="Murphy")#
返回陣列friends
中所有的last
為Murphy
的元素,然後讀取它們的first
欄位放在一個數組中返回。注意與上面一個的區別;friends.#(age>45)#.last
:friends.#(age>45)#
返回陣列friends
中所有年齡大於 45 的元素,然後讀取它們的last
欄位返回;friends.#(first%"D*").last
:friends.#(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
的回撥函式。遍歷物件時key
和value
分別為物件的鍵和值;遍歷陣列時,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 串是非法的,dj
和age
都沒有加上雙引號(實際上習慣了 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)
}
}
上面程式碼返回欄位name
、age
、陣列pets
的長度和contact.phone
欄位。
總結
gjson
使用比較方便,功能強大,效能可觀,值得一學。
大家如果發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue?
參考
- gjson GitHub:https://github.com/tidwall/gjson
- Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib
我
我的部落格:https://darjun.github.io
歡迎關注我的微信公眾號【GoUpUp】,共同學習,一起進步~