GO中 gjson 的應用和分享

[TOC]git

GO中gjson的應用和分享

我們上次分享到使用 GO 爬取靜態網頁的數據,一塊兒來回顧一下github

  • 分享靜態網頁和動態網頁的簡要說明
  • GO 爬取靜態網頁簡單數據
  • GO 爬取網頁上的圖片
  • 併發爬取網頁上的資源

要是對 GO 爬取靜態數據還有點興趣的話,歡迎查看文章 分享一波 GO 的爬蟲編程

json 是什麼?

JSON(JavaScript Object Notation, JS 對象簡譜) 是一種輕量級的數據交換格式json

它基於 ECMAScript (歐洲計算機協會制定的 JS 規範)的一個子集,採用徹底獨立於編程語言的文本格式來存儲和表示數據segmentfault

json 有以下幾個優點:數組

  • 層次結構簡潔清晰
  • 易於閱讀和編寫
  • 易於機器解析和生成
  • 可以提高網絡傳輸效率

簡單列一下我們經常使用的數據序列化的方式安全

  • json
  • xml
是可擴展標記語言,是一種簡單的數據存儲語言
  • protobuf
是一種 平臺無關、語言無關、可擴展且輕便高效的序列化數據結構的協議,能夠用於 網絡通訊 和 數據存儲

gjson 是什麼?

是 GO 裏面的一個庫網絡

它主要是提供了一種很是快速簡單的方式從json文檔中獲取相應值數據結構

這個 gjson庫,其實是 get + json的縮寫,無獨有偶,一樣的也有sjson庫,小夥伴們就知道他表明的含義了吧,是 set + json的意思併發

gjson 如何使用?

對於 gjson如何使用,XDM,我這裏把這個庫的基本使用,涉及到的知識點,以及注意事項,給你們梳理梳理

要是想看看 gjson的源碼是如何實現高效快速的操做json的,感興趣的朋友,能夠在先會引用的基礎上再去詳細查看一下源碼

本文的分享,圍繞以下 4 個方面來實操和梳理 gjson 的使用:

  • gjson 的簡單使用
  • gjsonjson
  • 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數據是否有效

gjsonjson

再來看看 json 行

gjson提供以下語法,來解析json 行 數據:

  • ..#

輸出 json 行數組的長度

  • ..#.author

輸出 json 每一行 裏面的 author 對應的值,組成一個數組

  • ..#(author="xiaomotong").hobby

    輸出輸出 json 行 中,author = xiaomotong 所在行 對應的 hobby 值

  • ..1

輸出 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 stringNum float64Index 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 是一個組數,數組裏面有元素字段是 nameage
我們匹配的時候,能夠加 #來靈活匹配咱們想要的數據
例如: 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
}

總結

  • 分享了 jsongjson分別表明什麼
  • gjson 的簡單使用
  • gjson 校驗,獲取值
  • gjsonjson 行
  • gjson的鍵路徑匹配規則
  • gjson的修飾符和自定義修飾符

歡迎點贊,關注,收藏

朋友們,你的支持和鼓勵,是我堅持分享,提升質量的動力

好了,本次就到這裏,下一次 GO 權限管理之 Casbin

技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

相關文章
相關標籤/搜索