GO中 gjson 的应用和分享
source link: https://segmentfault.com/a/1190000040419679
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
[TOC]
GO中gjson
的应用和分享
咱们上次分享到使用 GO 爬取静态网页的数据,一起来回顾一下
- 分享静态网页和动态网页的简要说明
- GO 爬取静态网页简单数据
- GO 爬取网页上的图片
- 并发爬取网页上的资源
要是对 GO 爬取静态数据还有点兴趣的话,欢迎查看文章 分享一波 GO 的爬虫
json
是什么?
JSON
(JavaScript Object Notation,JS
对象简谱) 是一种轻量级的数据交换格式它基于 ECMAScript (欧洲计算机协会制定的
JS
规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据
json
有如下几个优势:
- 层次结构简洁清晰
- 易于阅读和编写
- 易于机器解析和生成
- 能够提升网络传输效率
简单列一下咱们常用的数据序列化的方式
是可扩展标记语言,是一种简单的数据存储语言
- 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
都支持哪些匹配规则
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
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK