Go gjson 使用说明|Go主题月

468 阅读4分钟

一句话来描述这个库:只要你知道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()

获取 jsonStrpath 路径对应的值,然后将 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

  1. name.first:基本是以 . 作为分隔
  2. children.## 返回数据长度
  3. children.2.index 返回index位置的ele【从 0 开始】
  4. child*.1/c?ildren.1:正则中使用的 */?
  5. friends.#.last:如果 # 还有元素,那就读取前面数组中每一个后面路径的元素【array.#.path
  6. friends.1.lastarray.index.path

同时支持条件查询(真有点 SQL 的感觉)

  1. friends.#(age>48)#.nestfriendsarg>48全部元素,返回他们下面的 nest 属性
  2. friends.#(age>48).nestfriendsarg>48第一个元素,返回这个元素的 nest 属性
  3. friends.#(nets.#(=="fb"))#.firstfriendsnest数组含有'fb'全部元素,返回这些元素的 first 属性

修饰符

path 选择以及管道搭配使用:

  1. children|@reverse|0:逆转数组紧接着返回第一个元素,Jack
  2. @pretty:美化 json
  3. @flatten:扁平化数组
  4. @this:话说上面的:@this.#.id,就可以获取匿名数组全部元素的 id
  5. @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,但是不见得就一定会慢,理性看待。)