[TOC]

GO中gjson的利用和分享

咱们上次分享到应用 GO 爬取动态网页的数据,一起来回顾一下

  • 分享动态网页和动静网页的简要阐明
  • GO 爬取动态网页简略数据
  • GO 爬取网页上的图片
  • 并发爬取网页上的资源

要是对 GO 爬取静态数据还有点趣味的话,欢送查看文章 分享一波 GO 的爬虫

json 是什么?

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格局

它基于 ECMAScript (欧洲计算机协会制订的 JS 标准)的一个子集,采纳齐全独立于编程语言的文本格式来存储和示意数据

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 mainimport (   "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 : xiaomotong2021/06/xx xx:32:04 main.go:39: extra: hello wolrd2021/06/xx xx:32:04 main.go:43: non:2021/06/xx xx:32:04 main.go:50: 182021/06/xx xx:32:04 main.go:57: writing2021/06/xx xx:32:04 main.go:53: picList.name : xiaozhu12021/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 mainimport (   "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: 42021/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: play2021/06/xx xx:17:52 main2.go:30: author: play2021/06/xx xx:17:52 main2.go:30: author: eat2021/06/xx xx:17:52 main2.go:30: author: drink2021/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 mainimport (   "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: xiaomotong2021/06/xx xx:03:26 main3.go:29: age: 182021/06/xx xx:03:26 main3.go:31: hobby: ["play", "eat", "drink"]2021/06/xx xx:03:26 main3.go:32: hobby count: 32021/06/xx xx:03:26 main3.go:34: second hobby: eat2021/06/xx xx:03:26 main3.go:35: third hobby: drink2021/06/xx xx:03:26 main3.go:37: love.music one day2021/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 mainimport (    "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

技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。

我是小魔童哪吒,欢送点赞关注珍藏,下次见~