命令行下解析JSON神器:jq

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 命令行下解析JSON神器:jq,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

概述

官网
GitHub
jqplay提供一个在线的jq应用,检验jq表达式的正确性。

jq,轻量级的命令行JSON解析器。

jq is like sed for JSON data – you can use it to slice and filter and map and transform structured data with the same ease that sed, awk, grep and friends let you play with text.

安装

参考:https://github.com/stedolan/jq/releases

Linux

当然也适用于Mac,编译安装命令:

git clone https://github.com/stedolan/jq.git
cd jq
autoreconf –i
./configure --disable-maintainer-mode
Make
sudo make install

Windows

下载地址

安装方法:TODO

使用

格式化:.
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.'

获取特定字段:.特定字段
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.status'

数组操作
获取数组中第一个元素的currentCity字段
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.results[0].currentCity'

重新组合
.results[]:返回results数组所有数据;|:the output of one filter into the input of another,类似管道符;{biaoti: .title, miaoshu: .des}将input中.title,.des做为biaoti,miaoshu的value。
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.results[] | .index[] | {biaoti: .title, miaoshu: .des}'

,获取多个结果
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.error, .date'

长度
根据input判断长度,字符串,数组,对象的长度
JSON对象:
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.| length'

JSON数组
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.results[] | .index | length'
字符串
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '.status | length'

keys
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq '. | keys' [ "date", "error", "results", "status"]

-S 根据key排序输出
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq -S 'keys'[ "date", "error", "results", "status"]
map(foo)
invoke filter foo for each input
echo ‘[1,5,3,0,7]’ | jq ‘map(.+11)’[ 12, 16, 14, 11, 18]
select(foo)
返回满足条件的数据
echo ‘[1,5,3,0,7]’ | jq ‘map(select(.<2))’[ 1, 0]

if-then-else-end条件判断
curl 'http://api.map.baidu.com/telematics/v3/weather?location=shanghai&output=json&ak=W69oaDTCfuGwzNwmtVvgWfGH' -s | jq 'if .error==0 then "ok" elif .==1 then "false" else "null" end'

获取GitHub提交记录并另存为一个JSON Object对象:curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' |jq '.[0]' > only_one.json
获取commit-id:cat only_one.json | jq '.sha'
获取commit-msg:cat only_one.json | jq '.commit.message'

获取GitHub提交记录并另存为一个JSON Array数组:curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' > multi.json
获取commit-msg和commiter:cat multi.json | jq '.[] | {message: .commit.message, name: .commit.committer.name}'
格式化展示:jq '[.[] | {message: .commit.message, name: .commit.committer.name}]'

其可以接受标准输入,命令管道或者文件中的 JSON 数据,经过一系列的过滤器(filters)和表达式的转后形成需要的数据结构并将结果输出到标准输出中。

jq 可以处理 JSON 文件,也可以直接处理从命令行管道或者流中传入的数据。
jq -r '.' xxx.JSON cat xxx.JSON|jq -r '.'
jq只能接受 well form 的 JSON 字符串作为输入内容,即输入内容必须严格遵循 JSON 格式的标准。所有的属性名必须是以双引号包括的字符串。对象的最后一个属性的末尾或者数组的最后一个元素的末尾不能有逗号。否则抛出无法解析 JSON 的错误。

几个比较常用和重要的选项

  • -r:控制 jq 是输出 raw 格式内容或 JSON 格式内容。所谓的 JSON 格式是指符合 JSON 标准的格式。假设我们要查询 JSON 字符串{“name”:“tom”}中 name 的值. 使用-r 选项时返回的是’tom’. 不使用-r 选项时,返回的是’“tom”’.返回值多了一对双引号。
    s:jq 可以同时处理空格分割的多个 JSON 字符串输入。默认情况下,jq 会将 filter 分别对每个 JSON 输入应用,并返回结果。使用-s 选项,jq 会将所有的 JSON 输入放入一个数组中并在这个数组上使用 filter。”-s”选项不但影响到 filter 的写法。如果在 filter 中需要对数据进行选择和映射,其还会影响最终结果。
  • --arg。jq 通过该选项提供和宿主脚本语言交互的能力。该选项将值(v)绑定到一个变量(a)上。在后面的 filter 中可以直接通过变量引用这个值。例如,filter ‘.$a’表示查询属性名称等于变量 a 的值的属性。

jq 表达式
在调用 jq 处理 JSON 数据时有一个必须的部分”jq filters”。jq 内部构建一个简易的,功能完备的语言系统。在使用 jq 时,需要使用 jq 支持的语法来构建表达式(filters)并将其传给 jq。 jq 根据语法规则解析表达式并应用在输入的 JSON 数据上从而得到需要的结果。
jq 表达式支持串行化操作。一个复杂的表达式可以有多个简单的,以”|”符号分割的,串行化执行的表达式组成。每个表达式以其前边表达式的结果为输入。有JSON数据{"name":{"firstname":"Tom","lastname":"Clancy"}}。要查询 lastname 属性可以使用表达式.name|.lastname。jq特性:
jq 内建对 JSON 标准中各种数据类型的支持
jq 内建多种操作符和函数来进行数据的选择和转换。
jq 支持自定义函数和模块化系统。可以自定义自己的函数库,并在 jq 表达式中引用。

基础表达式

Basic filters,jq提供的基本过滤器,用来访问 JSON 对象中的属性,实现更复杂查询功能的基础:

  • .符号:表示对作为表达式输入的整个 JSON 对象的引用。
    JSON 对象操作。jq 提供两种基本表达式用来访问 JSON 对象的属性:..?。正常情况下,这两个表达式的行为相同:都是访问对象属性,如果 JSON 对象不包含指定的属性则返回 null。区别在于,当输入不是 JSON 对象或数组时,第一个表达式会抛出异常。第二个表达式无任何输出。
  • 数组操作。jq提供三种基础表达式来操作数组:
    迭代器操作.[],该表达式的输入可以是数组或者 JSON 对象。输出的是基于数组元素或者 JSON 对象属性值的 iterator。
    访问特定元素的操作(’.[index]‘或’.[attributename]’)。用来访问数组元素或者 JSON对象的属性值。输出是单个值
    数组切片操作.[startindex:endindex],其行为类似于 python 语言中数组切片操作。
    表达式操作(’,’和 ‘|’)。表达式操作是用来关联多个基础表达式。其中逗号表示对同一个输入应用多个表达式。管道符表示将前一个表达式的输出用作后一个表达式的输入。当前一个表达式产生的结果是迭代器时,会将迭代器中的每一个值用作后一个表达式的输入从而形成新的表达式。例如.[]|.+1表达式中,.[]在输入数组上构建迭代器,.+1则在迭代器的每个元素上加 1。

内置运算支持

jq 内部支持的数据类型有:数字,字符串,数组和对象(object)。并且在这些数据类型的基础上, jq 提供了一些基本的操作符来实现一些基本的运算和数据操作。列举如下:
数学运算。对于数字类型,jq实现基本的加减乘除(/)和求余(%)运算。对于除法运算,jq 最多支持 16 位小数。

字符串操作
连接操作,运算符为+,复制操作(例如:'a'*3结果为’aaa’),分割操作(将字符串按照指定的分割符分成数组,"sas"/"s"的结果为["","a",""],而"sas"/"a"的结果为["s","s"]
数组操作。jq 提供两种数组运算:并集(’+’)运算,结果数组中包含参与运算的数组的所有元素。差集运算(’-’),例如:有数组 a,b, a-b 的结果为所有在 a 中且不包含在 b 中的元素组成的数组。
对象操作。jq 实现两个 JSON 对象的合并操作(merge)。当两个参与运算的对象包含相同的属性时则保留运算符右侧对象的属性值。有两种合并运算符:+*。运算符+只做顶层属性的合并,*则是递归合并。例如:有对象a={"a":{"b":1}}b={"a":{"c":2}}a+b结果为{"a":{"c":2}},而a*b的结果为{"a":{"b":1,"c":2}}

比较操作:jq 内部支持的比较操作符有==, !=,>,>=,<=和<'=='的规则和JS中的恒等('===')类似,只有两个操作数的类型和值均相同时其结果才是true。
逻辑运算符:and/or/not。除false 和 null 外,其余的任何值都等同于 true。
默认操作符://,表达式a//b表示当表达式 a 的值不是 false 或 null 时,a//b 等于 a,否则等于 b。

当运算符的一个或两个操作数是迭代器时,其运算以类似与笛卡尔乘积的方式进行,即把两个操作数中的每一个元素拿出来分别运算:
jq -n '([1,2]|.[])+([4,6]|.[])' #result is 5 6 7 8

支持两种控制结构:

  1. 判断语句的完整结构为if then-elif then-else-end,当判断条件的结果为多个值时(迭代器),会对每个值执行一次判断。
  2. 异常处理语句的结构为try <表达式 a> catch <表达式 b>,当表达式 a 发生异常时,执行表达式 b,且输入为捕捉到的异常信息。如果不需要额外的处理,只是简单的抑制异常信息的输入,可以没有 catch 语句(如 try .a)。这时,整个表达式可以简写为’<表达式 a>?’(如:.a?)。

jq内部还支持函数。在使用 jq 函数时,注意区分两个概念:输入和参数。输入可能是整个表达式的输入数据也可能是表达式别的部分的输出。而参数和函数一起构成新的 filter 来处理输入。和其他编程语言不同的是,在调用函数时,多个参数之间以分号分隔。jq 通过内置函数提供数据处理时常用的操作,例如:过滤,映射,路径操作等。

映射操作
在数据处理过程中,经常有需求将数据从一种形式转换成另外一种形式,或者改变数据的值。jq提供两个内置映射函数来实现这种转换:map 和 map_values。map 处理的对象是数组,而 map_values 则处理对象属性的值。map 函数的参数为 filter 表达式。在该 filter 表达式中,’.’代表被映射的元素或值。
对输入的每个数组元素都+1:jq -r 'map(.+1)'

过滤操作
在 jq 中有两种类型的选择过滤操作:

  1. 基于数据类型的过滤,如表达式’.[]|arrays’的结果只包含数组(不包含方括号)。类型过滤器有:arrays, objects, iterables, booleans, numbers, normals, finites, strings, nulls, values, scalars。
  2. select 函数,接受一个条件表达式作为参数。其输入可以是迭代器,或者和 map 函数配合使用来处理数组。当输入中的某个元素使 select 参数中的条件表达式结果为真时,则在结果中保留该元素,否则不保留该元素。

输入:[1,2,3,4]
过滤:jq -r 'map(select(.>2))'
输出:[3,4]
jq -r '.[]|select(.>2)'
输出:3 4

路径操作
和 xpath 类似,jq 中的 path 也是指从根到某个叶子属性的访问路径。jq中有两种表示路径的方式:数组表示法和属性表示法。属性表示法类似于在 filter 中访问某个属性值的方式,如’.a.b’。数组表示法是将路径中的每一部分表示为数组的一个元素。jq 提供内置函数 path 用来实现路径从属性表示法到数组表示法的转换。
jq 还提供函数用来读取路径的值(getpath),设置路径的值(setpath)和删除路径(del)。不过这三个函数对路径的处理并不一致。前两者只接受数组表示法的路径,而 del 函数只能正确处理属性表示法的路径。
jq 还提供函数 paths 用来枚举可能存在的路径。在没有参数的情况下,paths 函数将输出 JSON 数据中所有可能的路径。paths函数可以接受一个过滤器,来只输出满足条件的路径。

存在判断函数
jq 中提供一系列的函数用来判断某个元素或属性是否存在于输入数据中。函数 has 和 in 用来判断 JSON 对象或数组是否包含特定的属性或索引。函数 contains 和 inside 用来判断参数是否完全包含在输入数据中。对于不同的数据类型,判断是否完全包含的规则不同。对于字符串,如果 A 是 B 的子字符串,则认为 A 完全包含于 B。对于对象类型,如果对象 A 的所有属性在对象 B 中都能找到且值相同,则认为 A 完全包含于 B。

数组函数
jq 提供内置函数用于完成数组的扁平化(flatten),反序(reverse),排序(sort, sort_by),比较(min, min_by, max, max_by)和查找(indices,index 和 rindex)。indices 函数的输入数据可以是数组、字符串。和 index 函数不同的是,其结果是一个包含所有参数在输入数据中位置的数组。
jq -nr '[1,[2,3],4]|flatten'
jq -nr '[1,2,3]|reverse'
jq -nr '[3,1,2]|sort'
jq -nr '[{"a":1},{"a":2}]|sort_by(.a)'
jq -nr '"abcb"|indices("b")'
jq -nr '[1,3,2,3]|indices(3)'

高级特性

jq 还提供许多内置函数,
变量
jq 内部支持两种变量的定义方式。第一种在前边 jq 的调用部分讲过,可以通过命令行参数(–arg)定义。这种方式用来从外部(如:shell)传入数据以供 filter 表达式使用。
第二种方式,在 jq 表达式内部,可以自己声明变量用来保存表达式的结果以供表达式其余部分使用。
jq 中定义变量的语句为: fiterexp as

v

a

r

i

a

b

l

e

n

a

m

e

variablename 变量

variablenamearraylen 用来保存数组长度,整个表达式结果为 4
jq -nr '[1,2,3]|length as $arraylen|$arraylen+1'
#可以同时定义多个变量
jq -nr '{"firstname":"tom","lastname":"clancy"}|. as {firstname:$fn, lastname:$ln}|"author is "+$fn+"*"+$ln'
jq 中同样存在变量作用域问题。jq中有两种方法分隔变量作用域。第一种是用括号包围部分表达式。括号内部的表达式与外部的表达式不在同一个作用域范围内。第二种方法是定义函数。默认情况下,声明的变量对其后的表达式可见。但是,如果变量在特定作用域内声明,则对作用域外部的表达式不可见,例如:
清单 8.变量作用域
#会抛出 arraylen 没定义的异常
jq -nr ‘[1,2,3]|(length as

a

r

r

a

y

l

e

n

arraylen|

arraylenarraylen)|$arraylen+1’
#正常执行,结果为 4.
jq -nr ‘[1,2,3]|(length as

a

r

r

a

y

l

e

n

arraylen|

arraylenarraylen+1)’
#函数作用域。该表达式会抛出异常,因为变量KaTeX parse error: Expected ‘EOF’, got ‘#’ at position 28: …e 中定义,对最后一个子表达式#̲#来说,fn 是不可见的。
jq -nr ‘{“firstname”:“tom”,“lastname”:“clancy”}|def fname:. as {firstname:

f

n

,

l

a

s

t

n

a

m

e

:

fn, lastname:

fn,lastname:ln}|

f

n

;

f

n

a

m

e

fn; fname|

fn;fnamefn’

Reduce
jq 有一种特殊的数据类型:迭代器。通常,有迭代器参与的运算,其结果也是一个迭代器。jq 提供一些特殊的语法和内置函数用来缩减迭代器运算结果的个数。
reduce 关键字用来通过运算将迭代器的所有值合并为一个值。其调用形式为:reduce as $var (INIT; UPDATE)。其中,表达式 itexp 产生的迭代器被赋值给变量 var, UPDATE 是关于变量 var 的表达式。INIT 是该表达式的初始输入。相对于 itexp 结果中的每个元素,UPDATE 表达式被调用一次,计算出结果用作下一次 UPDATE 调用的输入。
#结果是 6
jq -nr 'reduce ([1,2,3]|.[]) as $item (0; .+$item)'
等同于
jq -nr '0 | (3 as $item|.+$item)|(2 as $item | . + $item)|(1 as $item | . + $item)'

Foreach
作用和 reduce 类似,其调用形式为foreach EXP as $var (INIT; UPDATE; EXTRACT)。和 reduce 不同的是,foreach的每次迭代是先调用 UPDATE 再调用 EXTRACT,并以一个迭代器保留每一次的中间结果,该迭代器最后作为整个表达式的结果输出。
jq -nr 'foreach ([1,2,3]|.[]) as $item (0; .+$item;.)'
输出:1 3 6

内置函数
表达式limit(n;exp),用来取得表达式 exp 结果的前 n 个值。内置函数 first, last 和 nth,用来取迭代器中某一个特定的元素,除函数形式调用外,也可以作为子表达式调用。
jq -nr 'first([1,2,3]|.[])'
以 filter 形式调用 first:jq -nr '[1,2,3]|.[]|first'
结果为 2:jq -nr 'nth(1;[1,2,3]|.[])'

自定义函数和模块化
作为一个类似于编程语言的表达式系统,jq 也提供定义函数的能力。其语法规则为:def funcname(arguments) : funcbodyexp;
定义函数的规则:

  • 函数名或者参数列表后面应该跟冒号以标志函数体开始
  • 如果不需要参数,可以直接把整个参数列表部分省去
  • 参数列表中,参数之间以分号;分隔
  • 函数体只能是一个表达式,且表达式需以分号结尾
  • 如果在表达式内部定义函数,整个子表达式部分不能只包含函数定义,否则抛出语法错误
  • 在很多情况下,函数的参数都是被当作表达式引用的,类似于编程其他语言中的 callback 函数。

map 函数的源代码
def map(f): [.[] | f];

#下面表达式的结果是 20,因为当作参数传入的表达式在函数 foo 中被引用两次
5|def foo(f): f|f;foo(.*2)
如果希望传入的参数只被当作一个简单的值来使用,则需要把参数的值定义为一个同名变量,并按照使用变量的方式引用。

清单 13. 值参数
#下面表达式结果为 10,传入的表达式’.*2’在函数 foo 中首先被求值。
5|def foo(f): f as $f|$f|$f;foo(.*2)
#上面的表达式可以简写为如下形式,注意,引用参数时必须带

5

d

e

f

f

o

o

(

。 `5|def foo(

5deffoo(f):

f

f|

ff;foo(.*2)#否则等于直接引用参数中的表达式。 #例如下面的表达式结果为 205|def foo($f): $f|f;foo(.*2)函数内部可以定义子函数。利用这个特性可以实现递归函数。 递归函数实现数组求和 #下面表达式的结果是 15jq -nr ‘[1,2,3,4,5]|def total: def _t: .|first+(if length>1 then .[1:]|_t else 0 end); _t;total’`

除了在表达式内部定义函数外,可以把自定义函数写在外部文件中形成单独的类库。jq 有一套完整的模块系统来支持自定义类库:

  1. 可以通过命令行参数’-L’来指定 jq 搜索模块时需要搜索的路径。
  2. 在模块内部,可以通过 import 指令和 include 指令来实现互相引用。引用指令中的几个特殊的路径前缀:
    • :表示当前用户的 home 目录
    • $ORIGIN:表示 jq 命令的可执行文件所在的目录
    • .:表示当前目录,该前缀只能用在 include 指令中。

当通过 import 指令引用一个模块 foo/bar 时,jq会在搜素路径中查找foo/bar.jqfoo/bar/bar.jq

参考

https://testerhome.com/topics/4102
http://adaam.logdown.com/posts/239879
https://baijiahao.baidu.com/s?id=1653873327974562304

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/142192.html

(1)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!