[TOC]git
gjson
的應用和分享我們上次分享到使用 GO 爬取靜態網頁的數據,一塊兒來回顧一下github
要是對 GO 爬取靜態數據還有點興趣的話,歡迎查看文章 分享一波 GO 的爬蟲編程
json
是什麼?
JSON
(JavaScript Object Notation,JS
對象簡譜) 是一種輕量級的數據交換格式json它基於 ECMAScript (歐洲計算機協會制定的
JS
規範)的一個子集,採用徹底獨立於編程語言的文本格式來存儲和表示數據segmentfault
json
有以下幾個優點:數組
簡單列一下我們經常使用的數據序列化的方式安全
是可擴展標記語言,是一種簡單的數據存儲語言
是一種 平臺無關、語言無關、可擴展且輕便高效的序列化數據結構的協議,能夠用於 網絡通訊 和 數據存儲
gjson
是什麼?是 GO 裏面的一個庫網絡
它主要是提供了一種很是快速且簡單的方式從json
文檔中獲取相應值數據結構
這個 gjson
庫,其實是 get + json
的縮寫,無獨有偶,一樣的也有sjson
庫,小夥伴們就知道他表明的含義了吧,是 set + json
的意思併發
gjson
如何使用?對於 gjson
如何使用,XDM,我這裏把這個庫的基本使用,涉及到的知識點,以及注意事項,給你們梳理梳理
要是想看看 gjson
的源碼是如何實現高效快速的操做json
的,感興趣的朋友,能夠在先會引用的基礎上再去詳細查看一下源碼
本文的分享,圍繞以下 4 個方面來實操和梳理 gjson
的使用:
gjson
的簡單使用gjson
的 json
行gjson
的 修飾符 和 自定義修飾符gjson
鍵路徑的匹配規則
gjson
的簡單使用我們簡單使用一個gjson
,以下編碼涉及以下幾個點:
json
數據json
數據 是否合法package main import ( "log" "github.com/tidwall/gjson" ) func main() { // 設置參數,打印行數 log.SetFlags(log.Lshortfile | log.LstdFlags) // 設置 json 數據 json := ` { "author": { "name": "xiaomotong", "age": 18, "hobby": "writing" }, "extra": "hello wolrd" "picList":[{"name":"xiaozhu1"},{"name":"xiaozhu2"}] } ` // 校驗 json 字符串是否合法 // 若是不合法的話, gjson 不會報錯 panic,可能會拿到一個奇怪值 if gjson.Valid(json){ log.Println("json valid ...") }else{ log.Fatal("json invalid ... ") } // 獲取 author.name 的 值 aName := gjson.Get(json, "author.name") log.Println("aName :", aName.String()) // 獲取 extra 的值 extra := gjson.Get(json, "extra") log.Println("extra:", extra) // 獲取 一個不存在的 鍵 對應的 值 non := gjson.Get(json, "non") log.Println("non:", non) // 一次性 獲取json 的多個鍵 值 res := gjson.GetMany(json, "author.age", "author.hobby","picList") for i, v := range res{ if i == 0{ log.Println(v.Int()) }else if i == 2{ for _,vv := range v.Array(){ log.Println("picList.name :",vv.Get("name")) } }else{ log.Println(v) } } }
運行上述代碼後,能夠看到以下效果:
2021/06/xx xx:32:04 main.go:28: json valid ... 2021/06/xx xx:32:04 main.go:35: aName : xiaomotong 2021/06/xx xx:32:04 main.go:39: extra: hello wolrd 2021/06/xx xx:32:04 main.go:43: non: 2021/06/xx xx:32:04 main.go:50: 18 2021/06/xx xx:32:04 main.go:57: writing 2021/06/xx xx:32:04 main.go:53: picList.name : xiaozhu1 2021/06/xx xx:32:04 main.go:53: picList.name : xiaozhu2
咱們須要注意,要把咱們的數據源弄對,也就是我們的json
數據必須是合法的,不然,使用gjson
庫拿到的數據就不會是我們指望的值
gjson.Get()
,獲取單個值gjson.GetMany()
,獲取多個值gjson.Valid()
,判斷json
數據是否有效gjson
的 json
行再來看看 json 行
gjson
提供以下語法,來解析json 行
數據:
輸出 json
行數組的長度
輸出 json
每一行 裏面的 author
對應的值,組成一個數組
..#(author="xiaomotong").hobby
輸出輸出 json 行 中,author = xiaomotong 所在行 對應的 hobby 值
輸出 json
行 數組的第 2 行 , 如果 ..2
則輸出第 3 行
json 行
使用 gjson.ForEachLine
遍歷json 行
的每一行數據,每一行數據裏面的細節也能遍歷出來
我們寫一個DEMO 來覆蓋一下上面須要用到的語法:
package main import ( "github.com/tidwall/gjson" "log" ) const json = ` {"author": "xiaomotong", "age": 18, "hobby":"play"} {"author": "xiaozhu", "age": 19 , "hobby":"eat"} {"author": "zhangsan", "age": 20, "hobby":"drink"} {"author": "lisi", "age": 21, "hobby":"sleep"}` func main() { // 設置參數,打印行數 log.SetFlags(log.Lshortfile | log.LstdFlags) // 輸出 json 行數組的長度 log.Println(gjson.Get(json, "..#")) // 輸出 json 行 數組的第 3 行 log.Println(gjson.Get(json, "..2")) // 輸出 json 每一行 裏面的 author 對應的值,組成一個數組 log.Println(gjson.Get(json, "..#.author")) // 輸出輸出 json 行 中,author = xiaomotong 所在行 對應的 hobby 值 log.Println(gjson.Get(json, `..#(author="xiaomotong").hobby`)) // 遍歷 json 行 gjson.ForEachLine(json, func(jLine gjson.Result) bool { log.Println("author:", gjson.Get(jLine.String(), "hobby")) return true }) }
上述代碼運行以後結果以下:
2021/06/xx xx:17:52 main2.go:20: 4 2021/06/xx xx:17:52 main2.go:22: {"author": "zhangsan", "age": 20, "hobby":"drink"} 2021/06/xx xx:17:52 main2.go:24: ["xiaomotong","xiaozhu","zhangsan","lisi"] 2021/06/xx xx:17:52 main2.go:26: play 2021/06/xx xx:17:52 main2.go:30: author: play 2021/06/xx xx:17:52 main2.go:30: author: eat 2021/06/xx xx:17:52 main2.go:30: author: drink 2021/06/xx xx:17:52 main2.go:30: author: sleep
我們來看看函數 gjson.ForEachLine
的實現方式:
// ForEachLine iterates through lines of JSON as specified by the JSON Lines // format (http://jsonlines.org/). // Each line is returned as a GJSON Result. func ForEachLine(json string, iterator func(line Result) bool) { var res Result var i int for { i, res, _ = parseAny(json, i, true) if !res.Exists() { break } if !iterator(res) { return } } }
每一行都會返回一個 JSON
的結果, Result
parseAny
是解析每一行的具體json
數據 , parseAny
函數裏面就會很詳細的涉及到 如何判斷處理每個字符
// parseAny parses the next value from a json string. // A Result is returned when the hit param is set. // The return values are (i int, res Result, ok bool) func parseAny(json string, i int, hit bool) (int, Result, bool) { var res Result var val string // 一個字符一個字符的作處理 // 不一樣的字符 有對應的邏輯,感興趣的XDM 能夠細品 for ; i < len(json); i++ { if json[i] == '{' || json[i] == '[' { i, val = parseSquash(json, i) if hit { // 對應字符賦值 res.Raw = val res.Type = JSON } return i, res, true } if json[i] <= ' ' { continue } // 排除上述特殊幾種狀況後,繼續按照下述狀況進行處理字符 switch json[i] { case '"': i++ var vesc bool var ok bool i, val, vesc, ok = parseString(json, i) if !ok { return i, res, false } if hit { res.Type = String res.Raw = val if vesc { res.Str = unescape(val[1 : len(val)-1]) } else { res.Str = val[1 : len(val)-1] } } return i, res, true case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': i, val = parseNumber(json, i) if hit { res.Raw = val res.Type = Number res.Num, _ = strconv.ParseFloat(val, 64) } return i, res, true case 't', 'f', 'n': vc := json[i] i, val = parseLiteral(json, i) if hit { res.Raw = val switch vc { case 't': res.Type = True case 'f': res.Type = False } return i, res, true } } } return i, res, false }
咱們來看看 Result
的具體數據結構
// Result represents a json value that is returned from Get(). type Result struct { // Type is the json type Type Type // Raw is the raw json Raw string // Str is the json string Str string // Num is the json number Num float64 // Index of raw value in original json, zero means index unknown Index int }
根據 Type Type
的不一樣,對應到 Str string
,Num float64
,Index int
裏面數據的不一樣
此處的Type
我們來看看都有哪些狀況
const ( // Null is a null json value Null Type = iota // False is a json false boolean False // Number is json number Number // String is a json string String // True is a json true boolean True // JSON is a raw block of JSON JSON )
這樣看起來就一目瞭然了吧,仍是對於gjson
庫解析對應字符想再深刻研究的話,能夠下載 gjson
,看看gjson.go
源碼文件裏面的具體實現
gjson
鍵路徑的匹配規則鍵路徑是什麼?
就是以 .
分隔的鍵 , 我們列個表格看看 gjson
都支持哪些匹配規則
tag | 說明 |
---|---|
? | 匹配單個字符,例如hell? 就可以匹配 hello 鍵,匹配不了 helloo |
* | 匹配任意多個字符,例如hell* 能夠匹配 hello , helloooo , 均可以 |
xx.xx | 用於匹配數組,例如 hello 是一個數組, 那麼 hello.0 就是匹配數組第 1 個元素hello.1 就是匹配的 2 個元素 |
xx.# | 獲取數組的長度,例如 hello.# |
若鍵名裏面出現了 . ,那麼須要用\ 進行轉義 |
這個也好理解, 例如 鍵名字就叫 hello.world ,此時須要使用這個鍵的時候,就須要這樣來轉義 hello\.world |
==、!=、<、<=、>、>= | 例如 hello 是一個組數,數組裏面有元素字段是 name 和 age 我們匹配的時候,能夠加 # 來靈活匹配咱們想要的數據例如: hello.#(name="xiaozhu").age |
% | 模式匹配 , 例如 hello.#(name%"n*").age |
!% | 模式匹配 , 例如 hello.#(name!%"n*").age |
我們一塊兒來使用一下上述的匹配規則:
package main import ( "github.com/tidwall/gjson" "log" ) // json 源 數據 const json = ` { "author":{"name":"xiaomotong", "nick": "xiaozhu"}, "age": 18, "hobby": ["play", "eat", "drink"], "love.music": "one day", "location": [ {"province": "gd", "city":"gz", "area": "huangpu"}, {"province": "gd", "city":"sz", "area": "nanshan"}, ] } ` func main() { // 設置參數,打印行數 log.SetFlags(log.Lshortfile | log.LstdFlags) // 獲取名字 log.Println("author:", gjson.Get(json, "author.name")) // 獲取年齡 log.Println("age:", gjson.Get(json, "age")) // 使用 ? # *操做 hobby log.Println("hobby:", gjson.Get(json, "hobb?")) log.Println("hobby count:", gjson.Get(json, "hobby.#")) log.Println("second hobby:", gjson.Get(json, "ho?by.1")) log.Println("third hobby:", gjson.Get(json, "ho*.2")) // 鍵中 帶有 . 咱們用 \. 來轉義 log.Println("love.music", gjson.Get(json, `love\.music`)) // 獲取數組裏面的元素 ,若咱們須要獲取location數組第一個元素裏面的 city ,咱們能夠這樣 gjson.Get(json, "location.0.city") log.Println("location first city :", gjson.Get(json, "location.0")) log.Println("location second city :", gjson.Get(json, "location.1")) }
上述代碼運行結果以下:
2021/06/xx xx:03:26 main3.go:27: author: xiaomotong 2021/06/xx xx:03:26 main3.go:29: age: 18 2021/06/xx xx:03:26 main3.go:31: hobby: ["play", "eat", "drink"] 2021/06/xx xx:03:26 main3.go:32: hobby count: 3 2021/06/xx xx:03:26 main3.go:34: second hobby: eat 2021/06/xx xx:03:26 main3.go:35: third hobby: drink 2021/06/xx xx:03:26 main3.go:37: love.music one day 2021/06/xx xx:03:26 main3.go:39: location first city : {"province": "gd", "city":"gz", "area": "huangpu"} 2021/06/xx xx:03:26 main3.go:40: location second city : {"province": "gd", "city":"sz", "area": "nanshan"}
gjson
庫裏面的各類規則,不難,咱們能夠看看其中的用法,本身實操找一下,記憶會更加深入一些,到時候真正用到了,再來查一遍,就不生疏了
gjson
的 修飾符 和 自定義修飾符最後我們再來講說 gjson
庫裏面的修飾符 , 修飾符的功能也是很強大的,通常是和鍵地址一塊兒玩
我們先整理一下內置的修飾符都有哪些:
tag | 說明 |
---|---|
@reverse | 翻轉一個數組 |
@ugly | 移除JSON 中的全部空白符 |
@valid | 校驗 JSON 的合法性 |
@pretty | 使 JSON 更易用閱讀 |
@flatten | 數組平坦化,即將["小豬1", ["小豬2", "小豬3"]]轉爲["小豬1","小豬2","小豬3"] |
@this | 返回當前的元素,能夠用來返回根元素 |
@join | 將多個對象合併到一個對象中 |
繼續上DEMO
package main import ( "github.com/tidwall/gjson" "log" ) const json = ` { "author":{"name":"xiaomotong", "nick": "xiaozhu"}, "age": 18, "hobby": ["play", "eat", "drink"], "love.music": "one day", "location": [ {"province": "gd", "city":"gz", "area": "huangpu"}, {"province": "gd", "city":"sz", "area": "nanshan"}, ] } ` func main() { // 設置參數,打印行數 log.SetFlags(log.Lshortfile | log.LstdFlags) // 翻轉 hobby 數組 log.Println("翻轉 hobby 數組 hobby reverse:", gjson.Get(json, "hobby|@reverse")) // 移除空白符 log.Println("移除空白符 location.0:", gjson.Get(json, "location.0|@ugly")) // 使json 更加容易閱讀 pretty log.Println("使json 更加容易閱讀 pretty location : ", gjson.Get(json, "location.1|@pretty")) // 輸出整個json log.Println(" 輸出整個json this : ", gjson.Get(json, "@this")) test := `["小豬1", ["小豬2", "小豬3"]]` // 扁平化 log.Println("扁平化 this : ", gjson.Get(test, "@flatten")) }
運行上述代碼,咱們能夠看到以下效果:
2021/06/xx xx:30:24 main4.go:27: 翻轉 hobby 數組 hobby reverse: ["drink","eat","play"] 2021/06/xx xx:30:24 main4.go:29: 移除空白符 location.0: {"province":"gd","city":"gz","area":"huangpu"} 2021/06/xx xx:30:24 main4.go:32: 使json 更加容易閱讀 pretty location : { "province": "gd", "city": "sz", "area": "nanshan" } 2021/06/xx xx:30:24 main4.go:34: 輸出整個json this : { "author":{"name":"xiaomotong", "nick": "xiaozhu"}, "age": 18, "hobby": ["play", "eat", "drink"], "love.music": "one day", "location": [ {"province": "gd", "city":"gz", "area": "huangpu"}, {"province": "gd", "city":"sz", "area": "nanshan"}, ] } 2021/06/xx xx:30:24 main4.go:39: 扁平化 this : ["小豬1","小豬2", "小豬3"]
哈哈,有沒有以爲很簡單嘞,
我們還能夠自定義修飾符哦,一塊兒來瞅瞅
使用函數 gjson.AddModifier
,添加咱們自定義修飾符的實現方式,具體能夠看看下面這個
func main() { gjson.AddModifier("myTo", func(json, arg string) string { // 將字符串修改成大寫 if arg == "title" { return strings.ToTitle(json) } return json }) const json = `{"children": ["hello", "world", "xiaomotong"]}` fmt.Println(gjson.Get(json, "children|@myTo:title")) }
結果以下:
["HELLO", "WORLD", "XIAOMOTONG"]
AddModifier
將自定義修飾命令綁定到GJSON
語法,這個操做不是線程安全的,應該在使用全部其餘gjson
函數以前執行。
// AddModifier binds a custom modifier command to the GJSON syntax. // This operation is not thread safe and should be executed prior to // using all other gjson function. func AddModifier(name string, fn func(json, arg string) string) { modifiers[name] = fn }
json
與 gjson
分別表明什麼gjson
的簡單使用gjson
校驗,獲取值gjson
的 json 行
gjson
的鍵路徑匹配規則gjson
的修飾符和自定義修飾符朋友們,你的支持和鼓勵,是我堅持分享,提升質量的動力
好了,本次就到這裏,下一次 GO 權限管理之 Casbin
技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~