[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
的简略应用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
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
技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。
我是 小魔童哪吒,欢送点赞关注珍藏,下次见~