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
的简单使用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
行数组的长度
- **…#.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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VnQNBfgg-1627475921604)(https://gitee.com/common_dev/mypic/raw/master/2DAB8338.jpg)]
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
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~