1.  
  2. 主页
  3.  / 
  4. Go 每日一库
  5.  / 
  6. Go 每日一库之 gjson 使用详解

Go 每日一库之 gjson 使用详解

简介

之前我们介绍过gojsonq,可以方便地从一个 JSON 串中读取值。同时它也支持各种查询、汇总统计等功能。今天我们再介绍一个类似的库gjson。在上一篇文章Go 每日一库之 buntdb中我们介绍过 JSON 索引,内部实现其实就是使用gjson这个库。gjson实际上是get + json的缩写,用于读取 JSON 串,同样的还有一个sjsonset + json)库用来设置 JSON 串。

快速使用

先安装:

后使用:

使用很简单,只需要传入 JSON 串和要读取的键路径即可。注意一点细节,因为gjson.Get()函数实际上返回的是gjson.Result类型,我们要调用其相应的方法进行转换对应的类型。如上面的String()Int()方法。

如果是直接打印输出,其实可以省略String()fmt包的大部分函数都可以对实现fmt.Stringer接口的类型调用String()方法。

键路径

键路径实际上是以.分隔的一系列键。gjson支持在键中包含通配符*?*匹配任意多个字符,?匹配单个字符,例如ca*可以匹配cat/cate/cake等以ca开头的键,ca?只能匹配cat/cap等以ca开头且后面只有一个字符的键。

数组使用键名 + . + 索引(索引从 0 开始)的方式读取元素,如果键pets对应的值是一个数组,那么pets.0读取数组的第一个元素,pets.1读取第二个元素。

数组长度使用键名 + . + #获取,例如pets.#返回数组pets的长度。

如果键名中出现.,那么需要使用\进行转义。

前 3 个比较简单,就不赘述了。看后面几个:

  • children.#:返回数组children的长度;
  • children.1:读取数组children的第 2 个元素(注意索引从 0 开始);
  • child*.2:首先child*匹配children.2读取第 3 个元素;
  • c?ildren.0c?ildren匹配到children.0读取第一个元素;
  • fav.\moive:因为键名中含有.,故需要\转义;
  • friends.#.first:如果数组后#后还有内容,则以后面的路径读取数组中的每个元素,返回一个新的数组。所以该查询返回的数组所有friendsfirst字段组成;
  • friends.1.last:读取friends第 2 个元素的last字段。

运行结果:

对于数组,gjson还支持按条件查询元素,#(条件)返回第一个满足条件的元素,#(条件)#返回所有满足条件的元素。括号内的条件可以有==!=<<=>>=,还有简单的模式匹配%(符合某个模式),!%(不符合某个模式):

还是使用上面的 JSON 串。

  • friends.#(last="Murphy").firstfriends.#(last="Murphy")返回数组friends中第一个lastMurphy的元素,.first表示取出该元素的first字段返回;
  • friends.#(last="Murphy")#.firstfriends.#(last="Murphy")#返回数组friends中所有的lastMurphy的元素,然后读取它们的first字段放在一个数组中返回。注意与上面一个的区别;
  • friends.#(age>45)#.lastfriends.#(age>45)#返回数组friends中所有年龄大于 45 的元素,然后读取它们的last字段返回;
  • friends.#(first%"D*").lastfriends.#(first%"D*")返回数组friends中第一个first字段满足模式D*的元素,取出其last字段返回;
  • friends.#(first!%"D*").last`friends.#(first!%"D*")返回数组friends中第一个first字段满足模式D*的元素,读取其last字段返回;
  • friends.#(nets.#(=="fb"))#.first:这是个嵌套条件,friends.#(nets.#(=="fb"))#返回数组friends的元素的nets字段中有fb的所有元素,然后取出first字段返回。

运行结果:

修饰符

修饰符gjson提供的非常强大的功能,和键路径搭配使用。gjson提供了一些内置的修饰符:

  • @reverse:翻转一个数组;
  • @ugly:移除 JSON 中的所有空白符;
  • @pretty:使 JSON 更易用阅读;
  • @this:返回当前的元素,可以用来返回根元素;
  • @valid:校验 JSON 的合法性;
  • @flatten:数组平坦化,即将["a", ["b", "c"]]转为["a","b","c"]
  • @join:将多个对象合并到一个对象中。

修饰符的语法和管道类似,以|分隔键路径和分隔符。

children|@reverse先读取数组children,然后使用修饰符@reverse翻转之后返回,输出:

children|@reverse|0在上面翻转的基础上读取第一个元素,即原数组的最后一个元素,输出:

friends|@ugly移除friends数组中的所有空白字符,返回一行长长的字符串:

friends|@pretty格式化friends数组,使之更易读:

@this返回原始的 JSON 串。

@flatten将数组nested的内层数组平坦到外层后返回,即将所有内层数组的元素依次添加到外层数组后面并移除内层数组,输出:

@join将一个数组中的各个对象合并到一个中,例子中将数组中存放的部分个人信息合并成一个对象返回:

修饰符参数

修饰符还可以有参数,通过在修饰符后加:后跟参数。如果我们在格式化 JSON 串时,想要对键进行排序,那么可以使用@pretty修饰符的sortKeys参数。我们还是拿上面的 JSON 数据举例:

最终按键名顺序输出 JSON 串:

当然还可以指定每行缩进indent(默认两个空格),每行开头字符串prefix(默认为空串)和一行最多显示字符数width(默认 80 字符)。下面在每行前增加两个空格:

自定义修饰符

如此强大的功当然要支持自定义!gjson使用AddModifier()添加一个修饰符,传入一个名字和类型为func(json arg string) string的处理函数。处理函数接受待处理的 JSON 值和修饰符参数,返回处理后的结果。下面编写一个转换大小写的修饰符:

输出:

JSON 行

gjson提供..语法可以将多行数据看成一个数组,每行数据是一个元素:

  • ..#:返回有多少行 JSON 数据;
  • ..1:返回第一行,即{"name": "Gilbert", "age": 61}
  • ..#.name#后再接路径,表示对数组中每个元素读取后面的路径,将读取到的值组成一个新数组返回;..#.name表示读取每一行中的name字段,最终返回["Gilbert","Alexa","May","Deloise"]
  • ..#(name="May").age:括号中的内容(name="May")表示条件,所以该条含义为取name"May"的行中的age字段。

gjson还提供了遍历 JSON 行的方法:gjson.ForEachLine(),参数为 JSON 串和类型为func(line gjson.Result) bool的回调函数。回调返回false时遍历停止。下面代码读取输出每一行的name字段:

遍历

上面我们介绍了遍历 JSON 行的方式,实际上gjson还提供了通用的遍历数组和对象的方式。gjson.Get()方法返回一个gjson.Result类型的对象,json.Result提供了ForEach()方法用于遍历。该方法接受一个类型为func (key, value gjson.Result) bool的回调函数。遍历对象时keyvalue分别为对象的键和值;遍历数组时,value为数组元素,key为空(不是索引)。回调返回false时,遍历停止。

校验 JSON

调用gjson.Get()时,gjson假设我们传入的 JSON 串是合法的。如果 JSON 非法也不会panic,这时会返回不确定的结果:

上面 JSON 串是非法的,djage都没有加上双引号(实际上习惯了 Go 语言map的写法,很容易把 JSON 写成这样。)。上面代码输出18,显然是错误的。我们可以使用gjson.Valid()检测 JSON 串是否合法:

一次获取多个值

调用gjson.Get()一次只能读取一个值,多次调用又比较麻烦,gjson提供了GetMany()可以一次读取多个值,返回一个数组[]gjson.Result

上面代码返回字段nameage、数组pets的长度和contact.phone字段。

总结

gjson使用比较方便,功能强大,性能可观,值得一学。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue。

参考

  1. gjson GitHub:https://github.com/tidwall/gjson
  2. Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib

这篇文章对您有用吗?

我们要如何帮助您?

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注