Working with Files¶
jpx provides several ways to read input and write output, making it flexible for different workflows.
Reading Input¶
From stdin (default)¶
Pipe JSON directly to jpx:
echo '{"name": "alice"}' | jpx 'name'
# "alice"
cat data.json | jpx 'users[*].name'
curl -s https://api.example.com/data | jpx 'items[0]'
From a File¶
Pass the file as a trailing argument (jq-style):
Or use -f / --file explicitly:
Null Input¶
Use -n or --null-input for expressions that don't need input data:
jpx -n 'now()'
# "2024-01-15T10:30:00Z"
jpx -n 'uuid()'
# "550e8400-e29b-41d4-a716-446655440000"
jpx -n 'range(1, 5)'
# [1, 2, 3, 4]
Writing Output¶
To stdout (default)¶
Output goes to stdout with pretty-printing by default:
To a File¶
Use -o or --output to write to a file:
jpx -f data.json 'users[?active]' -o active-users.json
jpx -f input.json 'transform(@)' --output result.json
Compact Output¶
Use -c or --compact for single-line JSON (no pretty-printing):
Processing Multiple Files¶
Evaluate Once Per File¶
Use --per-file to reuse one jpx process across a corpus. The shell expands
globs; jpx evaluates the expression independently for every resulting path:
jpx --per-file --lines 'metadata.title' *.json
# Repeated -f/--file is equivalent
jpx --per-file --lines -f first.json -f second.json 'metadata.title'
Per-file results form one outer array. --lines writes exactly one JSON value
per input file; without it, the outer array is rendered normally.
The $file binding contains the path exactly as passed, which makes provenance
easy to include in each result:
Combine --per-file with --slurp for JSONL files. Each file is slurped
separately before evaluation:
--per-file conflicts with --stream and --null-input. The $file binding
is a jpx extension and is unavailable in --strict mode.
Shell Loop Alternative¶
For workflows needing custom per-file control, a shell loop remains useful:
Combining Files with Slurp¶
Use -s or --slurp to combine multiple JSON values into an array:
Streaming JSON Lines (NDJSON)¶
Use --stream (or --each) for newline-delimited JSON:
# Process each line independently
cat events.ndjson | jpx --stream 'event_type'
# Filter and transform streaming data. Each line is an object, so use if()
# rather than an array filter; null results are skipped
cat logs.ndjson | jpx --stream "if(level == 'error', message, \`null\`)"
Streaming mode uses constant memory regardless of file size. For aggregates
across all records (counts, group_by, sorting), use --slurp instead;
--stream and --slurp conflict.
Tested recipes live in
crates/jpx/tests/fixtures/agent_recipes/recipes.json.
Query Files¶
Store queries in files for reuse:
Plain Text Query Files¶
Query Libraries (.jpx)¶
Create reusable query collections:
-- queries.jpx
-- :name active-users
-- :description Get all active users
users[?active]
-- :name user-emails
-- :description Extract just the email addresses
users[*].email
Run named queries:
jpx -Q queries.jpx:active-users -f data.json
# Or with --query flag
jpx -Q queries.jpx --query user-emails -f data.json
# List available queries
jpx -Q queries.jpx --list-queries
See Query Files for more details.
JSON Patch Operations¶
Generate Patches¶
Compare two files and generate a JSON Patch (RFC 6902):
Apply Patches¶
Apply a patch to a document:
Merge Patches¶
Apply a JSON Merge Patch (RFC 7396):
Tips¶
Checking File Validity¶
Use --stats to quickly validate and analyze a JSON file:
Exploring File Structure¶
Use --paths to see all paths in a JSON document:
jpx --paths -f data.json
# With types
jpx --paths --types -f data.json
# With values
jpx --paths --values -f data.json
Benchmarking File Operations¶
Measure query performance on a file: