Start
Getting started
A query is a sequence of tokens read left to right. Each token narrows what came before it, so a query describes a path through the document.
Keys are always double-quoted, because any string is a valid JSON key â including .valid, the empty string, and a lone quote. That means a query needs single quotes in a shell, so the double quotes survive.
echo '{ "name": "jql" }' | jql '"name"'
Read a file by passing it after the query, or pipe into it:
jql '"name"' package.json cat package.json | jql '"name"'
Wrap the whole query in single quotes. Without them your shell eats the double quotes around each key, and the query will not parse.
Reference
The grammar
Every token, with an example you can paste into a shell. Each output below was produced by running jql 9.0.3 against the input beside it.
Object keys
The key selector is the one you will use most. Write selectors next to each other to walk down the document.
Select a key
A key selector is always double-quoted, so the query must be wrapped in single quotes in a shell.
jql '"name"'{ "name": "jql", "stars": 1337 }
"jql"Walk down nested keys
Selectors sit next to each other with no separator. Each one descends one level.
jql '"repo""owner""login"'{ "repo": { "owner": { "login": "yamafaktory" }, "private": false } }
"yamafaktory"Keys that need escaping
A key carrying a quote or a backslash is written the way JSON writes it. The full escape set is supported: \" \\ \/ \b \f \n \r \t and \uXXXX.
jql '"a\"b"'{ "a\"b": 1, "plain": 2 }
1An empty key
The empty string is a valid JSON key, so it is a valid selector.
jql '""'{ "": "empty keys are valid JSON" }
"empty keys are valid JSON"A key that does not exist
The error names the key and shows the object it was looked for in.
jql '"missing"'{ "name": "jql" }
Key "missing" doesn't exist in parent {"name":"jql"}Arrays
Arrays are addressed by index or by range. Both accept several values, in any order you like.
Select one index
Indexes are zero-based.
jql '[1]'["a", "b", "c", "d"]
"b"Select several indexes
Indexes are returned in the order you ask for them, not in the order they appear.
jql '[2,0]'["a", "b", "c", "d"]
[ "c", "a" ]
A range
Ranges are inclusive at both ends.
jql '[1:2]'["a", "b", "c", "d"]
[ "b", "c" ]
A reversed range
Writing the bounds backwards reverses the result. There is no separate reverse operator.
jql '[2:0]'["a", "b", "c", "d"]
[ "c", "b", "a" ]
An open range
Leave a bound out and it runs to the end. [:1] does the same from the start.
jql '[2:]'["a", "b", "c", "d"]
[ "c", "d" ]
The whole array
Both bounds omitted selects everything, which is useful as the last step of a pipe.
jql '[:]'["a", "b", "c"]
[ "a", "b", "c" ]
An index out of bounds
The error reports the index and the array it was applied to.
jql '[9]'["a", "b"]
Index 9 in parent ["a","b"] is out of bounds
Objects
Objects can be addressed by name, and also by position â useful when you know the shape but not the keys.
Several keys at once
A multi key selector keeps the order you wrote, so it doubles as a way to reorder an object.
jql '{"stars","name"}'{ "name": "jql", "stars": 1337, "forks": 42 }
{ "stars": 1337, "name": "jql" }
Select keys by position
An object index selector picks entries by position rather than by name.
jql '{1,0}'{ "name": "jql", "stars": 1337, "forks": 42 }
{ "stars": 1337, "name": "jql" }
A range over positions
Object ranges behave like array ranges, including the reversed and open forms.
jql '{0:1}'{ "name": "jql", "stars": 1337, "forks": 42 }
{ "name": "jql", "stars": 1337 }
A reversed object range
Same rule as arrays: bounds written backwards give a reversed result.
jql '{2:1}'{ "name": "jql", "stars": 1337, "forks": 42 }
{ "forks": 42, "stars": 1337 }
Operators
Four operators reshape what a selector returned rather than descending into it.
The keys operator returns keys sorted alphabetically, not in document order, and the truncate operator may appear only once, as the final token of a query.
Flatten nested arrays
The flatten operator collapses every level of nesting into a single array.
jql '..'[[[["a"]], "b"], [["c"]]]
[ "a", "b", "c" ]
Flatten an object
On an object, flatten joins the path with dots and keeps the leaf value.
jql '..'{ "a": { "b": { "c": 1 } }, "d": 2 }
{ "a.b.c": 1, "d": 2 }
List the keys of an object
The keys operator answers what is in here, which is the usual first move on an unfamiliar document. Keys come back sorted alphabetically, not in document order.
jql '@'{ "zebra": 1, "apple": 2, "mango": 3 }
[ "apple", "mango", "zebra" ]
List the indexes of an array
On an array the same operator returns indexes rather than keys.
jql '@'["a", "b", "c"]
[ 0, 1, 2 ]
Truncate to a primitive
Truncate maps the output to a bare primitive, turning a big array into [] and an object into {}. Useful to check a shape without printing it.
jql '"repos"!'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
[]
Truncate must come last
Only one truncate operator is allowed and it has to be the final token.
jql '"repos"!"name"'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
Truncate operator found as non last element or multiple times in Key Selector "repos", Truncate Operator, Key Selector "name"
Pipes
A pipe applies the tokens that follow to every element of an array, then optionally collapses the result back to a single value.
Apply a selector to every element
The pipe in operator applies everything that follows to each element of an array, collecting the results.
jql '"repos"|>"name"'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
[ "jql", "hypergraphz", "old-thing" ]
Pick several fields from every element
Anything that works on one element works inside a pipe, including multi key selectors.
jql '"repos"|>{"name","stars"}'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
[ { "name": "jql", "stars": 1337 }, { "name": "hypergraphz", "stars": 42 }, { "name": "old-thing", "stars": 3 } ]
Stop piping and select from the result
The pipe out operator ends the iteration, so the tokens after it apply to the collected array as a whole.
jql '"repos"|>"name"<|[0]'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
"jql"Reach into nested elements
After a pipe out you can range, index or slice the collected array like any other array.
jql '"repos"|>"name"<|[2:1]'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
[ "old-thing", "hypergraphz" ]
Lenses
A lens filters an array, keeping the elements that match. It is the only token that removes elements rather than selecting them.
Keep elements that have a key
A lens with no value keeps every element where the selector resolves, regardless of what it holds.
jql '|={"archived"}'[{ "name": "jql", "archived": false }, { "name": "note" }]
[ { "name": "jql", "archived": false } ]
Filter on a value
Give the lens a value and it keeps only the elements matching it. Values can be a boolean, null, a number or a string.
jql '|={"lang"="Rust"}'[{ "name": "jql", "lang": "Rust" }, { "name": "hypergraphz", "lang": "Zig" }]
[ { "name": "jql", "lang": "Rust" } ]
Filter on a nested value
A lens takes a whole path, not just one key.
jql '|={"owner""login"="yamafaktory"}'[{ "owner": { "login": "yamafaktory" } }, { "owner": { "login": "someone" } }]
[ { "owner": { "login": "yamafaktory" } } ]
Several conditions
Conditions separated by commas act as OR: an element is kept when any of them matches.
jql '|={"lang"="Rust","archived"=true}'[{ "lang": "Rust", "archived": false }, { "lang": "Zig", "archived": true }, { "lang": "Rust", "archived": true }]
[ { "lang": "Rust", "archived": false }, { "lang": "Zig", "archived": true }, { "lang": "Rust", "archived": true } ]
Filter on a boolean
Booleans and null are written bare, without quotes.
jql '|={"archived"=false}'[{ "name": "jql", "archived": false }, { "name": "old", "archived": true }]
[ { "name": "jql", "archived": false } ]
Groups
A comma splits a query into sub-queries, each run against the same input, collected into an array.
Build an array from sub-queries
A group separator runs each sub-query against the same input and collects the results into an array. Unlike a multi key selector it keeps no keys.
jql '"name","stars"'{ "name": "jql", "stars": 1337, "forks": 42 }
[ "jql", 1337 ]
Combine unrelated paths
Each side of the comma is a full query, so groups reach into completely different parts of a document.
jql '"repos"[0]"name","repos"[1]"lang"'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
[ "jql", "Zig" ]
Group whole pipelines
Sub-queries may contain pipes, ranges, anything. The group is the outermost structure.
jql '"repos"|>"name"<|[0],"repos"|>"stars"'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
[ "jql", [ 1337, 42, 3 ] ]
Putting it together
Real queries chain these pieces, and reading one from left to right tells you the whole pipeline. Every example below runs against this document:
{ "meta": { "total": 3, "page": 1 }, "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false, "owner": { "login": "yamafaktory", "id": 1 }, "topics": ["cli", "json"] }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false, "owner": { "login": "yamafaktory", "id": 1 }, "topics": ["graph"] }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true, "owner": { "login": "someone", "id": 2 }, "topics": [] } ] }
Filter, then project
A lens narrows the array, then a pipe projects each survivor. This is the shape most real queries take.
jql '"repos"|={"lang"="Rust"}|>"name"'[ "jql", "old-thing" ]
Filter on a nested value
The lens path reaches into each element, so you can filter on something nested and still project from the top.
jql '"repos"|={"owner""login"="yamafaktory"}|>"name"'[ "jql", "hypergraphz" ]
Project several fields at once
A multi key selector inside a pipe reshapes every element, which is how you trim a wide API response down to what you need.
jql '"repos"|={"archived"=false}|>{"name","stars"}'[ { "name": "jql", "stars": 1337 }, { "name": "hypergraphz", "stars": 42 } ]
Filter, project, take the first
Pipe out ends the iteration, so the index applies to the collected array rather than to each element.
jql '"repos"|={"archived"=false}|>"owner""login"<|[0]'"yamafaktory"Slice the piped result
Anything that works on an array works after a pipe out, including reversed ranges.
jql '"repos"|>"name"<|[2:1]'[ "old-thing", "hypergraphz" ]
Combine two unrelated pipelines
A group separator runs each side against the same document, so a summary field and a filtered list arrive together in one array.
jql '"meta""total","repos"|={"lang"="Rust"}|>"name"'[ 3, [ "jql", "old-thing" ] ]
Reach through a range into a pipe
Select a slice first and the pipe applies only to it, which is cheaper than piping the whole array and slicing after.
jql '"repos"[0:1]|>"owner""login"'[ "yamafaktory", "yamafaktory" ]
Keys of every element
The keys operator inside a pipe reports the shape of each element. Useful when elements are not uniform.
jql '"repos"|>@<|[0]'[ "archived", "lang", "name", "owner", "stars", "topics" ]
Flatten a nested selection
Piping collects an array of arrays; flatten then collapses it into one flat list. Flatten only unnests arrays â objects inside an array are left whole.
jql '"repos"|>"topics"<|..'[ "cli", "json", "graph" ]
Filter on a number
Lens values are not limited to strings. Here a number picks one element, and the projection keeps a nested object whole.
jql '"repos"|={"stars"=1337}|>{"name","owner"}'[ { "name": "jql", "owner": { "login": "yamafaktory", "id": 1 } } ]
Mix array and object addressing
An array index picks the element, then an object range takes its first two entries by position. Selectors compose regardless of which kind of container they address.
jql '"repos"[0]{0:1}'{ "name": "jql", "lang": "Rust" }
Using it
Many documents
Input may hold more than one JSON document. Each is evaluated on its own and produces its own result.
Several documents at once
Concatenated documents, with or without whitespace between them, are each evaluated and produce one result apiece.
jql '"a"'{ "a": 1 }{ "a": 2 }{ "a": 3 }
1 2 3
Documents separated by newlines
Newline-delimited JSON works the same way, which is what most log pipelines produce.
jql '"name"'{ "name": "first" } { "name": "second" }
"first" "second"
Anything trailing that is not itself a document is an error rather than being ignored, so a truncated stream fails loudly instead of returning partial results.
Flags
Eight flags, all short. The full list is in jql --help.
| Flag | What it does |
|---|---|
-i, --inline | Print the output on one line instead of pretty-printing it. |
-q, --query <FILE> | Read the query from a file rather than the command line. |
-r, --raw-string | Print a string result without its surrounding quotes. |
-S, --sort-keys | Sort the keys of every object recursively, like jq -S. |
-s, --stream | Process input line by line as it arrives, one document per line. |
-v, --validate | Ignore the query and report whether the input is valid JSON. |
-h, --help | Print help, including the whole grammar. |
-V, --version | Print the version. |
Inline the output
Output is pretty-printed by default. Use -i or --inline for a single line, which is what you want when piping into another tool.
jql --inline '"repos"[0]'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
{"name":"jql","lang":"Rust","stars":1337,"archived":false}
Drop the quotes around a string
With -r or --raw-string a string result is printed bare, so it can be assigned to a shell variable.
jql --raw-string '"repos"[0]"name"'{ "repos": [ { "name": "jql", "lang": "Rust", "stars": 1337, "archived": false }, { "name": "hypergraphz", "lang": "Zig", "stars": 42, "archived": false }, { "name": "old-thing", "lang": "Rust", "stars": 3, "archived": true } ] }
jql
Sort every object key
-S or --sort-keys sorts keys recursively, matching jq's -S. Handy for diffing two documents.
jql --sort-keys --inline '"repo"'{ "repo": { "name": "jql", "archived": false, "lang": "Rust" } }
{"archived":false,"lang":"Rust","name":"jql"}
Check that input is valid JSON
-v or --validate ignores the query and reports whether the input parses, setting the exit code accordingly.
jql --validate ''{ "name": "jql" }
Valid JSON file or content
Shell integration
jql eats JSON and writes JSON back, so it composes with everything else in a pipeline.
Save the output
jql '"repos"' input.json > output.json
Assign a value to a variable
Combine --raw-string with --inline so the result arrives without quotes or newlines:
name=$(jql -r -i '"name"' package.json)
Keep the query in a file
Long queries are easier to read, and to version, when they live on disk:
jql --query ./select-repos.jql input.json
Follow a stream
Without --stream the whole input is read before anything is written, which is right for a file or a finished pipe. With it, each line is processed as it arrives â for output still being produced:
docker logs --follow my-container | jql --stream '"message"'
It expects one document per line. A document spread over several lines needs the default mode.
Check exit codes
jql --validate input.json && echo "valid"
Installation
Packaged for most platforms, or grab a prebuilt binary.
| Platform | Command |
|---|---|
| Cargo | cargo install jql |
| Cargo Binstall | cargo binstall jql |
| Alpine Linux | apk add jql |
| Arch Linux | yay -S jql |
| Fedora | dnf install jql |
| FreeBSD | pkg install jql |
| Homebrew | brew install jql |
| Nix | nix-env -i jql |
| openSUSE | zypper install jql |
Prebuilt binaries for Linux, macOS and Windows are attached to every release.
Good to know
Errors say what went wrong
A failed selection reports the token that failed and the value it was applied to, rather than returning null and leaving you to guess.
It is not jq
There is no plan to align jql with jq or any similar tool. jql selects and reshapes; it has no expression language, no arithmetic and no user-defined functions, and that is deliberate.
Speed
Selection queries run against a simd-json tape, so only the part of the document a query actually selects is ever built. Measurements against jq live in PERFORMANCE.md.
Two JSON parsers are in play, and they round the last bit of some scientific-notation literals differently, by up to two ULP. The tape returns the correctly rounded value. Plain decimals, integers and strings are unaffected.