jq vs JSONPath: Which JSON Query Tool Should You Use?
Last updated:
jq and JSONPath both let you query JSON — but they serve different purposes. JSONPath selects nodes; jq transforms entire documents. This guide compares syntax, power, language support, and real-world use cases so you can pick the right tool for the job.
1. Syntax at a Glance
Given this document:
{
"store": {
"books": [
{ "title": "JavaScript: The Good Parts", "price": 12.99, "inStock": true },
{ "title": "You Don't Know JS", "price": 9.99, "inStock": false },
{ "title": "Eloquent JavaScript", "price": 14.99, "inStock": true }
]
}
}Common queries side by side:
| Task | JSONPath | jq |
|---|---|---|
| All book titles | $.store.books[*].title | .store.books[].title |
| First book | $.store.books[0] | .store.books[0] |
| Books under $13 | $.store.books[?(@.price < 13)] | .store.books[] | select(.price < 13) |
| Only title + price | Not directly (returns full objects) | .store.books[] | {title, price} |
| Total price | Not supported | [.store.books[].price] | add |
| In-stock titles only | $.store.books[?(@.inStock)].title | .store.books[] | select(.inStock) | .title |
2. JSONPath Deep Dive
JSONPath uses a dollar-sign root ($) and dot or bracket notation to navigate:
$ — root
$.foo — property access
$.foo.bar — nested access
$['foo bar'] — bracket notation (for keys with spaces)
$.books[0] — array index
$.books[-1] — last element (RFC 9535)
$.books[0,2] — union (index 0 and 2)
$.books[1:3] — slice (index 1, 2)
$.books[*] — all array elements
$..title — recursive descent: all "title" at any depth
$.books[?(@.price<10)] — filter expressionJSONPath always returns a nodelist — a list of matched values. An expression that matches nothing returns an empty list, never an error (per RFC 9535). This makes it safe to use in pipelines where a key may or may not exist.
3. jq Deep Dive
jq filters are chained with |. Each filter receives input and produces output:
. — identity (pass through unchanged)
.foo — property access
.foo.bar — nested
.foo? — optional (no error if missing)
.foo // "default" — alternative operator
.books[] — iterate array
.books[0] — index
.books[0:2] — slice
.books | length — array length
.books | map(.title) — transform each element
select(.price < 10) — filter (keeps matching items)
{title, price} — build new object (shorthand)
{title: .title, p: .price} — build with renamed keys
[.books[] | .price] | add — sum all prices
.books | group_by(.inStock) — group array
@base64 — encode as base64
@json — convert to JSON string
@csv — convert to CSV rowjq also supports def for reusable functions, if/then/else/end conditionals,try/catch error handling, reduce, label-break for early exit, andenv for environment variable access.
4. Transformation: Where jq Wins
The defining jq capability is reshaping data. Here are patterns JSONPath cannot express:
# Rename keys + add computed field
.books[] | {
name: .title,
discountedPrice: (.price * 0.9 | . * 100 | round / 100)
}
# Group by field and count
.books | group_by(.inStock) | map({
inStock: .[0].inStock,
count: length
})
# Flatten nested structure
.store | to_entries | map({key, value: .value.books[0].title})
# Merge two objects
{"new": "field"} + .existingObject
# Conditional transform
.books[] | if .price > 12 then . + {tier: "premium"} else . + {tier: "budget"} end5. JSONPath in Applications
JSONPath is the right choice when you need to integrate querying into an application. Native libraries avoid subprocess overhead and are easier to deploy:
# Python — jsonpath-ng (RFC 9535 compatible)
pip install jsonpath-ng
from jsonpath_ng import parse
expr = parse("$.store.books[?(@.price < 13)].title")
matches = [m.value for m in expr.find(data)]
# JavaScript — jsonpath-plus
npm install jsonpath-plus
import { JSONPath } from 'jsonpath-plus'
const titles = JSONPath({ path: '$.store.books[?(@.price<13)].title', json: data })
# Java — Jayway JsonPath
JsonPath.read(json, "$.store.books[?(@.price < 13)].title")JSONPath expressions are also first-class in several specs: AWS Step Functions uses JSONPath for input/output filtering; Kubernetes uses JSONPath in kubectloutput formatting; OpenAPI 3.1 uses JSON Pointer (a JSONPath subset) for $ref.
6. jq in Shell Pipelines and CI
jq excels in shell scripts and CI pipelines where you process JSON from APIs or CLI tools:
# Extract a field from curl response
curl -s https://api.example.com/releases/latest | jq -r '.tag_name'
# Pretty-print + filter from a log file
cat app.log | jq 'select(.level == "error") | {time, message}'
# Transform and write new JSON
jq '.items[] | {id, name}' input.json > output.json
# Update a field in-place
jq '.version = "2.0.0"' package.json
# Count errors by type
jq -s '[.[].type] | group_by(.) | map({type: .[0], count: length})' errors.json
# Compact output (no whitespace) for piping
jq -c '.data[]' large.json7. Decision Guide: jq vs JSONPath
Use this table to pick the right tool:
| Scenario | Pick | Why |
|---|---|---|
| Shell script / CI pipeline | jq | Native CLI, no extra deps, powerful transforms |
| In-process querying in Python/JS/Java | JSONPath | Pure library, no subprocess, easy deploy |
| Spec requirement (AWS, k8s, OpenAPI) | JSONPath | Built into the spec |
| Reshape / aggregate / compute | jq | JSONPath only reads, does not transform |
| Stream very large files | jq | Streaming mode avoids full load |
| Config-driven query (stored in DB) | JSONPath | Simpler, safer to eval as data |
| One-off data exploration | jq | REPL-like, rich output formatting |
Frequently Asked Questions
What is the main difference between jq and JSONPath?
JSONPath is a query language — it selects and extracts nodes from an existing JSON document, analogous to XPath for XML. It returns the matched values unchanged. jq is a full transformation language — it can select, filter, reshape, compute, reduce, and generate JSON. A JSONPath expression like $.store.book[?(@.price < 10)] returns the original book objects that match; a jq filter like .store.book[] | select(.price < 10) | {title, price} returns only the title and price fields, reshaped into new objects. If you only need to extract data without changing its shape, JSONPath is simpler. If you need to transform, aggregate, or compute new values, jq is the right tool. JSONPath is widely available as a library in most languages; jq is primarily a CLI tool, though ports exist for several languages.
What is JSONPath RFC 9535 and why does it matter?
RFC 9535, published in February 2024, is the first formal IETF specification for JSONPath. Before it, JSONPath had no standard — every library (jsonpath-ng for Python, jsonpath for Go, jsonpath in JavaScript) had its own interpretation of edge cases: how filter expressions work, what the @ operator means in nested contexts, how recursive descent (..) handles duplicate nodes, and whether negative array indices are allowed. RFC 9535 resolves all of these ambiguities. It defines the syntax precisely using ABNF, specifies the normalized path representation, and clarifies that a JSONPath query always returns a nodelist (possibly empty), never an error for a missing path. Libraries that implement RFC 9535 (jsonpath-plus 9+, nimma in Node.js, jsonpath-rfc9535 in Python) produce consistent results. When choosing a JSONPath library, prefer one that explicitly advertises RFC 9535 compliance.
What can jq do that JSONPath cannot?
jq can do anything JSONPath can plus: (1) Transform shape — build entirely new objects/arrays from input values. (2) Compute — arithmetic, string interpolation, date formatting, base64 encode/decode, regex match. (3) Reduce — sum, count, group, or accumulate across an array with reduce or group_by. (4) Multiple outputs — pipe (|) chains multiple filters; filters that produce streams can generate multiple output values. (5) Conditionals — if/then/else, try/catch for error handling. (6) Define reusable functions with def. (7) Update in place — the update operator (.foo |= . + 1) modifies part of a structure without touching the rest. (8) Stream large files without loading the entire document. JSONPath's filter expressions (@.price > 10) cover simple comparisons, but they cannot aggregate, reshape, or compute derived values.
When should I use JSONPath instead of jq?
Prefer JSONPath when: (1) You are working inside an application using a language-native library — jsonpath-ng (Python), com.jayway.jsonpath (Java/Android), jsonpath (Go), jsonpath-plus (JavaScript) are easier to integrate than shelling out to jq. (2) The spec you are implementing references JSONPath — JSON Schema (draft 2020-12 uses JSON Pointer, but JSON Schema's "$dynamicRef" and some validators use JSONPath-like syntax), Kubernetes CRD validation, OpenAPI 3.1 path expressions, and AWS Step Functions JSONPath all build on JSONPath semantics. (3) You need a standardized wire format — a JSONPath expression is a string you can store in a database, pass as a config value, or include in an API request body. A jq filter is a mini-program that is harder to treat as data. (4) You only need to read/select, not transform — JSONPath is simpler to learn, audit, and sandbox.
How does jq recursive descent compare to JSONPath ..?
Both jq and JSONPath use .. for recursive descent, but with different semantics. JSONPath's .. operator traverses all descendants and collects every node that matches the following path step — $..[?(@.type=="error")] finds all "type":"error" nodes at any depth. jq's .. emits every node in the tree (including intermediate objects and arrays), which you then pipe into a filter: .. | objects | select(.type == "error"). This is more verbose but gives you access to intermediate nodes that JSONPath's .. skips. For deep-search queries, JSONPath's .. is more concise; jq's .. is more flexible because you can apply any transformation to each level. One practical difference: jq's .. on a large document can be slow because it materializes every node; JSONPath implementations typically use lazy evaluation and stop early when possible.
Can I use jq inside a Python or JavaScript program?
The primary jq distribution is a C binary, so using it inside a program requires either subprocess calls or a native port. In Python, the jq package (pip install jq) wraps libjq and lets you call jq.first(".foo", data) and jq.all(".bar[]", data) without subprocess. In Node.js, node-jq calls the jq binary via child_process — simpler to use but requires jq installed on the system. In Go, gojq is a pure-Go jq implementation you can import directly. In Rust, jaq is a jq-compatible implementation. For browser/Deno environments, jq-web compiles jq to WebAssembly. In most application code, if you find yourself reaching for jq in-process, consider whether a JSONPath library fits better — JSONPath libraries are pure-language, no binary dependency, and easier to bundle and deploy.
What is the @ operator in JSONPath filter expressions?
In a JSONPath filter expression (inside [?( )]), the @ symbol refers to the current node being tested — the element of the array currently under consideration. $.books[?(@.price < 20)] means "select from the books array every element where that element's price property is less than 20." The @ can be chained: $.books[?(@.author.name == "Tolkien")] accesses a nested property on the current element. RFC 9535 specifies that @ is only valid inside filter expressions and always refers to the current item, never to the root (which uses $). A common mistake is using $ where @ is needed inside a filter, which causes the filter to compare against the entire document root instead of the current array element.
Which is faster: jq or a JSONPath library?
For large documents (>1 MB), jq's C implementation is typically faster than pure-language JSONPath libraries, especially for complex queries. jq also supports streaming mode (--stream flag or the input/inputs builtins) that parses JSON as a stream without loading the entire document into memory — critical for files that do not fit in RAM. JSONPath libraries in interpreted languages (Python jsonpath-ng, JavaScript jsonpath-plus) load the entire document and build an in-memory tree before evaluating, which dominates runtime for large files. JSONPath libraries written in compiled languages (Go, Rust, Java with Jayway) close the gap significantly. For typical API response sizes (< 100 KB), both are fast enough that the choice should be driven by integration ergonomics, not performance. Benchmark your specific query and document size if performance is a concern.
Further reading and primary sources
- RFC 9535 — JSONPath: Query Expressions for JSON — The official IETF specification for JSONPath, published February 2024
- jq Manual — Complete reference for all jq filters, types, functions, and advanced features
- JSONPath Cheatsheet (Jsonic) — Quick reference for JSONPath operators, filter expressions, and slice syntax
- jq Filter Examples (Jsonic) — Cookbook of jq one-liners for common shell and CI tasks
- JSONPath Online Evaluator (Jsonic) — Test JSONPath expressions against live JSON in the browser