obfus.link
Analyzers

JSONPath + jq in one tool: dual-syntax querying with path suggestions

Query any JSON with JSONPath or jq syntax through one API. Suggestion mode recommends extraction expressions when the schema is unknown; tree-overview metadata surfaces the JSON's shape before querying.

The JSON Path Evaluator runs queries in both JSONPath and jq syntaxes through one API. Suggestion mode analyses the JSON shape and recommends extraction expressions for common patterns when the schema is unknown. Tree-overview metadata returns depth, key count, array sizes, and top-level keys with every query.

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')].email

jq:

.users[] | select(.role == "admin") | .email

Both 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

json

string | object

The JSON to query — strings are parsed first

expression

string

Query expression in the chosen syntax. Empty when suggestPaths: true

syntax

'jsonpath' | 'jq'

Query dialect

suggestPaths

boolean

false

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

$..email

.. | .email? // empty

Array indexing

$.users[0]

.users[0]

Slice

$.users[0:2]

.users[0:2]

Filter

$.users[?(@.role=='admin')]

.users[] | select(.role=="admin")

Existence

$.users[?(@.email)]

.users[] | select(has("email"))

Multiple keys

$.users[*]['name','email']

.users[] | {name, email}

Recursive descent

$..id

.. | .id? // empty

Error codes

Code

When it fires

Recovery

INPUT_EMPTY

json empty, OR expression empty without suggestPaths: true

Provide the required input

INPUT_MALFORMED

String input failed JSON.parse

Pre-clean with llm_to_json_cleaner

PARSE_FAILED

Query expression failed to parse in the chosen syntax

Verify the expression syntax matches the syntax parameter

UNSUPPORTED_FORMAT

syntax value outside {jsonpath, jq}

Use one of the two supported syntaxes

TIMEOUT

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).

Try it now

JSON Path Evaluator

Extract values from JSON with dual JSONPath + jq syntax and path suggestions

FAQ

Frequently asked questions

When should I use JSONPath vs jq?

JSONPath if your team is JavaScript / web-focused — most npm packages use it. jq if your team is CLI / DevOps — every shell script uses it. The tool runs both, so the choice can be per-consumer rather than per-tool.

What does suggestion mode return?

A list of suggested expressions for common extraction patterns derived from the JSON shape: "All ID fields (recursive)", "All array items in .users", "All leaf scalar values". Each suggestion includes the matchCount so the agent picks the right one without re-querying.

How deeply can recursive descent search?

1000 levels of nesting before the parser returns INPUT_MALFORMED. Real-world JSON rarely exceeds 20-30 levels. Both syntaxes' recursive descent ($.. and ..) walk the full tree at every level by default.

Does jq mode support full jq programs?

Yes, including def function definitions. The focus is query / extraction; for complex transformations (sort_by + group_by + reduce), running jq directly is the better path. The tool runs the expression as a single filter against the input JSON.