1. Insight
Insight
The problem this article addresses and why it matters.
Two query languages for the same job
JSONPath and jq are the two dominant query languages for navigating JSON. They look superficially similar — both extract values from JSON structures using a path expression — but they evolved in different communities. JSONPath grew out of XPath via Stefan Gössner's 2007 spec and dominates JavaScript and web tooling. jq grew out of Unix philosophy and dominates CLI workflows; its manual reads like a small programming language.
The choice between them is usually accidental. JavaScript developers reach for JSONPath because the npm package is there. Backend engineers and DevOps teams reach for jq because they've been using it for shell scripts. The result is two halves of the same problem solved in two different tools — and the engineer reading the code today often doesn't know the other half exists.
Why a dual-syntax tool
The tool in this article supports both syntaxes in one API surface. Pass syntax: 'jsonpath' and use the $.users[*].email style; pass syntax: 'jq' and use the .users[].email style. The output shape is identical so consumers don't have to branch on which dialect they used. For agents and pipelines that ingest queries from multiple sources, this means one tool can serve both communities without translation.
The path suggestion mode is the structural feature most other JSON-query tools don't ship. Pass suggestPaths: true with an empty expression, and the tool analyses the JSON shape and suggests query expressions for common extraction patterns — "all IDs", "all email addresses", "every leaf node at depth 3". Agents that don't know the schema can discover query paths without a separate inspection step.
What this article delivers
End-to-end walkthroughs of both syntaxes against the same JSON. We cover the suggestion mode, the tree-overview metadata that comes back with every query, and the cases where one syntax has an expression the other can't match (jq's reduce / foreach constructs have no JSONPath equivalent; JSONPath filter expressions have a different grammar than jq's predicates).
2. Intent
Intent
What you will be able to do after reading.
By the end of this article you will be able to:
- Query any JSON with JSONPath or jq syntax through the same API
- Use suggestion mode to get recommended extraction expressions when you don't know the schema
- Read the tree overview metadata (depth, key count, array sizes) to understand the JSON shape before querying
- Choose between syntaxes based on consumer context — JSONPath for JS / web tooling, jq for CLI / DevOps pipelines
- Recognise the constructs unique to each syntax and when to reach for one specifically
The Examples section walks through three queries (simple extraction, filter, recursive descent) in both syntaxes side-by-side.
3. Examples
Examples
Annotated code and worked scenarios.
Before / after: extracting all user emails
Sample JSON:
{
"users": [
{ "id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin" },
{ "id": 2, "name": "Bob", "email": "bob@example.com", "role": "user" },
{ "id": 3, "name": "Carol", "email": "carol@example.com", "role": "user" }
]
}JSONPath:
jsonPathEvaluator({
json,
expression: '$.users[*].email',
syntax: 'jsonpath',
});
// matches: ['alice@example.com', 'bob@example.com', 'carol@example.com']
// matchCount: 3
// paths: [
// '$.users[0].email',
// '$.users[1].email',
// '$.users[2].email',
// ]jq:
jsonPathEvaluator({
json,
expression: '.users[].email',
syntax: 'jq',
});
// Same matches, same paths in jq notation: '.users[0].email', etc.Identical results, different syntax. The output paths array is in the chosen syntax — useful when the agent needs to display "where the value came from" in the consumer's preferred dialect.
Before / after: filtered query
Get the emails of users with the admin role:
JSONPath:
$.users[?(@.role == 'admin')].emailjq:
.users[] | select(.role == "admin") | .emailBoth return ['alice@example.com']. The JSONPath filter syntax uses ?(@.<predicate>); jq uses select(<predicate>). The tool runs whichever syntax you pass — no translation required.
Before / after: recursive descent
Find every id field anywhere in the structure:
{
"org": { "id": "org_42", "members": [{ "id": "u_1" }, { "id": "u_2", "parent": { "id": "u_root" } }] }
}JSONPath uses .. for recursive descent:
expression: '$..id',
syntax: 'jsonpath',
// matches: ['org_42', 'u_1', 'u_2', 'u_root']
// paths: [
// '$.org.id',
// '$.org.members[0].id',
// '$.org.members[1].id',
// '$.org.members[1].parent.id',
// ]jq uses .. with field-name filter:
expression: '.. | .id? // empty',
syntax: 'jq',
// Same matches and paths.jq's recursive descent is more verbose because .. returns every value (including non-objects) — the .id? extracts the id field if present, and // empty drops nulls.
Before / after: suggestion mode
You have a JSON payload but don't know the schema. Set suggestPaths: true with an empty expression:
jsonPathEvaluator({
json,
expression: '',
syntax: 'jsonpath',
suggestPaths: true,
});
// matches: []
// matchCount: 0
// suggestions: [
// { expression: '$..id', description: 'All ID fields (recursive)', matchCount: 4 },
// { expression: '$..email', description: 'All email fields', matchCount: 3 },
// { expression: '$.users[*]', description: 'All array items in .users', matchCount: 3 },
// { expression: '$..*[*]', description: 'All leaf scalar values', matchCount: 17 },
// ]
// tree: {
// depth: 4,
// totalKeys: 12,
// totalArrayItems: 3,
// topLevelKeys: ['users', 'org'],
// }The agent picks the suggestion with the right matchCount and uses that expression directly. Useful when the JSON shape is unknown at planning time.
When humans use this
The dominant workflow is the iterative query: paste JSON, try an expression, refine. The dual-syntax support means a developer doesn't have to pick between two tools depending on which dialect they're more comfortable with — both are one click in the web UI. Suggestion mode powers the "what's in this JSON?" question that comes up when inheriting an undocumented API response or exploring an unknown data source.
When agents use this
Two patterns:
- Schema-less JSON extraction. An agent ingesting JSON from an unknown source uses suggestion mode to discover extraction paths, picks the suggestion matching its goal (e.g. "all email addresses" for an outreach pipeline), and runs the suggested expression. The same flow handles diverse JSON shapes without per-source configuration.
- Polyglot pipelines. An agent composing tools from different ecosystems (a JavaScript-side extractor calling a shell-script-side jq filter) keeps the query expression in one syntax and lets the tool handle execution in either. Eliminates the translation tax across language boundaries.
Edge cases
Cyclical structures
JSON itself can't represent cycles (the parser would reject them), but tree-shaped data with self-similar nesting (a comment with children comments with grandchildren comments) is handled by both syntaxes. Recursive descent ($.. in JSONPath, .. in jq) walks every node regardless of depth, capped at 1000 levels.
Numeric vs string keys
$["123"] (JSONPath) and .["123"] (jq) access keys that happen to be all-digits. The shorter form $.123 is rejected by some JSONPath libraries; the tool implements the strict version. Numeric array indices use [N] in both syntaxes.
jq programs vs filters
jq supports full programs with variables, functions, and reduce / foreach constructs. The tool runs the expression as a single filter — pass def f: ...; f style definitions and they work, but the focus is querying, not arbitrary computation. For complex transformations, run jq directly.
Schema drift
If the JSON shape changes (a key renamed, a nesting level added), your saved expressions break silently — they just return zero matches. Pair the evaluator with the diff_checker tool's JSON structural diff to detect shape changes before queries return surprises.
4. Documentation
Documentation
Reference signatures, edge cases, and lookup tables.
Input parameters
Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| ✓ | — | The JSON to query — strings are parsed first |
|
| ✓ | — | Query expression in the chosen syntax. Empty when |
|
| ✓ | — | Query dialect |
|
| ✗ |
| Return suggested expressions instead of executing |
Output shape
{
matches: unknown[]; // array of matched values
matchCount: number;
paths: string[]; // full path to each match in the chosen syntax
suggestions?: Array<{ // when suggestPaths: true
expression: string;
description: string;
matchCount: number;
}>;
tree?: { // structural overview
depth: number; // max nesting depth
totalKeys: number;
totalArrayItems: number;
topLevelKeys: string[];
};
}Common expression patterns
Pattern | JSONPath | jq |
|---|---|---|
All values at a key |
|
|
Array indexing |
|
|
Slice |
|
|
Filter |
|
|
Existence |
|
|
Multiple keys |
|
|
Recursive descent |
|
|
Error codes
Code | When it fires | Recovery |
|---|---|---|
|
| Provide the required input |
| String input failed JSON.parse | Pre-clean with |
| Query expression failed to parse in the chosen syntax | Verify the expression syntax matches the |
|
| Use one of the two supported syntaxes |
| Expression hit 3-second execution ceiling (very large input + recursive expression) | Narrow the expression or use suggestion mode first |
When NOT to use this tool
For full JSON transformation (not just querying — e.g. restructuring an entire response), use jq directly as a CLI tool or a dedicated transformation library (json-transforms, transform-js). The tool focuses on querying; transformations beyond simple field extraction are out of scope.
For schema-validating JSON queries (extract values AND validate types in one pass), use llm_to_json_cleaner with the targetSchema parameter — that tool combines schema validation with extraction.
For very large JSON inputs (over 5MB), streaming-JSON parsers are the right tool. This evaluator loads the whole document into memory; the alternative is stream-json (npm) or jq --stream (CLI).
Performance notes
Typical execution: under 5ms for JSON under 50KB and non-recursive expressions. Recursive descent (..) is O(n) in the total node count. Suggestion mode adds 10-30ms for the tree walk and pattern matching.
The tool is deterministic — same JSON + expression + syntax always produces the same output — so REST responses are Edge-Cache eligible. The jq runtime is bundled in the server; no external process spawn (the cost of which would dominate for short queries).