wiki | https://en.wikipedia.org/wiki/Jq_(programming_language) |
source (C) | https://github.com/jqlang/jq/ |
basic manual | https://jqlang.github.io/jq/manual/ |
home | https://jqlang.github.io/jq/ |
repl online | https://jqplay.org/ |
repl | https://github.com/gpanders/ijq |
awesome | https://github.com/fiatjaf/awesome-jq |
rust rewrite | https://github.com/01mf02/jaq |
- 2012
- Author: Stephen Dolan
- reads ~/.jq
- jq binary is 33k
- first written in Haskell, but early rewritten in C for performance (?)
cli
jq [OPTIONS] FILTER [INPUTFILES]
<c> | description | |
---|---|---|
-n | null-input | do NOT read input (to quickly test scripts) |
-s | slurp | reads whole STDIN as an array, runs script once |
-c | compat-output | minimizes output |
unbuffered | flush output after each json object is printed | |
-C | color-output | |
-M | monochrome-output | |
-f F | from-file FILE | read .jq program from FILE |
slurpfile VAR FILE | reads json FILE into array variable $VAR | |
rawfile VAR FILE | reads FILE raw into variable $VAR |
language
#!/usr/bin/jq -f
- variables are "lexically scoped bindings"
- jq-mode
- bug: wrong string interpolation can have (") inside (")
- bug: wrong identation constructing objects
filters / functions
- all have an input, and an output
- manual plumbing (passing of 1st argument) is NOT necessary
- eg: "add / length" both filters get the same input
- the body consists of a SINGLE expression
- single pass compiler (define a function before use it)
- arity function overloading
- has TCO for /0 arity functions ONLY
def funcname: expression; # takes stdin implicitly def funcname(args) expression; # takes an extra argument explicitly def A: def B(n): n + 1; # nested function definition B(10);
function arguments are filters
(not values)
- they act like what in other languages are
callbacks
the filter expression gets passed into the function body as is
def add_mul(adder;multiplier): (. + adder) * multiplier; add_mul(. + 5; . - 2); # => 200 # (. + . + 5) * . - 2 # 25 * 8
function arguments as values (syntatic sugar)
def my_func(arg): arg as $arg | #... ; def my_func($arg): #.... ;
recursion
def count_occurrences(x): if length == 0 then # base 0 elif first == x then # recursive (1 + (.[1:] | count_occurrences(x))) else # recursive (.[1:] | count_occurrences(x)) end; [11,22,33,22,44] | count_occurrences(22) # => 22
types
- booleans
- falsy values: null, false
type | example |
---|---|
number | 42, 3.14, 1e6, nan, infinite |
string | "hello" |
boolean | true, false |
array | [1, "2", {"foo": "bar"}] |
object | {"foo": 2} |
null | null |
empty |
modules
import "MODNAME" as MODNAME; def main: MODNAME::FUNCTION; main
def FUNCTION: split(", ") ;
operators
description | |
---|---|
+ | addition, concatenation([]), merge({}) |
- | subtraction |
¦ | pipe operator |
¦= | update operator (used for += -= *= /= %= //=) |
, | operator to join multiple streams |
() | to group subexpressions |
[] | constructor |
{foo: .} | constructor |
+ | string concatenation |
+ | arrays append |
+ | objects merge |
A // B | if A then A else B end (alternative operator ) |
? | error suppresion, optional operator , shorthand for try |
?// | destructuring alternative operator |
.[]? | does NOT error when input is NOT an object or array |
.foo? | does NOT error when input is NOT an object |
generators?
1 | while(.<30; .*2) | [1,2,4,8,16] | |
1 | repeat(.*2; error)? | [2] | repeats filter until error is raised |
4 | [.,1]¦until(.[0] < 1; [.[0]-1, .[1]*.[0]])¦.[1] | 24 | |
recurse??? |
format & escape strings
@text | just calls tostring |
@json | serializes input as JSON |
@html | applies HTML/XML escaping |
@uri | applies percent encoding |
@csv | rendered as CSV with double quotes |
@tsv | rendered as TSV |
@sh | escaped suitable for POSIX shell |
@base64 | as specified by RFC 4648 |
stdlib functions
general
description | |
---|---|
debug | like (.) but it prints to stderr too |
range(TO) | |
range(FROM;TO) | |
range(FROM;TO;BY) | produces a stream of numbers |
empty | returns empty |
input | outputs 1 NEW input |
inputs | outputs all remaining inputs, one by one |
halt | exit |
halt_error/0 | exit AND prints input |
halt_error/1 | exit with given code AND prints input |
error/0 | returns an error, can be catched (try/catch) |
error/1 | with message given |
casting
[1,"1"] | tonumber | 1,1 | |
[1,"1",[1]] | tostring | "1","1","[1]" | |
0 | type | "number" |
date
1425599507 | todate | "2015-03-05T23:51:47Z" |
"2015-03-05T23:51:47Z" | fromdate | 1425599507 |
"2015-03-05T23:51:47Z" | strptime("%Y-%m-%dT%H:%M:%SZ") | [2015,2,5,23,51,47,4,63] |
"2015-03-05T23:51:47Z" | strptime("%Y-%m-%dT%H:%M:%SZ")¦mktime | 1425599507 |
- | now | 1716057777.153488 |
1425599507 | strftime("%H:%M:%S") | "23:51:47" |
stream
86, 99, 13 | . + 1 | 87, 100, 14 | |
86, 99, 13 | [ . + 1 ] | [87], [100], [14] |
regex
- https://jqlang.github.io/jq/manual/v1.7/#regular-expressions
- compatible with Perl v5.8 regexes
- uses oniguruma implementation https://github.com/kkos/oniguruma/blob/6fa38f4084b448592888ed9ee43c6e90a46b5f5c/doc/RE
- as strings the backslash for classes needs to be escaped "\\d" for characters
- \n \t \r \f \b \u123f
- flags
- g: global search, find all matches
- i: case insensitive search
- m: multiline mode
- s: single line mode
- p: both "s" and "m" are enabled
- n: ignore empty matches
- l: find largest possible match
- x: extend regex format, ignores whitespaces and comments (#)
input | filter | output | description |
---|---|---|---|
"Hello World!" | test("W") | true | to know if a substring matches the pattern |
"Goodbye Mars" | test("W") | false | |
test(REGEX;FLAGS) | |||
test([REGEX,FLAGS]) | |||
match([REGEX,FLAGS]) | |||
"Hello World!" | match("([aeiou])\\1") | empty | to extract the substring that matched |
"Goodbye Mars" | match("([aeiou])\\1") | {"offset": 1, "length": 2, "string": "oo" | |
,"captures": [ | |||
{"offset": 1 | |||
,"length": 1 | |||
,"string": "o" | |||
,"name": null }]} | |||
"Goodbye Mars" | match("[aeiou]";"g") | {"offset":1,"length":1,"string":"o","captures":[]} | |
{"offset":2,"length":1,"string":"o","captures":[]} | |||
{"offset":6,"length":1,"string":"e","captures":[]} | |||
{"offset":9,"length":1,"string":"a","captures":[]} | |||
"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)") | {"project":"JIRAISSUE","issue_num":"1234"} | object of named captures |
capture(REGEX) | |||
capture(REGEX;FLAGS) | |||
capture([REGEX,FLAGS]) | |||
"Goodbye Mars" | scan("[aeiou]") | "o", "o", "e", "a" | only substrings, like match(RE,"g") |
"Goodbye Mars" | [scan("[aeiou]")] | ["o", "o", "e", "a"] | |
"first second" | split("\\s+"; "") | ["first","second"] | |
split(REGEX; FLAGS) | |||
sub(REGEX; REPLACEMENT) | |||
sub(REGEX; REPLACEMENT; FLAGS) | |||
gsub(REGEX; REPLACEMENT) | |||
gsub(REGEX; REPLACEMENT; FLAGS) | |||
"this: gnu, csv" | gsub("\\b(?<tla>[[:alpha:]{3})\\b") | "this: GNU, CSV" | |
; "\(.tla ¦ ascii_upcase)") |
array
in | filter | out |
---|---|---|
{foo:[1],bar:[2]} | .foo + .bar | [1,2] |
{foo:[1,2],bar:[2,3]} | .foo - .bar | [1] |
[2,4,6,8][] | 2,4,6,8 | |
[2,4,6,8] | .[] | 2,4,6,8 |
[2,4,6,8] | . + [1] | [2,4,6,8,1] |
[2,4,6,8] | [ .[] + 1 ] | [3,5,7,9] |
[2,4,6,8] | .[] + 1 | 3,5,7,9 |
[2,4,6,8] | .[1] | 4 |
[2,4,6,8] | .[1+1] | 6 |
[2,4,6,8] | .[1:2] | [4] |
[2,4,6,8] | limit(2;.[]) | 2,4 |
[2,4,6,8] | first | 2 |
[2,4,6,8] | last | 8 |
[2,4,6,8] | length | 4 |
[2,4,6,8] | indices(8) | [3] |
[2,4,6,8] | contains([2]) | true |
[2,4,6,8] | index(6) | 2 |
[2,2,4,6,8,4] | unique | [2,4,6,8] |
[{"foo":1},{"foo":1}] | unique_by(.foo) | [{"foo":1}] |
["foo","bar","bazinga"] | unique_by(length) | ["foo","bazinga"] |
[2,4,6,8] | reverse | [8,6,4,2] |
[8,4,6,2] | sort | [2,4,6,8] |
[2,4,6,8] | min | 2 |
[2,4,6,8] | max | 8 |
[2,4,6,8] | add | 20 |
["foo","bar"] | add | "foobar" |
[{foo: 1, bar: 2}] | add | {foo: 1, bar: 2} |
[72,101,108,108,111,33] | implode | "Hello!" |
[2,4,6,8] | nth(2) | 6 |
{name: "Jane", age: 21} | map(.age += 1) | {name: "Jane", age: 22} |
[2,4,6,8] | map(. * 10) | [20,40,60,80] |
[2,4,6,8] | [ .[] ¦ . * 10 ] | equivalent to map() |
["foo","bar"] | add // "" | "foobar" |
["foo","bar"] | join(",") | "foo,bar" |
{name: "Jane", age: 1} | select(.age > 18) | [] |
[2,[],3,[4],5] | flatten | [2,3,4,5] |
[true,false] | any | true |
[true,false] | all | false |
[1,2,3,4] | any(. >= 4) | true |
[1,2,3,4] | all(. >= 4) | false |
[1] | to_entries | [{key:0,value:1}] |
[10,20,30,40] | keys | [0,1,2,3] |
[2,4] | has(1) | true |
[2,4] | has(4) | false |
1 | in([0,23]) | true |
23 | in([0,23]) | false |
[10,20] | as [$foo,$bar] | (empty, destructuring) |
reduce stream as $var (init;fn) | ||
[10,20,30,40] | reduce .[] as $n (0; . + $n) | 100 |
["A","B","C","D"] | reduce .[] as $e ([]; [$e] + .) | ["D","C","B","A"] |
- in the reduce fn
- (.) is the accumulator
- if you need to reduce the input, store it in a variable
string
being "Hello!" the INPUT
filter | out | description |
---|---|---|
ascii_downcase | "hello!" | |
ascii_upcase | "HELLO!" | |
ltrimstr("Hell") | "o!" | |
rtrimstr("Hell") | "Hello!" | |
/ "l" | ["He","","o!"] | |
split("l") | ["He","","o!"] | |
explode | [72,101,108,108,111,33] | splits into codepoints |
implode | ||
startswith("!") | false | |
endswith("!") | true | |
test("He.*") | true | supports regex |
contains("!") | true | |
inside("Hi, Hello!") | true | inverse of contains/1 |
length | 6 | |
index("el") | 1 | position, otherwise null |
+ "bar" | "Hello!bar" | |
* 3 | "Hello!Hello!Hello" | |
* 0 | "" |
math
https://jqlang.github.io/jq/manual/v1.7/#math
- 1-input:
- acos acosh asin asinh atan atanh cbrt ceil cos cosh erf erfc exp exp10 exp2 expm1 fabs floor gamma j0 j1 lgamma log log10 log1p log2 logb nearbyint pow10 rint round significand sin sinh sqrt tan tanh tgamma trunc y0 y1
- pipe the input to the function
- 1 | atan
- 2-input:
- atan2 copysign drem fdim fmax fmin fmod frexp hypot jn ldexp modf nextafter nexttoward pow remainder scalb scalbln yn
- they ignore input
- uses (;) to separate parameters
- pow(2;10)
- 3-input:
- fma
object
in | filter | out | |
---|---|---|---|
{"a": 1, "b": 2, "c": 3} | .[] | 1,2,3 | stream of values |
{"a": 1, "b": 2, "c": 3} | {a,c} | {"a": 1, "c": 3} | |
{a: {foo: 1}, b: {bar: 2}} | .a + .b | {foo: 1, bar: 2} | |
{} | .a | null | *projection, dot notation |
{"a":1 , "b":2} | .a | 1 | |
{"a":1 , "b":2} | . + {c: 3} | {a: 1, b: 2, c: 3} | |
{"a":1 , "b":2} | .["a"] | 1 | |
{"a":1 , "b":2} | "foo" | "foo" | |
{"a":1 , "b":2} | .a = 100 | {a: 100, b: 2} | create/update property |
{"a":1 , "b":2} | .a ¦= . + 100 | {a: 101, b: 2} | |
{"a":1 , "b":2} | {foo: .a} | {foo: 1} | new obj, old prop |
{"name": "john"} | as {name: $n} ¦ $n | "john" | as object destructuring |
{"name": "john"} | as {$name} ¦ $name | "john" | as object destructuring short |
{"a": 1, "b": 2, "c": 3} | flatten | [1,2,3] | array of values |
{"a": 1, "b": 2, "c": 3} | keys | ["a","b","c"] | array of keys |
{"a": 1, "b": 2, "c": 3} | keys_unsorted | ["a","c","b"] | array of keys |
{"a": 1, "b": 2, "c": 3} | has("a") | true | |
"a" | in({"a": 2}) | true | |
{"a": 1, "b": 2, "c": 3} | add | 6 | adds values |
{"a": 1, "b": 2, "c": 3} | del(.a) | {"b":2, "c":3} | |
{"a": 1, "b": 2, "c": 3} | to_entries | [{"key":"a","value":1},…] | |
[{"key":"a","value":1}] | from_entries | {"a":1} | |
{"Jane": 42} | with_entries({key:(.value¦tostring),value:.key}) | {"42": "Jane"} | |
[{foo:1},{foo:2}] | group_by(.foo) | [[{foo:1}],[{foo:2}]] | |
{"first": "jane"} | .[] ¦= ascii_upcase | {first: "JANE"} | |
{"first": "jane"} | map_values(ascii_upcase) | {first: "JANE"} | equivalent to above |
- with_entries(filter), is equivalent to: to_entries | map(filter) | from_entries
snippets
- Get 5 random emails $ curl -s "https://randomuser.me/api/1.2/?results=5&seed=dsatcl2e" | jq -r '.results[].email'
filter | description |
---|---|
.foo? | |
.[] ¦ {msg: .commit.msg, name: .commit.commiter.name} | builds a new json |
path(..) ¦ map(tostring) ¦ join("/") | instant schema |
.parse.categories[].name | the contents of each field's "name" |
.["parse"] ¦ .["categories"] ¦ .[] ¦ .["name"] | equivalent code using pipelines |
in | filter | out | description |
---|---|---|---|
.. | recursive identity, from self to values | ||
42 | . | 42 | "identity" |
99 | 42 | 42 | "constant" |
5 | . * 2, . + 3, . / 5 | 10, 8, 1 | multiple selectors? |
"color" | {(.): "red"} | {color: "red"} | |
{"k":1,"v":[8,9]} | .v[1] | 9 | |
{"k":1,"v":[8,9]} | .v[ .["k"] ] | 9 | |
[range(10)] | map(select(. % 2 == 0)) | [0,2,4,6,8] | |
[range(10) ¦ | select(. % 2 == 0) ] | [0,2,4,6,8] | |
[{}, true, {"a":1}] | .[] ¦ .a? | null, 1 | optional operator |
["1","invalid",4] | .[] ¦ tonumber? | 1, 4 | |
[86, 99, 13] | length as $count | 66 | declaring variables with as |
¦ add / $count | |||
{"size": 34.6 | if ((.size¦floor)%2) == 0 | "even" | 1.6 - must have an ELSE |
,"weight": 24.1} | then "even" | 1.7 - ELSE defaults to . | |
else "odd" | |||
end | |||
{"name":"John" | "\(.name), Agent \(.nr)" | "John, Agent 86" | string interpolation |
,"nr":"86"} | |||
{foo: 1} {bar: 2} | [inputs] | [{foo: 1}, {bar: 2}] | streams to array |