分析 Json 格式接口参数或返回值。
jq 基本用法 从Github 找到最新的版本,比如 1.6,下载:
1 wget -O jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
下面是示例数据test.json
,后续例子都是基于这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 { "data": { "userInfo": { "name": "Mike" }, "range": [ { "time": "20190823", "type": "AM" }, { "time": "20190823", "type": "PM" }, { "time": "20190824", "type": "PM" } ], "one2many": [ { "id": "1", "list": [ { "name": "abc", "pwd": "123" }, { "name": "Mike", "pwd": "456" } ] } ] } }
过滤器(Filter) jq 的执行是基于“过滤器”的概念的,由过滤器来决定如何将数据聚合、转换或排除。
jq operates by way of filters: a series of text commands that you can string together, and which dictate how jq should transform the JSON you give it.
过滤器包括一些操作符(operator):
dot:.
.
保持输入不变,如果输入是一个复杂对象(被{}
包围),则可以在.
后面带上对应的 key 来获取其值。.
leaves the input unmodified. Add the name of a key to it, however, and the filter will return the value of that key.
array operator:[]
.
会直接返回一个大的 json 数组,如果要访问数组里的每一项,需要在对象后加上[]
。 当然如果要访问数组中的某一项,可以通过向数组里传索引来获取,比如[1]
。
pipe:|
The magic of jq is that you can connect, or pipe, several operators together to accomplish some very complex transformations of your data. What’s more, jq will repeat the filter for each JSON object provided by the previous step. Therefore, while we started with just one big JSON object, .artObjects[]
created 10 smaller JSON objects. Any operator we put after the | will be repeated for each of these objects.
create new json:[]
、{}
用于将结果合成为新的数组或对象。
一些函数过滤器:
select 过滤掉一部分结果到下一步,一般和|
组合使用。
group_by 输入数组,将某个字段相同的对象整合到同一个子数组内,这么说起来有点抽象,下面是一个简单例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [ "a", "b", "a" ] $ ./jq -r 'group_by(.)' test1.json -> [ [ "a", "a" ], [ "b" ] ]
csv 用于将数组结果转换为 csv 格式输出。
下面是一个最简单的例子:
1 ./jq -r '.data.range[] | {time: .time, type: .type}' test.json
但是需要注意的是,不能想当然地认为 jq 可以为我们处理复杂的关系,比如下面的命令结果是 4 个对象而不是 2 个:
1 ./jq -r '.data.one2many[] | {id: .id, name: .list[].name, pwd: .list[].pwd}' test.json
如果只获取 one2many 数组里的一个属性则可以正常输出,下面的语句输出 2 个对象:
1 ./jq -r '.data.one2many[] | {id: .id, name: .list[].name}' test.json
费了蛮大功夫在教程里找这种情况的解决方案,未发现jq
对 one2many 关系的查询有很好的支持,只能通过编写 shell 脚本一层一层解析 json 了,我将示例放到下面的应用中了。
select select 是一种过滤器函数,用于选出结果集合中的部分:
1 ./jq -r '.data.one2many[].list[] | select(.name == "abc")' test.json
group_by 按某个字段分组,比如希望将同一天的数据放到一块,统计每一天有多少个 type:
1 ./jq -r '.data.range | group_by(.time) | .[] | {time: .[0].time, type: [.[].type | tostring] | join(";")}' test.json
也可以用于去重并统计出现次数,下面的脚本用于统计每一天有多少个不同的 type:
1 2 3 ./jq -r '.data.range | group_by(.time) | .[] | {time: .[0].time, type: [.[].type | tostring]} | {time: .time, count: .type | length} | [.time, .count] | @csv' test.json 下面这种写法也是可以的: ./jq -r '.data.range | group_by(.time) | .[] | {time: .[0].time, type: [.[].type | tostring]} | [.time, (.type | length)] | @csv' test.json
格式化 格式化输出:
csv 格式输出:
1 jq -r '.data.range[] | [.time, .type] | @csv' test.json
传参
–arg,传递一个字符串参数,注意不能传数字,下面的脚本执行时会报错: 1 2 $ ./jq -r --arg i 1 '.data.one2many.list[$i]' test.json jq: error (at test.json:30): Cannot index array with string "1"
–argjson,相对来说更自由一点,可以传数字或其他复杂的 json 结构: 1 $ ./jq -r --argjson i 1 '.data.one2many.list[$i]' test.json
应用 One-to-many 正如前面所述,jq
对 one2many 结构没有特别好的支持,如果需要获取更多的属性,则只能按 json 格式层层遍历,写出类似下面这样的脚本:
1 2 3 4 5 6 7 8 9 i=0 while true; do json=$(./jq --argjson i $i -r '.data.one2many[].list[$i]' test.json) if [[ "$json" == "" || "$json" == "null" ]]; then break fi i=$(($i+1)) echo $json done
也不方便使用select
之类的函数来过滤,因为多个属性组合的结果是笛卡尔积,下面表格中的 item2 和 item4 都是冗余的,但是因为不知道输出的规律,过滤起来比较麻烦(其实是按 json 中数组的顺序 dfs)。
字段
name
pwd
item1
name1
pwd1
item2
name1
pwd2
item3
name2
pwd1
item4
name2
pwd2
通过线上服务器接口统计数据的一份快照 下面是实际工作中基于jq
统计数据的一个脚本:
因为目标业务数据并没有入库,都是存到缓存里的,只能通过调对应的接口来获取数据当时的一份快照,虽不准确,但对评估需求有一定的参考意义。 具体统计的什么业务数据并不重要,只是在脚本编写上提供一个参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #!/usr/bin/env bash set -o errexit set -o nounset # 下载医院详情数据(包含科室) curl -s http://10.32.128.178:8080/product/wx/baseinfo?hosCode=H109471\&channel=wechat -o baseinfo.json # 过滤出所有科室编码 ./jq -r '.data.departDetails[].subDepartments[] | [.hosCode, .parentDepartCode, .departCode] | @csv' baseinfo.json > depts.txt # 下载每个科室的号源 while read line do # 匹配"hosCode","firstDeptCode","secondDeptCode",像H123,,这样的都会被忽略 if [[ $line =~ ^\"(.+)\",\"(.+)\",\"(.+)\"$ ]];then curl -s http://10.32.128.178:8080/door/product/info?hosCode=${BASH_REMATCH[1]}\&firstDeptCode=${BASH_REMATCH[2]}\&secondDeptCode=${BASH_REMATCH[3]} -o ${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}.json #else #echo "Not match" fi done < depts.txt # 遍历号源列表过滤出0元号源 echo > result.csv while read line do if [[ $line =~ ^\"(.+)\",\"(.+)\",\"(.+)\"$ ]];then # 1、一次性extract全部属性没有意义,因为filter时会计算所有属性的笛卡尔积 # 之后不管是用jq内置的unique_by还是awk、uniq都无法方便地得到正确结果(相当于找到矩阵的主对角线) # 按hosCode-firstDeptCode-secondDeptCode-date-showTimeType-doctorId求unique,但uniq这些命令只能过滤出第一条 # ./jq --arg hosCode ${BASH_REMATCH[1]} --arg firstDeptCode ${BASH_REMATCH[2]} --arg secondDeptCode ${BASH_REMATCH[3]} -r '.data.productRedis[] | {hosCode: $hosCode, firstDeptCode: $firstDeptCode, secondDeptCode: $secondDeptCode, date: .date, showTimeType: .metaProductItems[].showTimeType, doctorId: .metaProductItems[].doctorId, doctorName: .metaProductItems[].doctorName, sellPrice: .metaProductItems[].sellPrice, count: .metaProductItems[].count} | [.hosCode, .firstDeptCode, .secondDeptCode, .date, .doctorId] | @csv' ${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}.json >> products.txt # 2、硬核双重循环 echo "开始查询"${BASH_REMATCH[1]}"-"${BASH_REMATCH[2]}"-"${BASH_REMATCH[3]} i=0 while true do # 传递数值参数必须用--argjson,如果用--arg会被解析为字符串,就会报错 productJson=$(./jq --argjson i $i -r '.data.productRedis[$i]' ${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}.json) # 如果为空则退出 if [[ "$productJson" == "" || "$productJson" == "null" ]];then break fi i=$(($i+1)) # 每个product一个date date=$(echo $productJson | ./jq -r '.date') echo "开始查询"$date # 遍历所有items j=0 while true do productItemJson=$(echo $productJson | ./jq --argjson j $j -r '.metaProductItems[$j]') if [[ "$productItemJson" == "" || "$productItemJson" == "null" ]];then break fi # echo $productItemJson j=$(($j+1)) # 输出到result.csv文件 echo $productItemJson | ./jq --arg hosCode ${BASH_REMATCH[1]} --arg firstDeptCode ${BASH_REMATCH[2]} --arg secondDeptCode ${BASH_REMATCH[3]} --arg date $date \ -r 'select(.sellPrice == 0) | [$hosCode, $firstDeptCode, $secondDeptCode, $date, .showTimeType, .doctorId, .doctorName, .doctorTitle, .sellPrice, .count] | @csv' \ >> result.csv done done #else #echo "Not match" fi done < depts.txt
脚本运行完毕后会生成一大堆临时文件,随后需要清理一下:
1 2 3 4 5 6 7 8 9 # 清理 while read line do if [[ $line =~ ^\"(.+)\",\"(.+)\",\"(.+)\"$ ]];then rm ${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}.json fi done < depts.txt rm depts.txt rm baseinfo.json
参考
Reshaping JSON with jq
jq Manual (development version)
stedolan/jq - Cookbook