一句话来描述这个库:只要你知道json的结构,并且你知道你要的数据在json中位置,就可以在不预先准备反序列化struct的情况下,得到你要的数据。
好吧!听起来有点绕口。那就看下面吧,哈哈哈哈😄。
需求
第一次接触这个库的时候,就是在做 抓取 grafana 面板SQL,然后做SQL分析提供给运营。需求很简单,而且 grafana 也提供了 HTTP API ,返回的也是 json。都很好。。。
但是我的获取路径是:dashboard -> panel folder -> single panel -> sqls(不止有一根线),这个层级和这个巨大json,从程序设计上,不可能构造一个和这个json层级一样的struct:
grafana后期发生API结构变更,需要花费很大精力维护一个struct- 构造一个这么大的struct,是真的很累
这也是我很少使用 go 写爬虫的原因,py 它不香嘛?
这次为什么要用 go ?因为最终 SQL 落库点是 clickhouse ,考虑到后期维护和库支持上,最后还是用 go。
交待完背景,开始说说使用过程中的一些感受。
API 介绍
几个方面:数组,嵌套json,等等吧。看代码 :hammer:
[
{
"id": 786,
"title": "trash",
"uri": "db/trash",
"url": "/trash",
"type": "dash-folder",
},
{
"id": 562,
"title": "一组",
"uri": "db/zu",
"url": "/zu",
"type": "dash-folder",
},
{
"id": 500,
"title": "推送结果",
"uri": "db/jie-guo-wu-he",
"url": "/jie-guo-wu-he",
"type": "dash-db",
"folderId": 609,
"folderUid": "***",
"folderTitle": "垃圾箱",
"folderUrl": "/dashboards/f/***/la-ji-xiang"
},
]
返回的就是 Array Json:
// 因为结构直接就是一个数组,所以直接 Parse -> Array() 转换成 array 就行
dashboards := gjson.Parse(body).Array()
for i := range dashboards {
if gjson.Get(dashboards[i].String(), "type").Str == "dash-folder" {
continue
}
boardFolderTitle := gjson.Get(dashboards[i].String(), "folderTitle")
boardFolderId := gjson.Get(dashboards[i].String(), "folderId")
panelId := gjson.Get(dashboards[i].String(), "uid")
panelTitle := gjson.Get(dashboards[i].String(), "title")
tPanel := &Panel{
DashboardTitle: panelTitle.Str,
FolderTitle: boardFolderTitle.Str,
DashboardId: panelId.Str,
FolderId: int(boardFolderId.Int()),
}
res = append(res, tPanel)
}
gjson.Get(jsonStr, "path").Str/String():
获取 jsonStr 中 path 路径对应的值,然后将 gjson 中内置的 Result 转换为 str
类似的还有:Int(),Bool() 反正基础类型都是可以转换的。
查询规则
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
{"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
{"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
],
"nested": ["one", "two", ["three", "four"]]
}
path
name.first:基本是以.作为分隔children.#:#返回数据长度children.2:.index返回index位置的ele【从 0 开始】child*.1/c?ildren.1:正则中使用的*/?friends.#.last:如果#还有元素,那就读取前面数组中每一个后面路径的元素【array.#.path】friends.1.last:array.index.path
同时支持条件查询(真有点 SQL 的感觉)
friends.#(age>48)#.nest:friends中arg>48的 全部元素,返回他们下面的nest属性friends.#(age>48).nest:friends中arg>48的 第一个元素,返回这个元素的nest属性friends.#(nets.#(=="fb"))#.first:friends中nest数组含有'fb'的 全部元素,返回这些元素的first属性
修饰符
和 path 选择以及管道搭配使用:
children|@reverse|0:逆转数组紧接着返回第一个元素,Jack@pretty:美化json@flatten:扁平化数组@this:话说上面的:@this.#.id,就可以获取匿名数组全部元素的id@join:将多个对象合并到一个对象中【适用于对象间没有重复键,会自动将结构合并,减少层级寻找】
遍历
最上面的例子给到了一个 gjson.Parse(body).Array(),获得数组然后遍历即可。gjson 提供 Foreach() 这种通用便利方式:
userJSON := `[{
"id": 786,
"title": "trash",
"uri": "db/trash",
"url": "/trash",
"type": "dash-folder",
},
{
"id": 562,
"title": "一组",
"uri": "db/zu",
"url": "/zu",
"type": "dash-folder",
}]`
gjson.Get(userJSON, "@this").ForEach(func(k, v gjson.Result) bool {
gjson.Get(v.String(), "@this").ForEach(func(_k, _v gjson.Result) bool {
fmt.Println(_k, _v)
return true
})
fmt.Println("===============")
return true
})
如果你 foreach 的对象是数组,那就只有 value ,只需要获取 value 就行。上述的结果就是:
id 786
title trash
uri db/trash
url /trash
type dash-folder
===============
id 562
title 一组
uri db/zu
url /zu
type dash-folder
===============
其实官方文档很清晰地列举了使用方式以及场景,以上我只是列举了我日常使用的方式和API。至于剩下的就需要大家去观看官方文档了。
下篇文章来聊聊 gjson 是怎么直接选取 json 属性的(先不考虑什么性能,盲猜使用了 reflect,但是不见得就一定会慢,理性看待。)