Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

This documentation covers the JMESPath Extensions project, which provides 400+ additional functions for JMESPath queries.

Project Components

This project includes multiple components:

ComponentDescriptionDocumentation
jmespath_extensionsRust library with 400+ extension functionsdocs.rs
jpxCommand-line tool for querying JSONThis site
jmespath-extensions-pyPython bindingsPython section
MCP ServerAI assistant integrationMCP section

This documentation primarily focuses on jpx, the CLI tool. For using the Rust library directly in your code, see the rustdoc documentation.

What is JMESPath?

JMESPath is a query language for JSON. It allows you to declaratively specify how to extract and transform elements from a JSON document.

Why JMESPath Extensions?

While standard JMESPath is powerful, it’s intentionally minimal with only 26 built-in functions. This project extends JMESPath with 400+ additional functions for:

  • String manipulation: upper, lower, split, replace, camel_case, snake_case, and more
  • Array operations: unique, chunk, flatten, group_by, zip, and more
  • Object manipulation: pick, omit, deep_merge, flatten_keys, and more
  • Math & statistics: round, sqrt, median, stddev, percentile, and more
  • Date/time: now, format_date, parse_date, date_add, date_diff
  • Hashing & encoding: md5, sha256, base64_encode, hex_encode
  • Fuzzy matching: levenshtein, jaro_winkler, soundex
  • And much more: geo functions, validation, regex, UUIDs, network utilities…

Features

  • Rust Library: Use the functions in your Rust applications (docs.rs)
  • Powerful CLI: Query JSON from files, stdin, or inline with jpx
  • MCP Server: Use jpx with AI assistants like Claude
  • Python Bindings: Use the same 400+ functions in Python
  • Function Discovery: Built-in help for all functions
  • Strict Mode: Use only standard JMESPath for portable queries

Quick Example

# Basic query
echo '{"users": [{"name": "alice"}, {"name": "bob"}]}' | jpx 'users[*].name'
# ["alice", "bob"]

# Using extension functions
echo '{"name": "hello world"}' | jpx 'upper(name)'
# "HELLO WORLD"

echo '{"items": [1, 2, 2, 3, 3, 3]}' | jpx 'unique(items)'
# [1, 2, 3]

echo '{"values": [10, 20, 30, 40, 50]}' | jpx 'median(values)'
# 30

Getting Started

Ready to dive in? Start with Installation to get jpx set up on your system.

Installation

Docker (Quickest)

Try jpx instantly without installing anything:

# Pull the image
docker pull ghcr.io/joshrotenberg/jpx

# Run a query
echo '{"name": "Alice", "age": 30}' | docker run -i ghcr.io/joshrotenberg/jpx 'name'
# "Alice"

# Fetch and transform data from an API
curl -s 'https://hacker-news.firebaseio.com/v0/item/1.json' | \
  docker run -i ghcr.io/joshrotenberg/jpx '{title: title, by: by, score: score}'
# {"by": "pg", "score": 57, "title": "Y Combinator"}

Available for linux/amd64 and linux/arm64.

Homebrew (macOS/Linux)

The easiest way to install jpx on macOS or Linux:

brew tap joshrotenberg/brew
brew install jpx

Pre-built Binaries

Download pre-built binaries for your platform from the GitHub Releases page.

Available platforms:

  • macOS (Apple Silicon / arm64)
  • macOS (Intel / x86_64)
  • Linux (x86_64)
  • Windows (x86_64)

Cargo (from crates.io)

If you have Rust installed:

cargo install jpx

From Source

Clone and build from source:

git clone https://github.com/joshrotenberg/jmespath-extensions
cd jmespath-extensions/jpx
cargo install --path .

Without MCP Server Support

MCP support is included by default. To build without it (smaller binary):

cargo install --path . --no-default-features

Verify Installation

Check that jpx is installed correctly:

jpx --version

You should see output like:

jpx 0.1.15

Shell Completions

Generate shell completions for your shell:

# Bash
jpx --completions bash > ~/.local/share/bash-completion/completions/jpx

# Zsh
jpx --completions zsh > ~/.zfunc/_jpx

# Fish
jpx --completions fish > ~/.config/fish/completions/jpx.fish

# PowerShell
jpx --completions powershell > jpx.ps1

Quick Start

jpx is a command-line JSON processor with 400+ built-in functions. Pipe JSON in, get transformed JSON out.

Five things to know to start using it:

1. Get a field

echo '{"name": "Alice", "city": "NYC"}' | jpx 'name'
# "Alice"

Nested fields use dots:

echo '{"user": {"name": "Alice"}}' | jpx 'user.name'
# "Alice"

2. Get all items from an array

Use [*] to get every element:

echo '[{"name": "Alice"}, {"name": "Bob"}]' | jpx '[*].name'
# ["Alice", "Bob"]

3. Filter an array

Use [?condition] to filter:

echo '[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]' | jpx '[?age > `28`]'
# [{"name": "Alice", "age": 30}]

Note: literal values use backticks (`28`).

4. Call a function

jpx has 400+ functions. Pass data with @ (current value):

echo '[3, 1, 4, 1, 5]' | jpx 'sort(@)'
# [1, 1, 3, 4, 5]

echo '[3, 1, 4, 1, 5]' | jpx 'unique(@)'
# [3, 1, 4, 5]

echo '{"name": "hello"}' | jpx 'upper(name)'
# "HELLO"

5. Chain with pipes

Combine operations with |:

echo '[{"n": "Alice"}, {"n": "Bob"}, {"n": "Alice"}]' | jpx '[*].n | unique(@) | sort(@)'
# ["Alice", "Bob"]

That’s it. You can do a lot with just these five patterns.

Finding Functions

Don’t memorize 400 functions. Search for what you need:

jpx --search unique
jpx --describe unique

Next Steps

Basic Usage

Input Sources

jpx can read JSON from multiple sources:

Standard Input (stdin)

echo '{"name": "Alice"}' | jpx 'name'
# "Alice"

cat data.json | jpx 'users[*].name'
curl -s https://api.example.com/data | jpx 'results[0]'

File Input

jpx 'users[*].name' -f data.json
jpx --file users.json 'length(@)'

Null Input

Use -n to start with null (useful for functions that don’t need input):

jpx -n 'now()'
# 1705312200

jpx -n 'uuid()'
# "550e8400-e29b-41d4-a716-446655440000"

jpx -n 'range(`1`, `5`)'
# [1, 2, 3, 4]

Output Options

Pretty Printing (default)

By default, jpx pretty-prints JSON output with colors:

echo '{"a":1,"b":2}' | jpx '@'

Output:

{
  "a": 1,
  "b": 2
}

Compact Output

Use -c for single-line output:

echo '{"a":1,"b":2}' | jpx -c '@'

Output:

{"a":1,"b":2}

Raw Strings

Use -r to output strings without quotes:

echo '{"msg": "hello"}' | jpx -r 'msg'

Output:

hello

Output to File

Write results to a file:

jpx 'users[*].email' -f data.json -o emails.json
# (writes to emails.json)

Slurp Mode

Read multiple JSON objects into an array:

echo '{"a":1}
{"b":2}
{"c":3}' | jpx -s 'length(@)'

Output:

3

Expression as Positional Argument

The expression can be the first positional argument:

jpx 'users[*].name' -f data.json

Or use -e / --expression:

jpx -e 'users[*].name' -f data.json

Chaining Expressions

Three ways to chain transformations:

# 1. Pipes within a single expression (most common)
jpx 'users | [*].name | sort(@)' -f data.json

# 2. Multiple positional arguments
jpx 'users' '[*].name' 'sort(@)' -f data.json

# 3. Multiple -e flags
jpx -e 'users' -e '[*].name' -e 'sort(@)' -f data.json

All three are equivalent - each expression receives the output of the previous one.

Verbose Mode

See expression details and timing:

echo '{"x": 1}' | jpx -v 'x'

Output:

Input: object (1 keys)

[1] Expression: x
[1] Result: number (1)
[1] Time: 0.040ms

Total time: 0.214ms
1

Quiet Mode

Suppress errors and warnings:

jpx -q 'invalid[' -f data.json
# (no error output, exits with non-zero status)

Color Control

Control colorized output:

jpx --color=always 'name' -f data.json  # Force colors
jpx --color=never 'name' -f data.json   # No colors
jpx --color=auto 'name' -f data.json    # Auto-detect (default)

CLI Reference

Complete reference for all jpx command-line options.

Synopsis

jpx [OPTIONS] [EXPRESSIONS]...

Arguments

ArgumentDescription
[EXPRESSIONS]...JMESPath expression(s) to evaluate (multiple are chained as a pipeline)

Options

Input/Output

OptionDescription
-e, --expression <EXPR>Expression(s) to evaluate (can be repeated)
-Q, --query-file <FILE>Read JMESPath expression from file (supports .jpx libraries with colon syntax: file.jpx:query-name)
--query <NAME>Select a named query from a .jpx library
--list-queriesList all queries in a .jpx library file
--checkValidate all queries in a .jpx library without running
-f, --file <FILE>Input JSON file (reads stdin if not provided)
-o, --output <FILE>Output file (writes to stdout if not provided)
-n, --null-inputDon’t read input, use null as input value
-s, --slurpRead all inputs into an array
--streamStream mode - process input line by line (for NDJSON/JSON Lines)

Output Format

OptionDescription
-r, --rawOutput raw strings without quotes
-c, --compactCompact output (no pretty printing)
-y, --yamlOutput as YAML
--tomlOutput as TOML
--csvOutput as CSV (comma-separated values)
--tsvOutput as TSV (tab-separated values)
-l, --linesOutput one JSON value per line
-t, --tableOutput as a formatted table (for arrays of objects)
--table-style <STYLE>Table style: unicode (default), ascii, markdown, plain
--color <MODE>Colorize output: auto, always, never

See Output Formats for detailed examples.

Modes

OptionDescription
--strictStrict mode - only standard JMESPath functions
-v, --verboseShow expression details and timing
-q, --quietSuppress errors and warnings

JSON Patch Operations

OptionDescription
--diff <SOURCE> <TARGET>Generate JSON Patch (RFC 6902) from two files
--patch <PATCH_FILE>Apply JSON Patch (RFC 6902) to input
--merge <MERGE_FILE>Apply JSON Merge Patch (RFC 7396) to input

Data Analysis

OptionDescription
--statsShow statistics about the input data
--pathsList all paths in the input JSON
--typesShow types alongside paths (use with --paths)
--valuesShow values alongside paths (use with --paths)

Benchmarking

OptionDescription
--bench [N]Benchmark expression performance (default: 100 iterations)
--warmup <N>Number of warmup iterations before benchmarking (default: 5)

Function Discovery

OptionDescription
--list-functionsList all available functions
--list-category <NAME>List functions in a specific category
--describe <FUNCTION>Show detailed info for a function
--search <QUERY>Search functions by name, description, or category (fuzzy matching)
--similar <FUNCTION>Find functions similar to the specified function

Debugging

OptionDescription
--explainShow how an expression is parsed (AST)
--debugShow diagnostic information for troubleshooting

Interactive Mode

OptionDescription
--replStart interactive REPL mode
--demo <NAME>Load a demo dataset (use with --repl)

Other

OptionDescription
--completions <SHELL>Generate shell completions (bash, zsh, fish, powershell, elvish)
-h, --helpPrint help
-V, --versionPrint version

Environment Variables

VariableDescription
JPX_VERBOSE=1Enable verbose mode
JPX_QUIET=1Enable quiet mode
JPX_STRICT=1Enable strict mode
JPX_RAW=1Output raw strings
JPX_COMPACT=1Compact output

Environment variables are overridden by command-line flags.

Quick Examples

A few examples showing common flag combinations. For comprehensive examples, see:

# Query a file, raw output for scripting
jpx -r 'config.api_key' -f settings.json

# Chain expressions, table output
jpx 'users' '[?active]' -t -f data.json

# Stream NDJSON, filter errors
cat logs.ndjson | jpx --stream '[?level == `"error"`]'

# Analyze structure before querying
jpx --paths --types -f data.json

# Find functions by keyword
jpx --search unique

# Run a named query from a library
jpx -Q queries.jpx:active-users -f data.json

Exit Codes

CodeMeaning
0Success
1Error (invalid expression, file not found, etc.)

Cookbook

Common tasks and how to do them in jpx.

Extracting Data

Most JSON wrangling starts with pulling out the fields you care about.

Get a nested field

echo '{"user": {"profile": {"name": "Alice"}}}' | jpx 'user.profile.name'
# "Alice"

Get multiple fields

echo '{"id": 1, "name": "Alice", "email": "a@example.com", "age": 30}' | jpx '{id: id, name: name}'
# {"id": 1, "name": "Alice"}

Get a field from every object in an array

echo '[{"name": "Alice"}, {"name": "Bob"}]' | jpx '[*].name'
# ["Alice", "Bob"]

Get with a default value

echo '{"name": "Alice"}' | jpx 'get(@, `"email"`, `"no email"`)'
# "no email"

Filtering

Filter expressions ([?condition]) are one of JMESPath’s most powerful features. They let you select array elements that match criteria without writing loops.

Filter by a condition

echo '[{"status": "active"}, {"status": "inactive"}]' | jpx '[?status == `"active"`]'
# [{"status": "active"}]

Filter by multiple conditions

echo '[{"age": 25, "active": true}, {"age": 35, "active": true}, {"age": 30, "active": false}]' \
  | jpx '[?age > `30` && active]'
# [{"age": 35, "active": true}]

Filter by nested field

echo '[{"user": {"role": "admin"}}, {"user": {"role": "guest"}}]' | jpx '[?user.role == `"admin"`]'
# [{"user": {"role": "admin"}}]

Check if field exists

echo '[{"name": "Alice", "email": "a@b.com"}, {"name": "Bob"}]' | jpx '[?email]'
# [{"name": "Alice", "email": "a@b.com"}]

Transforming

Reshape data to match the structure you need - rename fields, compute new values, or convert between formats.

Rename fields

echo '{"firstName": "Alice", "lastName": "Smith"}' | jpx '{first: firstName, last: lastName}'
# {"first": "Alice", "last": "Smith"}

Add a computed field

echo '{"price": 100, "qty": 3}' | jpx '{price: price, qty: qty, total: multiply(price, qty)}'
# {"price": 100, "qty": 3, "total": 300}

Convert object keys to snake_case

echo '{"firstName": "Alice", "lastName": "Smith"}' | jpx 'snake_keys(@)'
# {"first_name": "Alice", "last_name": "Smith"}

Flatten nested structure

echo '{"user": {"name": "Alice", "address": {"city": "NYC"}}}' | jpx 'flatten(@)'
# {"user.name": "Alice", "user.address.city": "NYC"}

Arrays

Common operations on lists of items - sorting, deduplication, grouping, and slicing.

Sort

echo '[3, 1, 4, 1, 5]' | jpx 'sort(@)'
# [1, 1, 3, 4, 5]

Sort objects by field

echo '[{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}]' | jpx 'sort_by(@, &name)'
# [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]

Remove duplicates

echo '[1, 2, 2, 3, 3, 3]' | jpx 'unique(@)'
# [1, 2, 3]

First/last N items

echo '[1, 2, 3, 4, 5]' | jpx '[:3]'   # first 3
# [1, 2, 3]

echo '[1, 2, 3, 4, 5]' | jpx '[-2:]'  # last 2
# [4, 5]

Group by field

echo '[{"dept": "eng", "name": "Alice"}, {"dept": "eng", "name": "Bob"}, {"dept": "sales", "name": "Carol"}]' \
  | jpx 'group_by(@, `"dept"`)'
# {"eng": [{"dept": "eng", "name": "Alice"}, {"dept": "eng", "name": "Bob"}], "sales": [...]}

Chunk into batches

echo '[1, 2, 3, 4, 5, 6, 7]' | jpx 'chunk(@, `3`)'
# [[1, 2, 3], [4, 5, 6], [7]]

Aggregation

Reduce arrays to summary values - counts, sums, averages, and grouped statistics.

Count

echo '[1, 2, 3, 4, 5]' | jpx 'length(@)'
# 5

Sum, average, min, max

echo '[10, 20, 30]' | jpx 'sum(@)'
# 60

echo '[10, 20, 30]' | jpx 'avg(@)'
# 20

echo '[10, 20, 30]' | jpx 'min(@)'
# 10

Count by group

echo '[{"status": "active"}, {"status": "active"}, {"status": "inactive"}]' \
  | jpx 'group_by(@, `"status"`) | map_values(`"length(@)"`, @)'
# {"active": 2, "inactive": 1}

Strings

Text manipulation - case conversion, splitting, joining, and search/replace.

Upper/lower case

echo '"hello"' | jpx 'upper(@)'
# "HELLO"

Split and join

echo '"a,b,c"' | jpx 'split(@, `","`)'
# ["a", "b", "c"]

echo '["a", "b", "c"]' | jpx 'join(`", "`, @)'
# "a, b, c"

Replace

echo '"hello world"' | jpx 'replace(@, `"world"`, `"jpx"`)'
# "hello jpx"

Trim whitespace

echo '"  hello  "' | jpx 'trim(@)'
# "hello"

Dates

Working with timestamps - get current time, format for display, or calculate durations.

Current time

jpx -n 'now()'
# 1705312200 (Unix timestamp)

jpx -n 'format_date(now(), `"%Y-%m-%dT%H:%M:%SZ"`)'
# "2024-01-15T10:30:00Z"

Format a timestamp

jpx -n 'format_date(now(), `"%B %d, %Y"`)'
# "January 15, 2024"

# From a Unix timestamp
echo '1705312200' | jpx 'format_date(@, `"%Y-%m-%d"`)'
# "2024-01-15"

Add time to a timestamp

jpx -n 'date_add(now(), `7`, `"days"`) | format_date(@, `"%Y-%m-%d"`)'
# "2024-01-22" (7 days from now)

Working with Objects

Inspect and manipulate object structure - list keys, pick/omit fields, merge objects.

Get all keys

echo '{"a": 1, "b": 2, "c": 3}' | jpx 'keys(@)'
# ["a", "b", "c"]

Get all values

echo '{"a": 1, "b": 2, "c": 3}' | jpx 'values(@)'
# [1, 2, 3]

Pick specific keys

echo '{"a": 1, "b": 2, "c": 3}' | jpx 'pick(@, [`"a"`, `"c"`])'
# {"a": 1, "c": 3}

Omit specific keys

echo '{"a": 1, "b": 2, "c": 3}' | jpx 'omit(@, [`"b"`])'
# {"a": 1, "c": 3}

Merge objects

echo '{}' | jpx 'merge({a: `1`}, {b: `2`})'
# {"a": 1, "b": 2}

Cleaning Data

Real-world JSON is messy. These functions help you strip out nulls, empty strings, and other unwanted values.

Remove nulls

echo '{"a": 1, "b": null, "c": 3}' | jpx 'remove_nulls(@)'
# {"a": 1, "c": 3}

Remove empty strings

echo '{"a": "hello", "b": "", "c": "world"}' | jpx 'remove_empty_strings(@)'
# {"a": "hello", "c": "world"}

Compact (remove all empty values)

echo '{"a": 1, "b": null, "c": "", "d": [], "e": {}}' | jpx 'remove_empty(@)'
# {"a": 1}

Putting It Together

Chain these patterns together for real-world data processing tasks.

Extract, filter, transform, sort

echo '[
  {"name": "Alice", "dept": "eng", "salary": 100000},
  {"name": "Bob", "dept": "sales", "salary": 80000},
  {"name": "Carol", "dept": "eng", "salary": 120000}
]' | jpx '[?dept == `"eng"`] | sort_by(@, &salary) | [*].{name: name, salary: salary}'
# [{"name": "Alice", "salary": 100000}, {"name": "Carol", "salary": 120000}]

API response processing

curl -s 'https://api.github.com/users/octocat/repos' \
  | jpx '[?stargazers_count > `100`] | sort_by(@, &stargazers_count) | reverse(@) | [:5] | [*].{name: name, stars: stargazers_count}'

Log analysis

cat logs.json | jpx '[?level == `"error"`] | group_by(@, `"service"`) | map_values(`"length(@)"`, @)'
# {"api": 12, "worker": 3, "scheduler": 1}

Workflow Tips

jpx has built-in tools to help you build and reuse queries.

Explore data structure first

Before writing a query, see what you’re working with:

jpx --paths -f data.json
users (array)
users.0 (object)
users.0.name (string)
users.0.email (string)
users.0.role (string)
jpx --stats -f data.json
Type: object
Size: 2 keys
Depth: 3
Array fields:
  users: 150 items (object)
    Fields: name (100%), email (98%), role (100%), last_login (85%)

Find functions you need

Don’t memorize 400 functions - search for them:

jpx --search "date"
✓ Found 12 functions matching 'date':

  ▸ EXACT:
    date_add [datetime] - Add time to timestamp
    date_diff [datetime] - Difference between timestamps

  ▸ PREFIX:
    format_date [datetime] - Format timestamp as string
    parse_date [datetime] - Parse string to timestamp
    ...
jpx --describe format_date
format_date
===========
Category: datetime
Signature: number, string -> string
Description: Format a Unix timestamp as a string

Example:
  format_date(`1705312200`, '%Y-%m-%d') -> "2024-01-15"

Save queries to files

For complex or reusable queries, store them in a .jpx file:

-- queries.jpx

-- :name active-users
-- :description Get all active users with their email
users[?status == `"active"`].{name: name, email: email}

-- :name error-summary  
-- :description Count errors by service
[?level == `"error"`] | group_by(@, `"service"`) | map_values(`"length(@)"`, @)

Then run by name:

jpx -Q queries.jpx:active-users -f data.json

# List available queries
jpx -Q queries.jpx --list-queries
Available queries in queries.jpx:

  active-users
    Get all active users with their email

  error-summary
    Count errors by service

Debug complex expressions

When a query isn’t working, break it down:

jpx --explain 'users[?active].name | sort(@)'
Expression AST:
  Pipe
  ├─ Projection
  │  ├─ Field: users
  │  └─ Filter: active
  │     └─ Field: name
  └─ Function: sort
     └─ Current (@)

Benchmark performance

For queries you’ll run often, check how fast they are:

jpx --bench 1000 '[*].{id: id, name: upper(name)}' -f users.json
Benchmarking: [*].{id: id, name: upper(name)}
Iterations: 1000

  Mean:    0.234ms
  Median:  0.215ms
  p95:     0.312ms
  p99:     0.456ms

Throughput: 4,273 ops/sec

Output Formats

jpx supports multiple output formats beyond JSON, making it easy to integrate with other tools and workflows.

Available Formats

FlagShortDescription
--yaml-yYAML format
--tomlTOML format
--csvComma-separated values
--tsvTab-separated values
--lines-lOne JSON value per line (JSONL)
--table-tFormatted table (for arrays of objects)

YAML Output

YAML is great for human-readable configuration files and nested data structures.

# Simple object
echo '{"name": "alice", "age": 30}' | jpx '@' --yaml
age: 30
name: alice
# Nested structures
echo '{"server": {"host": "localhost", "port": 8080}}' | jpx '@' -y
server:
  host: localhost
  port: 8080
# Arrays
echo '[{"name": "alice"}, {"name": "bob"}]' | jpx '@' --yaml
- name: alice
- name: bob

Use Cases

  • Converting JSON configs to YAML for Kubernetes, Docker Compose, etc.
  • Human-readable output for debugging
  • Generating configuration files
# Extract and convert to YAML
jpx '[*].{name: name, role: role}' -f users.json --yaml > users.yaml

TOML Output

TOML is ideal for configuration files, especially in the Rust ecosystem.

# Simple object
echo '{"name": "myapp", "version": "1.0.0"}' | jpx '@' --toml
name = "myapp"
version = "1.0.0"
# Nested tables
echo '{"database": {"host": "localhost", "port": 5432}}' | jpx '@' --toml
[database]
host = "localhost"
port = 5432

Use Cases

  • Generating Cargo.toml snippets
  • Creating configuration files for Rust applications
  • Converting JSON API responses to TOML configs

CSV Output

CSV format is perfect for tabular data that needs to go into spreadsheets or databases.

# Array of objects becomes a table
echo '[{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]' | jpx '@' --csv
age,name
30,alice
25,bob

Nested Object Flattening

Nested objects are automatically flattened using dot notation:

echo '[
  {"name": "alice", "address": {"city": "NYC", "zip": "10001"}},
  {"name": "bob", "address": {"city": "LA", "zip": "90001"}}
]' | jpx '@' --csv
address.city,address.zip,name
NYC,10001,alice
LA,90001,bob

Arrays in Cells

Arrays are JSON-encoded within cells:

echo '[{"name": "alice", "tags": ["admin", "dev"]}]' | jpx '@' --csv
name,tags
alice,"[""admin"",""dev""]"

Single Objects

A single object is output as a single-row table:

echo '{"name": "alice", "role": "admin"}' | jpx '@' --csv
name,role
alice,admin

Array of Primitives

Arrays of primitives become single-column output (no header):

echo '[1, 2, 3, 4, 5]' | jpx '@' --csv
1
2
3
4
5

Use Cases

  • Exporting data for Excel or Google Sheets
  • Loading into databases
  • Generating reports
# Export user emails for a mailing list
jpx '[*].{email: email, name: name}' -f users.json --csv > contacts.csv

TSV Output

TSV (tab-separated values) works the same as CSV but uses tabs instead of commas. This is useful for:

  • Unix command-line tools that expect tab-separated input
  • Pasting directly into spreadsheets
  • Avoiding issues with commas in data
echo '[{"name": "alice", "city": "New York"}, {"name": "bob", "city": "Los Angeles"}]' | jpx '@' --tsv
city	name
New York	alice
Los Angeles	bob

Use Cases

# Pipe to awk for further processing
jpx '[*].{name: name, score: score}' -f data.json --tsv | awk -F'\t' '$2 > 90'

# Pipe to column for pretty printing
jpx '@' -f users.json --tsv | column -t -s $'\t'

Lines Output (JSONL)

The --lines flag outputs one JSON value per line, also known as JSON Lines (JSONL) or newline-delimited JSON (NDJSON).

echo '[1, 2, 3, "hello", {"key": "value"}]' | jpx '@' --lines
1
2
3
"hello"
{"key":"value"}
# Array of objects
echo '[{"id": 1}, {"id": 2}, {"id": 3}]' | jpx '@' -l
{"id":1}
{"id":2}
{"id":3}

Use Cases

  • Streaming processing with tools like jq, grep, or awk
  • Log file format
  • Parallel processing (each line is independent)
  • Piping to other JSON tools
# Process each line with another tool
jpx '[*]' -f events.json --lines | while read -r line; do
  echo "$line" | jpx '.timestamp'
done

# Filter with grep
jpx '@' -f logs.json --lines | grep '"level":"error"'

# Count items
jpx '[*]' -f data.json --lines | wc -l

Table Output

The --table flag renders arrays of objects as formatted tables, perfect for terminal display.

echo '[{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]' | jpx '@' --table
┌───────┬─────┐
│ name  │ age │
├───────┼─────┤
│ alice │ 30  │
│ bob   │ 25  │
└───────┴─────┘

Table Styles

Use --table-style to choose different table formats:

StyleDescription
unicodeDefault, uses box-drawing characters
asciiASCII characters only (+, -, `
markdownGitHub-flavored markdown tables
plainNo borders, space-separated
# ASCII style (for older terminals)
echo '[{"name": "alice", "age": 30}]' | jpx '@' -t --table-style ascii
+-------+-----+
| name  | age |
+-------+-----+
| alice | 30  |
+-------+-----+
# Markdown style (for documentation)
echo '[{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]' | jpx '@' -t --table-style markdown
| name  | age |
|-------|-----|
| alice | 30  |
| bob   | 25  |

Use Cases

  • Quick data inspection in the terminal
  • Generating markdown tables for documentation
  • Human-readable output for reports
# Display users as a table
jpx '[*].{name: name, email: email, role: role}' -f users.json --table

# Generate markdown documentation
jpx '[*].{Function: name, Description: description}' -f functions.json -t --table-style markdown > docs/functions.md

Combining with Expressions

Output formats work with any JMESPath expression:

# Extract specific fields and output as YAML
echo '[{"name": "alice", "age": 30, "email": "alice@example.com"}]' | \
  jpx '[*].{name: name, email: email}' --yaml
- email: alice@example.com
  name: alice
# Filter and export as CSV
jpx '[?status == `active`].{id: id, name: name}' -f users.json --csv
# Aggregate and output as TOML
jpx '{total: length(@), active: length([?active])}' -f users.json --toml
active = 42
total = 100

Output to Files

All output formats work with the -o flag:

# Save as YAML
jpx '@' -f config.json --yaml -o config.yaml

# Save as CSV
jpx '[*].{name: name, email: email}' -f users.json --csv -o contacts.csv

# Save as TOML
jpx '@' -f settings.json --toml -o settings.toml

Format Selection Tips

Use CaseRecommended Format
Human-readable config--yaml
Rust/Python config files--toml
Spreadsheet export--csv
Unix pipeline processing--tsv or --lines
Streaming/logging--lines
Database import--csv
API debugging--yaml
Terminal display--table
Markdown documentation--table --table-style markdown

Expression Syntax

JMESPath expression syntax reference.

Identifiers

Access object properties by name:

echo '{"name": "Alice", "age": 30}' | jpx 'name'
# "Alice"

Index Expressions

Access array elements by index (0-based):

echo '{"items": ["a", "b", "c"]}' | jpx 'items[0]'
# "a"

echo '{"items": ["a", "b", "c"]}' | jpx 'items[-1]'
# "c"

Slicing

Extract array slices with [start:stop:step]:

echo '[0, 1, 2, 3, 4, 5]' | jpx '[0:3]'
# [0, 1, 2]

echo '[0, 1, 2, 3, 4, 5]' | jpx '[::2]'
# [0, 2, 4]

echo '[0, 1, 2, 3, 4, 5]' | jpx '[::-1]'
# [5, 4, 3, 2, 1, 0]

Wildcard

Project all elements with *:

echo '{"users": [{"name": "Alice"}, {"name": "Bob"}]}' | jpx 'users[*].name'
# ["Alice", "Bob"]

Flatten

Flatten nested arrays with []:

echo '[[1, 2], [3, 4]]' | jpx '[]'
# [1, 2, 3, 4]

Filter Expressions

Filter arrays with [?expression]:

echo '[{"age": 25}, {"age": 17}, {"age": 30}]' | jpx '[?age > `18`]'
# [{"age": 25}, {"age": 30}]

echo '[{"name": "Alice", "active": true}, {"name": "Bob", "active": false}]' | jpx '[?active].name'
# ["Alice"]

Comparison Operators

OperatorDescription
==Equal
!=Not equal
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal

Logical Operators

OperatorDescription
&&Logical AND
||Logical OR
!Logical NOT

Pipe Expressions

Chain expressions with |:

echo '{"items": [3, 1, 2]}' | jpx 'items | sort(@) | reverse(@)'
# [3, 2, 1]

Multi-Select List

Create arrays from multiple expressions:

echo '{"a": 1, "b": 2, "c": 3}' | jpx '[a, b]'
# [1, 2]

Multi-Select Hash

Create objects from multiple expressions:

echo '{"first": "Alice", "last": "Smith", "age": 30}' | jpx '{name: first, surname: last}'
# {"name": "Alice", "surname": "Smith"}

Literal Values

Use backticks for literal values:

# Literal number
echo '{"items": [1, 2, 3]}' | jpx '[?@ > `1`]'

# Literal string
echo '{"items": ["a", "b"]}' | jpx 'contains(items, `"a"`)'

# Literal array
echo '{}' | jpx '`[1, 2, 3]`'

# Literal object
echo '{}' | jpx '`{"key": "value"}`'

Current Node

Reference the current node with @:

echo '[1, 2, 3, 4, 5]' | jpx '[?@ > `2`]'
# [3, 4, 5]

echo '"hello"' | jpx 'upper(@)'
# "HELLO"

Function Calls

Call functions with function_name(arg1, arg2, ...):

echo '{"items": [1, 2, 3]}' | jpx 'length(items)'
# 3

echo '{"name": "hello"}' | jpx 'upper(name)'
# "HELLO"

echo '{"a": [1, 2], "b": [3, 4]}' | jpx 'merge(a, b)'
# [1, 2, 3, 4]

Functions can take literal JSON values as arguments using backticks:

# Apply default values to an object
echo '{"name": "Alice"}' | jpx 'defaults(@, `{"role": "user", "active": true}`)'
# {"name": "Alice", "role": "user", "active": true}

# Build an object from key-value pairs
echo 'null' | jpx 'from_items(`[["a", 1], ["b", 2]]`)'
# {"a": 1, "b": 2}

Expression References

Use & for expression references (used with higher-order functions):

echo '[{"age": 30}, {"age": 20}]' | jpx 'sort_by(@, &age)'
# [{"age": 20}, {"age": 30}]

echo '[1, 2, 3]' | jpx 'map(&@ * `2`, @)'
# [2, 4, 6]

String Literals and Escaping

This guide covers how to properly write string literals, regex patterns, and special characters in JMESPath expressions. Understanding escaping is crucial for working with split(), replace(), regex functions, and any operation involving literal strings.

The Golden Rule

In JMESPath:

  • Single quotes '...' = Identifiers (field names)
  • Backticks with JSON `"..."` = String literals
# WRONG - 'hello' is an identifier (looks for a field named "hello")
echo '{"hello": "world"}' | jpx "'hello'"
# null (unless there's a field whose value equals the string "hello")

# RIGHT - `"hello"` is a string literal
echo '{"data": "hello"}' | jpx 'data == `"hello"`'
# true

String Literals Quick Reference

What you wantJMESPath syntaxShell example
String hello`"hello"`jpx 'length(“hello”)' --null-input
String with spaces`"hello world"`jpx 'length(“hello world”)' --null-input
Empty string`""`jpx '@ == “”' --null-input
Number 42`42`jpx '42+8' --null-input
Boolean`true` or `false`jpx 'true' --null-input
Null`null`jpx 'null' --null-input

Special Characters

Newlines, Tabs, and Escapes

Inside backtick literals, use standard JSON escape sequences:

CharacterJMESPath syntaxExample
Newline`"\n"`split(@, “\n”)
Tab`"\t"`split(@, “\t”)
Carriage return`"\r"`replace(@, “\r”, “”)
Backslash`"\\"`split(@, “\”)
Double quote`"\""`contains(@, “"”)

Example: Splitting by Newlines

# Split multi-line text into array
echo '"line1\nline2\nline3"' | jpx 'split(@, `"\n"`)'
# ["line1", "line2", "line3"]

Example: Working with Tabs

# Split TSV (tab-separated values)
echo '"name\tage\tcity"' | jpx 'split(@, `"\t"`)'
# ["name", "age", "city"]

Regex Patterns

Regex functions (regex_match, regex_extract, regex_replace, etc.) need their patterns as string literals.

Regex Quick Reference

Regex patternJMESPath syntaxWhat it matches
\d+ (digits)`"\\d+"`One or more digits
\w+ (word chars)`"\\w+"`Word characters
\s+ (whitespace)`"\\s+"`Whitespace
[a-z]+`"[a-z]+"`Lowercase letters
^start`"^start"`Starts with “start”
end$`"end$"`Ends with “end”

Key insight: Backslash escapes in regex patterns need to be escaped for JSON, so \d becomes \\d inside the backtick literal.

Example: Extract Numbers

echo '"Order #12345"' | jpx 'regex_extract(@, `"\\d+"`)'
# "12345"

Example: Match Email Pattern

echo '"contact@example.com"' | jpx 'regex_match(@, `"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"`)'
# true

Example: Replace Whitespace

echo '"hello   world"' | jpx 'regex_replace(@, `"\\s+"`, `" "`)'
# "hello world"

Shell Escaping Layers

When running jpx from a shell, you have multiple escaping layers:

  1. Shell interprets your command line
  2. JMESPath parses the expression
  3. JSON (inside backticks) parses the literal
  4. Regex engine (for regex functions) interprets the pattern

Shell Quoting Strategies

Single quotes (recommended) - Shell doesn’t interpret anything:

# Best: Use single quotes around the entire expression
echo '{"text": "hello"}' | jpx 'regex_match(text, `"\\w+"`)'

Double quotes - Need to escape backticks and backslashes:

# Harder: Must escape backticks and double backslashes
echo '{"text": "hello"}' | jpx "regex_match(text, \`\"\\\\w+\"\`)"

Recommendation

Always use single quotes around your JMESPath expression when possible:

jpx 'your_expression_here'

This lets you write backticks and backslashes without shell interference.

Common Mistakes

Mistake 1: Using Single Quotes for Strings

# WRONG - 'hello' is an identifier
echo '{"x": "hello"}' | jpx "x == 'hello'"

# RIGHT - use backtick literal
echo '{"x": "hello"}' | jpx 'x == `"hello"`'

Mistake 2: Forgetting Quotes Inside Backticks

# WRONG - `hello` is not valid JSON
jpx '`hello`' --null-input

# RIGHT - strings need quotes inside backticks
jpx '`"hello"`' --null-input

Mistake 3: Over-escaping Regex Patterns

# WRONG - too many backslashes
echo '"abc123"' | jpx 'regex_extract(@, `"\\\\d+"`)'

# RIGHT - just escape once for JSON
echo '"abc123"' | jpx 'regex_extract(@, `"\\d+"`)'

Mistake 4: Confusing Identifier vs String

# This looks for field named "name" - CORRECT
echo '{"name": "Alice"}' | jpx 'name'
# "Alice"

# This compares against literal string - CORRECT
echo '{"name": "Alice"}' | jpx 'name == `"Alice"`'
# true

# This looks for field named by VALUE of 'name' field - WRONG (usually)
echo '{"name": "Alice"}' | jpx "'name'"
# null (looks for field "name" on string "Alice")

MCP Server Context

When using jpx through the MCP server (e.g., with Claude), there’s an additional JSON encoding layer. The MCP server handles this automatically, but be aware:

  • Expressions are passed as JSON strings
  • The server unescapes before evaluating
  • When writing expressions in MCP tool calls, you typically don’t need extra escaping

Example MCP Tool Call

{
  "name": "jpx_query",
  "arguments": {
    "expression": "split(@, `\"\\n\"`)",
    "data": "line1\nline2\nline3"
  }
}

The MCP server handles the JSON string escaping, so \" becomes " and \\n becomes \n in the actual expression.

Debugging Tips

  1. Use --null-input to test expressions without needing input data:

    jpx '`"test\\nstring"`' --null-input
    
  2. Start simple - verify your literal works before using it in a function:

    # First, check the literal
    jpx '`"\\d+"`' --null-input
    # "\d+"
    
    # Then use it
    echo '"abc123"' | jpx 'regex_extract(@, `"\\d+"`)'
    
  3. Check the AST for complex expressions:

    jpx --ast 'split(@, `"\n"`)'
    

Summary

ContextSyntaxExample
Field accessfieldnameuser.name
String literal`"string"``"hello"`
Newline in string`"\n"`split(@, “\n”)
Regex digit`"\\d+"`regex_match(@, “\d+”)
Comparisonfield == “value”``status == “active”``

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

Use -f or --file to read from a file:

jpx -f data.json 'users[*].name'

jpx --file config.json 'settings.theme'

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:

jpx -f data.json 'users[0]'
# {
#   "name": "alice",
#   "age": 30
# }

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

jpx -c -f data.json 'users[0]'
# {"name":"alice","age":30}

Processing Multiple Files

Sequential Processing

Process files one at a time in a loop:

for f in *.json; do
  jpx -f "$f" 'metadata.title'
done

Combining Files with Slurp

Use -s or --slurp to combine multiple JSON values into an array:

cat file1.json file2.json file3.json | jpx -s 'length(@)'
# 3

cat *.json | jpx -s '[*].name'

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
cat logs.ndjson | jpx --stream '[?level == `error`].message'

Streaming mode uses constant memory regardless of file size.

Query Files

Store queries in files for reuse:

Plain Text Query Files

# query.txt contains: users[?active].{name: name, email: email}
jpx -Q query.txt -f data.json

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

jpx --diff original.json modified.json > changes.patch

Apply Patches

Apply a patch to a document:

jpx --patch changes.patch -f document.json -o updated.json

Merge Patches

Apply a JSON Merge Patch (RFC 7396):

jpx --merge updates.json -f document.json

Tips

Checking File Validity

Use --stats to quickly validate and analyze a JSON file:

jpx --stats -f data.json

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:

jpx --bench 1000 -f large-data.json 'complex[*].transformation'

Query Files

Query files let you store JMESPath expressions externally, making them reusable, version-controlled, and easier to maintain. jpx supports two formats:

  1. Simple query files - A single expression in a plain text file
  2. Query libraries (.jpx) - Multiple named queries in one file

Simple Query Files

The simplest approach: put your expression in a file and reference it with -Q:

# Create a query file
echo 'users[?active].{name: name, email: email}' > active-users.txt

# Use it
jpx -Q active-users.txt data.json

This is useful for:

  • Long, complex expressions you don’t want to retype
  • Sharing queries across scripts
  • Version-controlling important queries

Query Libraries (.jpx)

For projects with multiple related queries, use a .jpx query library file. This format lets you define named queries with optional descriptions:

-- :name active-users
-- :desc Get all active users with their contact info
users[?active].{name: name, email: email}

-- :name admin-emails
-- :desc Extract just the admin email addresses  
users[?role == `admin`].email

-- :name user-stats
-- :desc Summary statistics about users
{
  total: length(users),
  active: length(users[?active]),
  admins: length(users[?role == `admin`])
}

File Format

  • -- :name <name> starts a new query (required)
  • -- :desc <description> adds a description (optional)
  • -- other comment lines are ignored
  • Everything else until the next -- :name is the query expression
  • Multi-line expressions are supported

Using Query Libraries

There are two ways to run a query from a library:

# Colon syntax (concise)
jpx -Q queries.jpx:active-users data.json

# Separate flag (explicit)
jpx -Q queries.jpx --query active-users data.json

Both are equivalent. Use whichever feels more natural.

Listing Available Queries

See what queries are in a library:

jpx -Q queries.jpx --list-queries

Output:

Queries in queries.jpx:

  NAME          DESCRIPTION
  -----------   ----------------------------------------
  active-users  Get all active users with their contact info
  admin-emails  Extract just the admin email addresses
  user-stats    Summary statistics about users

Use: jpx -Q queries.jpx:<query-name> <input>

Validating Queries

Check that all queries in a library are syntactically valid:

jpx -Q queries.jpx --check

Output:

Validating queries.jpx...

  ✓ active-users
  ✓ admin-emails
  ✓ user-stats

All queries valid.

This is useful in CI pipelines to catch syntax errors before deployment.

Real-World Examples

NLP Analysis Library

Create reusable text processing pipelines:

-- :name clean-html
-- :desc Strip HTML tags and normalize whitespace
regex_replace(@, `<[^>]+>`, ` `) | collapse_whitespace(@)

-- :name extract-keywords
-- :desc Get top keywords from text (stemmed, no stopwords)
tokens(@) | remove_stopwords(@) | stems(@) | frequencies(@)

-- :name title-keywords
-- :desc Extract keywords from article titles
hits[*].title | join(` `, @) | tokens(@) | remove_stopwords(@) | stems(@) | frequencies(@)

-- :name reading-stats
-- :desc Get reading time and word count
{
  word_count: word_count(@),
  reading_time: reading_time(@),
  sentence_count: sentence_count(@)
}

Use it:

# Clean HTML from a field
jpx 'story_text' data.json | jpx -Q nlp.jpx:clean-html

# Analyze Hacker News titles
jpx -Q nlp.jpx:title-keywords hn_front.json

API Response Processing

Standardize how you extract data from APIs:

-- :name github-repos
-- :desc Extract repo summary from GitHub API response
[*].{
  name: name,
  stars: stargazers_count,
  language: language,
  description: description | default(@, `"No description"`)
}

-- :name github-issues
-- :desc Format GitHub issues for display  
[*].{
  number: number,
  title: title,
  state: state,
  author: user.login,
  labels: labels[*].name | join(`, `, @)
}

-- :name paginated-total
-- :desc Get total from paginated API response
{
  count: length(items),
  total: total_count,
  has_more: length(items) < total_count
}

Data Transformation Library

Common transformations for ETL pipelines:

-- :name flatten-nested
-- :desc Flatten nested user records for CSV export
[*].{
  id: id,
  name: profile.name,
  email: profile.email,
  city: profile.address.city,
  country: profile.address.country,
  created: metadata.created_at
}

-- :name aggregate-by-status
-- :desc Group and count records by status
group_by(@, &status) | map(&{ status: [0].status, count: length(@) }, @)

-- :name enrich-timestamps
-- :desc Add formatted date fields
[*] | map(&merge(@, {
  created_date: format_datetime(created_at, `%Y-%m-%d`),
  created_time: format_datetime(created_at, `%H:%M:%S`)
}), @)

Log Analysis Library

Queries for processing structured logs:

-- :name errors-only
-- :desc Filter to just error-level logs
[?level == `error` || level == `ERROR`]

-- :name errors-by-service
-- :desc Count errors grouped by service name
[?level == `error`] | group_by(@, &service) | map(&{ service: [0].service, count: length(@) }, @)

-- :name recent-errors
-- :desc Errors from the last hour with context
[?level == `error`] | sort_by(@, &timestamp) | reverse(@) | [:20].{
  time: timestamp,
  service: service,
  message: message,
  trace_id: trace_id
}

-- :name slow-requests
-- :desc Requests taking longer than 1 second
[?duration_ms > `1000`] | sort_by(@, &duration_ms) | reverse(@)

Best Practices

Naming Conventions

Use clear, descriptive names:

  • active-users not au or query1
  • errors-by-service not err-svc
  • Use kebab-case for consistency

Add Descriptions

Always add -- :desc lines. They show up in --list-queries and help others (and future you) understand what each query does.

Organize by Domain

Group related queries into domain-specific libraries:

  • nlp.jpx - Text processing pipelines
  • api.jpx - API response transformations
  • logs.jpx - Log analysis queries
  • etl.jpx - Data transformation queries

Version Control

Query libraries are plain text files - perfect for git:

  • Track changes to important queries
  • Review query changes in PRs
  • Share queries across your team

Validate in CI

Add query validation to your CI pipeline:

# .github/workflows/validate.yml
- name: Validate query libraries
  run: |
    for f in queries/*.jpx; do
      jpx -Q "$f" --check || exit 1
    done

CLI Reference

OptionDescription
-Q, --query-file <FILE>Load expression from file
--query <NAME>Select a named query from a .jpx library
--list-queriesList all queries in a .jpx file
--checkValidate all queries without running

Colon Syntax

The colon syntax -Q file.jpx:query-name is shorthand for -Q file.jpx --query query-name.

Detection Logic

jpx automatically detects query libraries:

  1. Files ending in .jpx are always treated as libraries
  2. Files starting with -- :name are treated as libraries
  3. Everything else is a simple single-query file

Migration from Simple Files

If you have many simple query files, consolidate them:

# Before: multiple files
queries/
  active-users.txt
  admin-emails.txt
  user-stats.txt

# After: one library
queries/users.jpx

Just add -- :name headers to combine them:

-- :name active-users
users[?active].{name: name, email: email}

-- :name admin-emails
users[?role == `admin`].email

-- :name user-stats
{total: length(users), active: length(users[?active])}

Strict Mode

Strict mode disables all extension functions, limiting jpx to only the 26 standard JMESPath functions. This is useful for writing portable queries that work with any JMESPath implementation.

Enabling Strict Mode

Use the --strict flag or set the JPX_STRICT=1 environment variable:

# Via flag
echo '{"name": "alice"}' | jpx --strict 'upper(name)'
# Error: Unknown function: upper

# Via environment variable
export JPX_STRICT=1
echo '{"items": [1, 2, 3]}' | jpx 'length(items)'
# 3

Standard JMESPath Functions

In strict mode, only these functions are available:

FunctionDescription
abs(n)Absolute value
avg(array)Average of numbers
ceil(n)Round up
contains(subject, search)Check if contains value
ends_with(str, suffix)Check string suffix
floor(n)Round down
join(glue, array)Join strings
keys(obj)Object keys
length(subject)Length of array/string/object
map(&expr, array)Transform each element
max(array)Maximum value
max_by(array, &expr)Maximum by expression
merge(obj1, obj2, ...)Merge objects
min(array)Minimum value
min_by(array, &expr)Minimum by expression
not_null(arg1, arg2, ...)First non-null value
reverse(array)Reverse array
sort(array)Sort array
sort_by(array, &expr)Sort by expression
starts_with(str, prefix)Check string prefix
sum(array)Sum of numbers
to_array(arg)Convert to array
to_number(arg)Convert to number
to_string(arg)Convert to string
type(arg)Get type name
values(obj)Object values

Use Cases

Portable Queries

If you’re writing queries that need to work across different JMESPath implementations (AWS CLI, Python jmespath, etc.), use strict mode to ensure compatibility:

# Test your query works with standard JMESPath
jpx --strict '[?status == `active`].name | sort(@)'

CI/CD Validation

Ensure queries in your codebase don’t accidentally use extensions:

# In your CI pipeline
jpx --strict -Q queries.jpx --check

Gradual Migration

When migrating from another JMESPath tool, start in strict mode and gradually adopt extensions as needed.

Checking if an Expression Uses Extensions

Use --explain to see what functions an expression uses:

jpx --explain 'users[*].name | unique(@) | sort(@)'

The unique function is an extension, while sort is standard.

MCP Server Strict Mode

The MCP server also supports strict mode. When configured with --strict, only standard JMESPath functions are available:

{
  "mcpServers": {
    "jpx-strict": {
      "command": "jpx-server",
      "args": ["--strict"]
    }
  }
}

Why jpx?

“I could just write Python or JavaScript for this - why use jpx?”

This is a fair question. Here’s why jpx is worth learning for JSON wrangling.

Almost No Syntax

JMESPath’s entire syntax fits on an index card:

SyntaxExampleMeaning
Dot accessfoo.barGet nested field
Brackets[0], [*], [?filter]Index, project, filter
Pipea | bChain expressions
Functionsname(args)Call a function
Literals`"string"`, `123`, `true`Literal values

That’s it. No for, if, def, import, class, let, const, =>, async, await

You can learn the entire language in 10 minutes. The power comes from composing simple pieces, not memorizing syntax - the same insight that makes Lisp and shell pipelines compelling.

Compare to Python where “I want to extract some fields” requires knowing:

  • Variable assignment
  • Import statements
  • List comprehensions (or for loops)
  • Dictionary access
  • String formatting
  • File I/O

Zero Boilerplate

No imports. No script files. No virtual environments. Just pipe and go:

curl -s https://api.example.com/users | jpx 'users[*].name'

Compare to the Python equivalent:

import json
import sys

data = json.load(sys.stdin)
names = [user["name"] for user in data["users"]]
print(json.dumps(names))

That’s 5 lines (plus saving to a file, making it executable, etc.) vs. a single shell command.

Composable One-Liners

Shell pipelines are underrated. With jpx, you can do complex transformations in a single expression:

# Fetch, filter, transform, and sort in one line
curl -s https://api.github.com/users/octocat/repos \
  | jpx '[?stargazers_count > `100`] | sort_by(@, &stargazers_count) | reverse(@) | [*].{name: name, stars: stargazers_count}'

Or break it into multiple jpx calls when debugging:

curl -s https://api.github.com/users/octocat/repos \
  | jpx '[?stargazers_count > `100`]' \
  | jpx 'sort_by(@, &stargazers_count) | reverse(@) | [*].{name: name, stars: stargazers_count}'

Each step is independently testable. Add or remove transformations without rewriting a script.

Declarative vs Imperative

You describe what you want, not how to loop through it:

Imperative (Python):

result = []
for item in data["items"]:
    if item["active"]:
        result.append({"n": item["name"], "v": item["value"]})

Declarative (jpx):

jpx 'items[?active].{n: name, v: value}'

The jpx version is often more readable because it expresses intent directly.

The Learning Curve Comparison

TaskPythonjpx
Get nested fielddata["users"][0]["name"]users[0].name
Filter array[x for x in items if x["active"]]items[?active]
Transform all[{"n": x["name"]} for x in items]items[*].{n: name}
Chain operationsMultiple lines or nested callsa | b | c
Sort by fieldsorted(items, key=lambda x: x["age"])sort_by(items, &age)

Consistent Interface

Same syntax across:

  • CLI (jpx 'query')
  • MCP server (for AI assistants)
  • Python bindings (jmespath_extensions.search('query', data))
  • Rust library

Learn once, use everywhere.

No Runtime Dependencies

jpx is a single ~15MB binary. It works on any machine without Python, Node, or anything else installed.

  • Great for CI/CD pipelines
  • Works in minimal containers
  • No “works on my machine” issues
  • No dependency conflicts

Built for Exploration

When you’re exploring an unfamiliar API response, jpx helps you discover what’s there:

# Interactive REPL
jpx --repl

# See all paths in the data
jpx --paths -f data.json

# Search for functions
jpx --search "date"

# Get help on any function
jpx --describe format_date

# Find similar functions
jpx --similar levenshtein

Python requires knowing what to import before you start. jpx lets you explore first.

Safe for Automation

JMESPath expressions are pure functions with no side effects:

  • No “oops I overwrote my file” moments
  • No network calls from within queries
  • No shell injection vulnerabilities
  • Predictable, deterministic behavior

This makes jpx safe to use in scripts and automation.

When to Use What

jpx is for ad-hoc JSON exploration and transformation:

  • “What’s in this API response?”
  • “Extract these fields from 50 log files”
  • “Quick data munging in a shell script”
  • “Prototype a transformation before implementing it”

Use jpx when:

  • Quick field extraction from JSON
  • Ad-hoc API exploration
  • Shell script data processing
  • CI/CD pipeline JSON manipulation
  • You need to explain the query to someone else

Use Python/JavaScript when:

  • Complex business logic with many conditionals
  • Stateful transformations
  • Database operations
  • Production ETL pipelines
  • You need to integrate with other services

The sweet spot: use jpx for the JSON transformation step inside larger Python/JS applications:

import jmespath_extensions

# Complex Python logic here...
data = fetch_from_api()

# Let jpx handle the transformation
result = jmespath_extensions.search('items[?active].{id: id, name: upper(name)}', data)

# More Python logic...
save_to_database(result)

Coming from jq?

The key syntax differences:

jqjpx
.[].name[*].name
select(.age > 30)[?age > \30`]`
lengthlength(@)

jpx has more built-in functions (400+). jq is better for complex recursive transformations.

Summary

jpx trades generality for ergonomics. It can’t do everything Python can, but for its intended purpose - querying and transforming JSON - it’s faster to write, easier to read, and simpler to maintain.

The 10 minutes you spend learning JMESPath will save you hours of writing boilerplate data munging code.

MCP Server Overview

jpx can run as an MCP (Model Context Protocol) server, allowing AI assistants like Claude to use JMESPath for JSON querying and transformation.

What is MCP?

MCP is an open protocol that enables AI assistants to interact with external tools and data sources. By running jpx as an MCP server, you give Claude (and other MCP-compatible assistants) the ability to:

  • Query JSON data using JMESPath expressions
  • Transform and manipulate JSON structures
  • Use all 400+ extension functions
  • Explore available functions and their documentation

Why Use jpx with Claude?

When working with JSON data in Claude, jpx provides:

  • Precise queries: Extract exactly the data you need
  • Complex transformations: Reshape data structures on the fly
  • Powerful functions: String manipulation, math, dates, hashing, and more
  • Consistent results: Deterministic query execution

Available Tools

The MCP server exposes 25 tools organized by purpose:

Function Discovery Tools

Tools for finding and exploring available JMESPath functions:

ToolDescription
searchFuzzy search for functions by name, description, category, or signature
similarFind functions related to a specified function
functionsList available functions (optionally filter by category)
describeGet detailed info for a specific function
categoriesList all function categories

Multi-Server Tool Discovery

Tools for semantic search across multiple MCP servers (see Discovery):

ToolDescription
register_discoveryRegister an MCP server’s tools for indexing
query_toolsBM25 semantic search across registered tools
similar_toolsFind tools related to a specific tool
list_discovery_serversList registered servers
list_discovery_categoriesList tool categories across servers
inspect_discovery_indexDebug index statistics
unregister_discoveryRemove a server from the registry
get_discovery_schemaGet the registration schema

Data Analysis Tools

Tools for understanding JSON structure before querying:

ToolDescription
statsAnalyze JSON structure (type, size, depth, field analysis)
pathsExtract all paths in dot notation (e.g., users[0].name)
keysExtract object keys (optionally recursive with dot notation)

Query Tools

Tools for evaluating JMESPath expressions:

ToolDescription
evaluateRun a JMESPath expression against JSON input
evaluate_fileQuery JSON files directly from disk
batch_evaluateRun multiple expressions against the same input
validateCheck expression syntax without executing

JSON Utility Tools

Tools for JSON manipulation (RFC 6902/7396):

ToolDescription
formatPretty-print JSON with configurable indentation
diffGenerate RFC 6902 JSON Patch between documents
patchApply RFC 6902 JSON Patch operations
mergeApply RFC 7396 JSON Merge Patch

Typical Workflow for AI Agents

  1. Analyze data: Use stats and paths to understand the JSON structure
  2. Discover functions: Use search to find relevant functions, similar to explore alternatives
  3. Build query: Use validate to check syntax before executing
  4. Execute: Use evaluate or batch_evaluate to run queries
  5. Transform: Use diff, patch, or merge for modifications

Getting Started

See Setup to configure jpx as an MCP server for Claude Desktop.

Setup with Claude Desktop

Configure jpx-server as an MCP server for Claude Desktop.

Prerequisites

  1. Install jpx-server:

    cargo install jpx-server
    

    Or use Docker (no installation required).

  2. Have Claude Desktop installed

Configuration

The simplest way to run jpx as an MCP server using the dedicated server image:

{
  "mcpServers": {
    "jpx": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "ghcr.io/joshrotenberg/jpx-server"]
    }
  }
}

Available for linux/amd64 and linux/arm64.

macOS

Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "jpx": {
      "command": "jpx-server"
    }
  }
}

If jpx-server is not in your PATH, use the full path:

{
  "mcpServers": {
    "jpx": {
      "command": "/Users/yourname/.cargo/bin/jpx-server"
    }
  }
}

Windows

Edit %APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "jpx": {
      "command": "C:\\path\\to\\jpx-server.exe"
    }
  }
}

Verify Setup

  1. Restart Claude Desktop
  2. Look for the jpx tools in Claude’s tool list
  3. Try a simple query:
User: I have this JSON: {"users": [{"name": "alice"}, {"name": "bob"}]}
      Get all the names.

Claude: [Uses jpx.evaluate]
        Result: ["alice", "bob"]

Strict Mode

To use only standard JMESPath functions (no extensions):

{
  "mcpServers": {
    "jpx": {
      "command": "jpx-server",
      "args": ["--strict"]
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "jpx": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "ghcr.io/joshrotenberg/jpx-server", "--strict"]
    }
  }
}

Troubleshooting

jpx-server not found

Make sure jpx-server is in your PATH, or use the full path in the config.

Tools not appearing

  1. Check the config file syntax (must be valid JSON)
  2. Restart Claude Desktop completely
  3. Check Claude Desktop logs for errors

Permission errors on file access

The evaluate_file tool has security restrictions. It only allows access to files in safe directories (not system paths).

Available Tools

The jpx MCP server provides 26 tools organized into five categories.

Function Discovery

Tools for exploring the 400+ JMESPath extension functions.

Fuzzy search for functions by name, description, category, or signature.

{
  "query": "string manipulation",
  "limit": 10
}

similar

Find functions related to a specified function.

{
  "function": "upper",
  "limit": 5
}

Returns functions with similar purpose (e.g., lower, capitalize, title_case).

functions

List available functions, optionally filtered by category.

{
  "category": "string"
}

describe

Get detailed documentation for a specific function.

{
  "function": "pad_left"
}

Returns signature, description, parameters, return type, and examples.

categories

List all function categories with tool counts.

{}

Data Analysis

Tools for understanding JSON structure before writing queries.

stats

Analyze JSON structure and statistics.

{
  "json": {"users": [{"name": "Alice"}, {"name": "Bob"}]}
}

Returns type, size, depth, and field analysis.

paths

Extract all paths in a JSON document using dot notation.

{
  "json": {"user": {"profile": {"name": "Alice"}}}
}

Returns: ["user", "user.profile", "user.profile.name"]

keys

Extract object keys, optionally recursive with dot notation.

{
  "json": {"a": {"b": 1}},
  "recursive": true
}

Query Execution

Tools for running JMESPath expressions.

evaluate

Run a JMESPath expression against JSON input.

{
  "expression": "users[?age > `30`].name",
  "json": {"users": [{"name": "Alice", "age": 35}, {"name": "Bob", "age": 25}]}
}

Returns: ["Alice"]

evaluate_file

Query a JSON file directly from disk. Useful when the file is too large to pass inline or when you want to avoid serialization overhead.

{
  "expression": "length(items)",
  "path": "/path/to/data.json"
}

batch_evaluate

Run multiple expressions against the same input.

{
  "expressions": ["length(@)", "keys(@)", "@.name"],
  "json": {"name": "test", "value": 42}
}

validate

Check expression syntax without executing.

{
  "expression": "users[?active].name"
}

Returns validation status and any syntax errors.

JSON Utilities

Tools for JSON manipulation following RFC standards (RFC 6902 for JSON Patch, RFC 7396 for Merge Patch).

format

Pretty-print JSON with configurable indentation.

{
  "json": {"compact": true},
  "indent": 2
}

diff

Generate RFC 6902 JSON Patch between two documents.

{
  "source": {"a": 1},
  "target": {"a": 2, "b": 3}
}

Returns patch operations: [{"op": "replace", "path": "/a", "value": 2}, {"op": "add", "path": "/b", "value": 3}]

patch

Apply RFC 6902 JSON Patch operations.

{
  "json": {"a": 1},
  "operations": [{"op": "add", "path": "/b", "value": 2}]
}

merge

Apply RFC 7396 JSON Merge Patch.

{
  "json": {"a": 1, "b": 2},
  "patch": {"b": null, "c": 3}
}

Returns: {"a": 1, "c": 3} (null removes keys)

Multi-Server Discovery

Tools for semantic search across multiple MCP servers. See Multi-Server Discovery for full documentation.

register_discovery

Register an MCP server’s tools for BM25 indexing using the full discovery spec.

{
  "spec": {
    "server": {"name": "myserver", "version": "1.0.0"},
    "tools": [
      {"name": "tool_one", "description": "Does something useful", "category": "utils"}
    ]
  }
}

register_tools_simple

Quick registration without the full schema. Just provide server name and tools array.

{
  "server_name": "myserver",
  "tools": [
    {"name": "backup_db", "description": "Backup the database", "tags": ["database", "backup"]},
    {"name": "restore_db", "description": "Restore from backup", "tags": ["database", "restore"]}
  ]
}

query_tools

Semantic search across all registered tools.

{
  "query": "backup database",
  "top_k": 5
}

Returns ranked results with BM25 scores.

similar_tools

Find tools related to a specific tool.

{
  "tool_id": "myserver:tool_one",
  "top_k": 5
}

list_discovery_servers

List all registered servers with tool counts.

{}

list_discovery_categories

List tool categories across all registered servers.

{}

inspect_discovery_index

Get index statistics for debugging.

{}

Returns server count, tool count, term count, and average document length.

unregister_discovery

Remove a server from the registry.

{
  "server": "myserver"
}

get_discovery_schema

Get the JSON schema for registration.

{}

Multi-Server Tool Discovery

jpx provides BM25-based semantic search for discovering tools across multiple MCP servers. This enables natural language queries like “how do I backup a database?” instead of scanning flat tool lists.

The Problem

When an agent connects to multiple MCP servers, it has access to dozens or hundreds of tools. Finding the right tool for a task means:

  • Scanning tool names hoping for a match
  • Reading descriptions one by one
  • Missing tools with different terminology (“backup” vs “snapshot” vs “export”)

jpx provides a discovery registry that indexes tools from any MCP server using BM25 (the algorithm behind search engines). The workflow:

  1. Register: Agent registers each server’s tools with jpx
  2. Search: Agent queries across all registered tools semantically
  3. Discover: BM25 finds relevant tools even with different wording

Architecture

MCP servers don’t communicate with each other - they only respond to the agent. The agent orchestrates registration:

┌─────────┐     ┌─────────────┐     ┌─────────────┐
│  Agent  │────▶│     jpx     │     │  redisctl   │
│         │────▶│  (registry) │     │  (65 tools) │
│         │────▶│             │     └─────────────┘
│         │     └─────────────┘     ┌─────────────┐
│         │────────────────────────▶│ other-mcp   │
│         │                         │  (N tools)  │
└─────────┘                         └─────────────┘
     │
     │  1. Connect to all servers
     │  2. Get tool metadata from each
     │  3. Register with jpx
     │  4. Query jpx for tool discovery
     ▼

Discovery Tools

Registration

ToolDescription
register_discoveryRegister an MCP server’s tools
unregister_discoveryRemove a server from the registry
get_discovery_schemaGet the registration schema

Search

ToolDescription
query_toolsBM25 semantic search across all registered tools
similar_toolsFind tools related to a specific tool

Browse

ToolDescription
list_discovery_serversList all registered servers
list_discovery_categoriesList tool categories across servers
inspect_discovery_indexDebug index statistics

Registration Schema

Register a server’s tools with register_discovery:

{
  "spec": {
    "server": {
      "name": "redisctl",
      "version": "0.1.0",
      "description": "Redis management tools"
    },
    "tools": [
      {
        "name": "enterprise_database_create",
        "description": "Create a new Redis Enterprise database with specified configuration",
        "category": "enterprise",
        "subcategory": "database",
        "tags": ["database", "create", "write"],
        "examples": ["Create a 500MB database named 'cache'"]
      },
      {
        "name": "enterprise_database_backup",
        "description": "Trigger a backup snapshot of a Redis Enterprise database",
        "category": "enterprise",
        "subcategory": "database",
        "tags": ["database", "backup", "snapshot", "read"]
      }
    ],
    "categories": {
      "enterprise": {
        "description": "Redis Enterprise on-premises tools",
        "tool_count": 30
      }
    }
  },
  "replace": false
}

Minimal Schema

Only server.name and tools[].name are required:

{
  "spec": {
    "server": { "name": "myserver" },
    "tools": [
      { "name": "tool_one" },
      { "name": "tool_two" }
    ]
  }
}

Full Schema

For best search results, include rich metadata:

  • description: Natural language description (indexed for search)
  • category / subcategory: For browsing and filtering
  • tags: Keywords that improve search relevance
  • examples: Usage examples (indexed for search)
  • inputSchema: Parameter documentation

Searching

// query_tools
{
  "query": "backup database",
  "top_k": 5
}

Returns ranked results with scores:

[
  {
    "tool_id": "redisctl:enterprise_database_backup",
    "server": "redisctl",
    "name": "enterprise_database_backup",
    "description": "Trigger a backup snapshot of a Redis Enterprise database",
    "score": 8.42,
    "category": "enterprise"
  },
  {
    "tool_id": "redisctl:enterprise_database_export",
    "server": "redisctl",
    "name": "enterprise_database_export",
    "description": "Export database data to RDB file",
    "score": 5.17,
    "category": "enterprise"
  }
]

Similar Tools

Find tools related to one you know:

// similar_tools
{
  "tool_id": "redisctl:enterprise_database_delete",
  "top_k": 5
}

Returns tools with similar descriptions/tags - useful for finding alternatives or related operations.

Agent Workflow

Manual Registration

At session start, register each server:

1. Get tool list from redisctl (agent already has this via MCP)
2. Call jpx.register_discovery with redisctl's tools
3. Repeat for other MCP servers
4. Use query_tools for semantic search

Add to your agent’s system prompt or CLAUDE.md:

## MCP Tool Discovery

At session start, if jpx and other MCP servers are available:

1. For each non-jpx MCP server, register its tools with jpx:
   - Extract tool name, description, and input schema
   - Call `mcp__jpx__register_discovery` with the server name and tool list

2. When searching for tools, use `mcp__jpx__query_tools` for semantic search
   instead of scanning the tool list manually.

3. Use `mcp__jpx__similar_tools` to find related tools when exploring options.

Example: Multi-Server Setup

With jpx + redisctl + github-mcp:

// Register redisctl (65 tools)
register_discovery({
  spec: {
    server: { name: "redisctl", version: "0.1.0" },
    tools: [
      { name: "enterprise_database_create", description: "...", category: "enterprise" },
      { name: "enterprise_database_backup", description: "...", category: "enterprise" },
      // ... 63 more tools
    ]
  }
})

// Register github-mcp (20 tools)
register_discovery({
  spec: {
    server: { name: "github", version: "1.0.0" },
    tools: [
      { name: "create_issue", description: "Create a GitHub issue", category: "issues" },
      { name: "list_pull_requests", description: "List PRs for a repo", category: "pulls" },
      // ... 18 more tools
    ]
  }
})

// Now search across both!
query_tools({ query: "create new resource", top_k: 10 })
// Returns: enterprise_database_create, create_issue, enterprise_crdb_create, ...

BM25 Scoring

The search uses BM25, a proven ranking algorithm that:

  • Weights rare terms higher: “kubernetes” is more distinctive than “create”
  • Handles term frequency saturation: Repeating a word doesn’t linearly increase relevance
  • Normalizes by document length: Short and long descriptions scored fairly

This means “backup database” finds tools mentioning “snapshot”, “export”, “persist” if those terms appear in similar contexts across the corpus.

Session Scope

The registry is session-scoped (in-memory):

  • Fresh index each agent session
  • No persistence across sessions
  • Cheap to rebuild from source

This is intentional - tool metadata changes, and re-registration ensures the index matches current reality.

Inspecting the Index

Debug with inspect_discovery_index:

{
  "server_count": 2,
  "tool_count": 85,
  "term_count": 342,
  "avg_doc_length": 12.4,
  "servers": ["redisctl", "github"]
}

List categories with list_discovery_categories:

{
  "enterprise": { "tool_count": 30, "servers": ["redisctl"] },
  "cloud": { "tool_count": 25, "servers": ["redisctl"] },
  "issues": { "tool_count": 8, "servers": ["github"] },
  "pulls": { "tool_count": 6, "servers": ["github"] }
}

MCP Examples

This page demonstrates common workflows using the jpx MCP server with Claude or other AI assistants.

Data Exploration

Understanding Unknown JSON

When you receive unfamiliar JSON data, start by exploring its structure:

User: I have this API response, can you help me understand it?

Claude: Let me analyze the structure first.

[Uses stats tool]
{
  "input": "{\"users\": [{\"id\": 1, \"profile\": {\"name\": \"Alice\", \"settings\": {\"theme\": \"dark\"}}}]}"
}

Result: Object with 1 key, depth 4, contains array of 1 object

[Uses paths tool]
{
  "input": "{\"users\": [{\"id\": 1, \"profile\": {\"name\": \"Alice\"}}]}",
  "include_types": true
}

Result:
  users: array
  users.0: object
  users.0.id: number
  users.0.profile: object
  users.0.profile.name: string

Finding the Right Function

When you need to transform data but aren’t sure which function to use:

User: I need to remove duplicate entries from an array

Claude: Let me search for relevant functions.

[Uses search tool]
{
  "query": "remove duplicates array unique"
}

Result: Found unique(), distinct(), dedupe() - all do similar things.

[Uses describe tool]
{
  "name": "unique"
}

Result: unique(array) - Returns array with duplicate elements removed, preserving order.

Query Building

Iterative Query Development

Build complex queries step by step:

User: Help me extract active premium users with their subscription dates

Claude: Let me build this query incrementally.

[Uses evaluate tool - Step 1: Filter active users]
{
  "expression": "users[?status == `active`]",
  "input": "{\"users\": [{\"name\": \"Alice\", \"status\": \"active\", \"plan\": \"premium\", \"since\": \"2024-01-15\"}, {\"name\": \"Bob\", \"status\": \"inactive\", \"plan\": \"free\", \"since\": \"2023-06-01\"}]}"
}

[Uses evaluate tool - Step 2: Add premium filter]
{
  "expression": "users[?status == `active` && plan == `premium`]",
  "input": "..."
}

[Uses evaluate tool - Step 3: Project fields]
{
  "expression": "users[?status == `active` && plan == `premium`].{name: name, member_since: since}",
  "input": "..."
}

Final query: users[?status == `active` && plan == `premium`].{name: name, member_since: since}

Validating Expressions

Check syntax before running on large datasets:

[Uses validate tool]
{
  "expression": "users[?active].name | sort(@"
}

Result: Invalid - unclosed parenthesis at position 27

[Uses validate tool]
{
  "expression": "users[?active].name | sort(@)"
}

Result: Valid expression

Batch Operations

Multiple Extractions

Extract several pieces of information at once:

[Uses batch_evaluate tool]
{
  "input": "{\"items\": [1, 2, 3, 4, 5], \"metadata\": {\"count\": 5, \"type\": \"numbers\"}}",
  "expressions": [
    "length(items)",
    "sum(items)",
    "avg(items)",
    "metadata.type"
  ]
}

Result:
  length(items): 5
  sum(items): 15
  avg(items): 3
  metadata.type: "numbers"

File Operations

Working with Large Files

Query files directly without loading into memory:

[Uses evaluate_file tool]
{
  "file_path": "/path/to/large-dataset.json",
  "expression": "records[?timestamp > `2024-01-01`] | length(@)"
}

Result: 15234 records match the filter

JSON Transformations

Generating Patches

Track changes between document versions:

[Uses diff tool]
{
  "source": "{\"version\": 1, \"config\": {\"debug\": true, \"timeout\": 30}}",
  "target": "{\"version\": 2, \"config\": {\"debug\": false, \"timeout\": 60, \"retries\": 3}}"
}

Result:
[
  {"op": "replace", "path": "/version", "value": 2},
  {"op": "replace", "path": "/config/debug", "value": false},
  {"op": "replace", "path": "/config/timeout", "value": 60},
  {"op": "add", "path": "/config/retries", "value": 3}
]

Applying Updates

Merge configuration updates:

[Uses merge tool]
{
  "input": "{\"database\": {\"host\": \"localhost\", \"port\": 5432}, \"cache\": {\"enabled\": true}}",
  "patch": "{\"database\": {\"host\": \"db.example.com\"}, \"cache\": null}"
}

Result:
{
  "database": {"host": "db.example.com", "port": 5432}
}

Note: Setting a key to null in merge patch removes it.

Function Discovery Workflows

Learning New Functions

Explore related functionality:

[Uses similar tool]
{
  "function": "snake_case"
}

Result:
  Same category: camel_case, kebab_case, pascal_case, title_case
  Similar signature: upper, lower, capitalize
  Related concepts: snake_keys, camel_keys (for object keys)

Category Exploration

See all functions in a category:

[Uses functions tool]
{
  "category": "datetime"
}

Result:
  now() - Current timestamp
  format_date(date, format) - Format date string
  parse_date(string, format) - Parse date from string
  date_add(date, amount, unit) - Add time to date
  date_diff(date1, date2, unit) - Difference between dates
  ...

Multi-Server Discovery

When working with multiple MCP servers, use discovery tools to find the right tool across all servers:

[Uses query_tools tool]
{
  "query": "send email notification",
  "top_k": 5
}

Result:
  1. notifications:send_email (score: 12.4)
  2. mailer:compose_message (score: 8.2)
  3. alerts:notify_user (score: 6.1)

See Multi-Server Discovery for more details on setting up cross-server tool search.

Function Overview

jpx provides 400+ functions organized into 31 categories.

Discovering Functions

List All Functions

jpx --list-functions

List by Category

jpx --list-category string
jpx --list-category math
jpx --list-category datetime

Get Function Details

jpx --describe upper

Categories

CategoryDescriptionCount
ArrayFunctions for working with arrays: chunking, filtering, tran…27
ColorColor manipulation and conversion functions.8
ComputingComputing-related utility functions.9
Date/TimeFunctions for working with dates and times: parsing, formatt…26
DurationFunctions for working with time durations.5
EncodingEncoding and decoding functions: Base64, hex, URL encoding, …8
ExpressionHigher-order functions that work with JMESPath expressions a…33
FormatData formatting functions for numbers, currencies, and other…6
FuzzyFuzzy matching and string similarity functions.9
GeolocationGeolocation functions: distance calculation, coordinate pars…4
HashCryptographic hash functions: MD5, SHA family, and other has…9
ID GenerationFunctions for generating various types of unique identifiers…3
JSON PatchJSON Patch (RFC 6902) functions: applying patches, generatin…3
LanguageNatural language processing functions.5
MathMathematical and statistical functions: arithmetic, rounding…35
Multi-MatchFunctions for matching multiple patterns or expressions in a…10
NetworkNetwork-related functions: IP addresses, CIDR notation, and …7
ObjectFunctions for working with JSON objects: merging, filtering …48
PathFile path manipulation functions.4
PhoneticPhonetic encoding functions for sound-based string matching.9
RandomFunctions for generating random values: numbers, strings, an…3
Regular ExpressionRegular expression functions: matching, replacing, splitting…3
Semantic VersioningSemantic versioning functions: parsing, comparing, and manip…7
Standard JMESPathThese are the standard JMESPath functions as defined in the …26
StringFunctions for string manipulation: case conversion, splittin…36
TextText analysis and processing functions.11
TypeType conversion and checking functions.13
URLFunctions for parsing and manipulating URLs and their compon…3
UtilityGeneral utility functions that don’t fit other categories.11
UUIDFunctions for generating and working with UUIDs.1
ValidationFunctions for validating data: email, URL, UUID, and format …13

Function Syntax

Functions are called with parentheses:

function_name(arg1, arg2, ...)

Examples

# No arguments
echo '{}' | jpx 'now()'

# One argument
echo '{"name": "hello"}' | jpx 'upper(name)'

# Multiple arguments
echo '{"text": "hello world"}' | jpx 'split(text, ` `)'

# Literal arguments (use backticks)
echo '{}' | jpx 'range(`1`, `10`)'

Standard vs Extension Functions

Standard Functions (26)

These are part of the JMESPath specification and work in all implementations:

abs, avg, ceil, contains, ends_with, floor, join, keys, length, map, max, max_by, merge, min, min_by, not_null, reverse, sort, sort_by, starts_with, sum, to_array, to_number, to_string, type, values

Extension Functions (369)

These are jpx-specific and won’t work in other JMESPath implementations.

Strict Mode

Use --strict to disable extension functions:

# This works
jpx --strict 'length(items)' -f data.json

# This fails (upper is an extension)
jpx --strict 'upper(name)' -f data.json

Standard JMESPath Functions

These are the standard JMESPath functions as defined in the specification. They work in all JMESPath implementations.

Summary

FunctionSignatureDescription
absnumber -> numberReturns the absolute value of a number
avgarray[number] -> numberReturns the average of an array of numbers
ceilnumber -> numberReturns the smallest integer greater than or equal to the number
containsarray|string, any -> booleanReturns true if the subject contains the search value
ends_withstring, string -> booleanReturns true if the subject ends with the suffix
floornumber -> numberReturns the largest integer less than or equal to the number
joinstring, array[string] -> stringReturns array elements joined into a string with a separator
keysobject -> array[string]Returns an array of keys from an object
lengtharray|object|string -> numberReturns the length of an array, object, or string
mapexpression, array -> arrayApplies an expression to each element of an array
maxarray[number]|array[string] -> number|stringReturns the maximum value in an array
max_byarray, expression -> anyReturns the element with maximum value by expression
mergeobject... -> objectMerges objects into a single object
minarray[number]|array[string] -> number|stringReturns the minimum value in an array
min_byarray, expression -> anyReturns the element with minimum value by expression
not_nullany... -> anyReturns the first non-null argument
reversearray|string -> array|stringReverses an array or string
sortarray[number]|array[string] -> arraySorts an array of numbers or strings
sort_byarray, expression -> arraySorts an array by expression result
starts_withstring, string -> booleanReturns true if the subject starts with the prefix
sumarray[number] -> numberReturns the sum of an array of numbers
to_arrayany -> arrayConverts a value to an array
to_numberany -> numberConverts a value to a number
to_stringany -> stringConverts a value to a string
typeany -> stringReturns the type of a value as a string
valuesobject -> arrayReturns an array of values from an object

Functions

abs

Returns the absolute value of a number

Signature: number -> number

Examples:

# Negative number
abs(`-5`) -> 5
# Positive number
abs(`5`) -> 5
# Zero
abs(`0`) -> 0

CLI Usage:

echo '{}' | jpx 'abs(`-5`)'

avg

Returns the average of an array of numbers

Signature: array[number] -> number

Examples:

# Simple average
avg([1, 2, 3]) -> 2
# Four numbers
avg([10, 20, 30, 40]) -> 25
# Single element
avg([5]) -> 5

CLI Usage:

echo '{}' | jpx 'avg([1, 2, 3])'

ceil

Returns the smallest integer greater than or equal to the number

Signature: number -> number

Examples:

# Round up decimal
ceil(`1.5`) -> 2
# Negative rounds toward zero
ceil(`-1.5`) -> -1
# Integer unchanged
ceil(`5`) -> 5

CLI Usage:

echo '{}' | jpx 'ceil(`1.5`)'

contains

Returns true if the subject contains the search value

Signature: array|string, any -> boolean

Examples:

# Array contains number
contains([1, 2, 3], `2`) -> true
# String contains substring
contains('hello', 'ell') -> true
# Not found
contains([1, 2, 3], `5`) -> false

CLI Usage:

echo '{}' | jpx 'contains([1, 2, 3], `2`)'

ends_with

Returns true if the subject ends with the suffix

Signature: string, string -> boolean

Examples:

# Ends with suffix
ends_with('hello', 'lo') -> true
# Does not end with
ends_with('hello', 'he') -> false
# File extension
ends_with('test.txt', '.txt') -> true

CLI Usage:

echo '{}' | jpx 'ends_with(`"hello"`, `"lo"`)'

floor

Returns the largest integer less than or equal to the number

Signature: number -> number

Examples:

# Round down decimal
floor(`1.9`) -> 1
# Negative rounds away from zero
floor(`-1.5`) -> -2
# Integer unchanged
floor(`5`) -> 5

CLI Usage:

echo '{}' | jpx 'floor(`1.9`)'

join

Returns array elements joined into a string with a separator

Signature: string, array[string] -> string

Examples:

# Join with comma
join(', ', ['a', 'b', 'c']) -> \"a, b, c\"
# Join date parts
join('-', ['2024', '01', '15']) -> \"2024-01-15\"
# Join without separator
join('', ['a', 'b', 'c']) -> \"abc\"

CLI Usage:

echo '{}' | jpx 'join(`", "`, ['a', `"b"`, 'c'])'

keys

Returns an array of keys from an object

Signature: object -> array[string]

Examples:

# Get object keys
keys({a: 1, b: 2}) -> [\"a\", \"b\"]
# Keys sorted alphabetically
keys({name: \"John\", age: 30}) -> [\"age\", \"name\"]
# Empty object
keys({}) -> []

CLI Usage:

echo '{}' | jpx 'keys({a: 1, b: 2})'

length

Returns the length of an array, object, or string

Signature: array|object|string -> number

Examples:

# Array length
length([1, 2, 3]) -> 3
# String length
length('hello') -> 5
# Object key count
length({a: 1, b: 2}) -> 2

CLI Usage:

echo '{}' | jpx 'length([1, 2, 3])'

map

Applies an expression to each element of an array

Signature: expression, array -> array

Examples:

# Extract field from objects
map(&a, [{a: 1}, {a: 2}]) -> [1, 2]
# Apply function
map(&length(@), ['a', 'bb', 'ccc']) -> [1, 2, 3]
# Identity mapping
map(&@, [1, 2, 3]) -> [1, 2, 3]

CLI Usage:

echo '{}' | jpx 'map(&a, [{a: 1}, {a: 2}])'

max

Returns the maximum value in an array

Signature: array[number]|array[string] -> number|string

Examples:

# Max of numbers
max([1, 3, 2]) -> 3
# Max of strings
max(['a', 'c', 'b']) -> 'c'
# Negative numbers
max([-5, -1, -10]) -> -1

CLI Usage:

echo '{}' | jpx 'max([1, 3, 2])'

max_by

Returns the element with maximum value by expression

Signature: array, expression -> any

Examples:

# Max by field
max_by([{a: 1}, {a: 2}], &a) -> {a: 2}
# Max by age
max_by([{name: 'a', age: 30}, {name: 'b', age: 25}], &age) -> {name: 'a', age: 30}
# Max by length
max_by(['aa', 'b', 'ccc'], &length(@)) -> 'ccc'

CLI Usage:

echo '{}' | jpx 'max_by([{a: 1}, {a: 2}], &a)'

merge

Merges objects into a single object

Signature: object... -> object

Examples:

# Merge two objects
merge({a: 1}, {b: 2}) -> {a: 1, b: 2}
# Later values override
merge({a: 1}, {a: 2}) -> {a: 2}
# Multiple objects
merge({a: 1}, {b: 2}, {c: 3}) -> {a: 1, b: 2, c: 3}

CLI Usage:

echo '{}' | jpx 'merge({a: 1}, {b: 2})'

min

Returns the minimum value in an array

Signature: array[number]|array[string] -> number|string

Examples:

# Min of numbers
min([1, 3, 2]) -> 1
# Min of strings
min(['a', 'c', 'b']) -> 'a'
# Negative numbers
min([-5, -1, -10]) -> -10

CLI Usage:

echo '{}' | jpx 'min([1, 3, 2])'

min_by

Returns the element with minimum value by expression

Signature: array, expression -> any

Examples:

# Min by field
min_by([{a: 1}, {a: 2}], &a) -> {a: 1}
# Min by age
min_by([{name: 'a', age: 30}, {name: 'b', age: 25}], &age) -> {name: 'b', age: 25}
# Min by length
min_by(['aa', 'b', 'ccc'], &length(@)) -> 'b'

CLI Usage:

echo '{}' | jpx 'min_by([{a: 1}, {a: 2}], &a)'

not_null

Returns the first non-null argument

Signature: any... -> any

Examples:

# Skip nulls
not_null(`null`, 'a', 'b') -> \"a\"
# First non-null
not_null('first', 'second') -> \"first\"
# Multiple nulls
not_null(`null`, `null`, `1`) -> 1

CLI Usage:

echo '{}' | jpx 'not_null(`null`, `"a"`, `"b"`)'

reverse

Reverses an array or string

Signature: array|string -> array|string

Examples:

# Reverse array
reverse([1, 2, 3]) -> [3, 2, 1]
# Reverse string
reverse('hello') -> 'olleh'
# Empty array
reverse([]) -> []

CLI Usage:

echo '{}' | jpx 'reverse([1, 2, 3])'

sort

Sorts an array of numbers or strings

Signature: array[number]|array[string] -> array

Examples:

# Sort numbers
sort([3, 1, 2]) -> [1, 2, 3]
# Sort strings
sort(['c', 'a', 'b']) -> ['a', 'b', 'c']
# Empty array
sort([]) -> []

CLI Usage:

echo '{}' | jpx 'sort([3, 1, 2])'

sort_by

Sorts an array by expression result

Signature: array, expression -> array

Examples:

# Sort by field
sort_by([{a: 2}, {a: 1}], &a) -> [{a: 1}, {a: 2}]
# Sort by length
sort_by(['bb', 'a', 'ccc'], &length(@)) -> ['a', 'bb', 'ccc']
# Sort by name
sort_by([{name: 'z'}, {name: 'a'}], &name) -> [{name: 'a'}, {name: 'z'}]

CLI Usage:

echo '{}' | jpx 'sort_by([{a: 2}, {a: 1}], &a)'

starts_with

Returns true if the subject starts with the prefix

Signature: string, string -> boolean

Examples:

# Starts with prefix
starts_with('hello', 'he') -> true
# Does not start with
starts_with('hello', 'lo') -> false
# URL protocol
starts_with('https://example.com', 'https') -> true

CLI Usage:

echo '{}' | jpx 'starts_with(`"hello"`, `"he"`)'

sum

Returns the sum of an array of numbers

Signature: array[number] -> number

Examples:

# Sum of numbers
sum([1, 2, 3]) -> 6
# With negative
sum([10, -5, 3]) -> 8
# Empty array
sum([]) -> 0

CLI Usage:

echo '{}' | jpx 'sum([1, 2, 3])'

to_array

Converts a value to an array

Signature: any -> array

Examples:

# String to array
to_array('hello') -> [\"hello\"]
# Array unchanged
to_array([1, 2]) -> [1, 2]
# Number to array
to_array(`5`) -> [5]

CLI Usage:

echo '{}' | jpx 'to_array(`"hello"`)'

to_number

Converts a value to a number

Signature: any -> number

Examples:

# String to number
to_number('42') -> 42
# String to float
to_number('3.14') -> 3.14
# Number unchanged
to_number(`5`) -> 5

CLI Usage:

echo '{}' | jpx 'to_number(`"42"`)'

to_string

Converts a value to a string

Signature: any -> string

Examples:

# Number to string
to_string(`42`) -> \"42\"
# Boolean to string
to_string(`true`) -> \"true\"
# String unchanged
to_string('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'to_string(`42`)'

type

Returns the type of a value as a string

Signature: any -> string

Examples:

# String type
type('hello') -> \"string\"
# Number type
type(`42`) -> \"number\"
# Array type
type([1, 2]) -> \"array\"
# Object type
type({a: 1}) -> \"object\"

CLI Usage:

echo '{}' | jpx 'type(`"hello"`)'

values

Returns an array of values from an object

Signature: object -> array

Examples:

# Get object values
values({a: 1, b: 2}) -> [1, 2]
# String values
values({x: 'hello', y: 'world'}) -> ['hello', 'world']
# Empty object
values({}) -> []

CLI Usage:

echo '{}' | jpx 'values({a: 1, b: 2})'

String Functions

Functions for string manipulation: case conversion, splitting, joining, padding, and text processing.

Summary

FunctionSignatureDescription
abbreviatestring, number, string? -> stringTruncate string with ellipsis suffix
camel_casestring -> stringConvert to camelCase
capitalizestring -> stringCapitalize the first character
centerstring, number, string? -> stringCenter-pad string to given width
concatstring... -> stringConcatenate strings
explodestring -> arrayConvert a string to an array of Unicode codepoints
find_firststring, string -> number | nullFind first occurrence of substring
find_laststring, string -> number | nullFind last occurrence of substring
implodearray -> stringConvert an array of Unicode codepoints to a string
indicesstring, string -> arrayFind all indices of substring occurrences
insidestring, string -> booleanCheck if search string is contained in string
is_blankstring -> booleanCheck if string is empty or whitespace-only
kebab_casestring -> stringConvert to kebab-case
lowerstring -> stringConvert string to lowercase
pascal_casestring -> stringConvert to PascalCase
ltrimstrstring, string -> stringRemove prefix from string if present
maskstring, number?, string? -> stringMask string, keeping last N characters visible
normalize_whitespacestring -> stringCollapse multiple whitespace to single space
pad_leftstring, number, string -> stringPad string on the left to reach target length
pad_rightstring, number, string -> stringPad string on the right to reach target length
redactstring, string, string? -> stringRedact regex pattern matches with replacement
repeatstring, number -> stringRepeat a string n times
shouty_kebab_casestring -> stringConvert to SHOUTY-KEBAB-CASE
shouty_snake_casestring -> stringConvert to SHOUTY_SNAKE_CASE
replacestring, string, string -> stringReplace occurrences of a substring
reverse_stringstring -> stringReverse a string
rtrimstrstring, string -> stringRemove suffix from string if present
shell_escapestring -> stringEscape a string for safe use in shell commands (POSIX sh compatible, jq parity)
slicestring, number, number -> stringExtract substring by start and end index
snake_casestring -> stringConvert to snake_case
splitstring, string -> arraySplit string by delimiter
sprintfstring, any... -> stringPrintf-style string formatting
substrstring, number, number -> stringExtract substring by start index and length
titlestring -> stringConvert to title case
train_casestring -> stringConvert to Train-Case
trimstring -> stringRemove leading and trailing whitespace
trim_leftstring -> stringRemove leading whitespace
trim_rightstring -> stringRemove trailing whitespace
upperstring -> stringConvert string to uppercase
wrapstring, number -> stringWrap text to specified width

Functions

abbreviate

Truncate string with ellipsis suffix

Signature: string, number, string? -> string

Examples:

# Default ellipsis
abbreviate('hello world', `8`) -> \"hello...\"
# No truncation needed
abbreviate('hello', `10`) -> \"hello\"
# Custom suffix
abbreviate('hello world', `8`, '>>') -> \"hello >>\"

CLI Usage:

echo '{}' | jpx 'abbreviate(`"hello world"`, `8`)'

camel_case

Convert to camelCase

Signature: string -> string

Examples:

# From snake_case
camel_case('hello_world') -> \"helloWorld\"
# From kebab-case
camel_case('hello-world') -> \"helloWorld\"
# From title case
camel_case('Hello World') -> \"helloWorld\"

CLI Usage:

echo '{}' | jpx 'camel_case(`"hello_world"`)'

capitalize

Capitalize the first character

Signature: string -> string

Examples:

# Basic capitalize
capitalize('hello') -> \"Hello\"
# Already uppercase
capitalize('HELLO') -> \"HELLO\"
# Empty string
capitalize('') -> \"\"

CLI Usage:

echo '{}' | jpx 'capitalize(`"hello"`)'

center

Center-pad string to given width

Signature: string, number, string? -> string

Examples:

# Center with spaces
center('hi', `6`) -> \"  hi  \"
# Center with dashes
center('hi', `6`, '-') -> \"--hi--\"
# Already wider
center('hello', `3`) -> \"hello\"

CLI Usage:

echo '{}' | jpx 'center(`"hi"`, `6`)'

concat

Concatenate strings

Signature: string... -> string

Examples:

# Multiple strings
concat('hello', ' ', 'world') -> \"hello world\"
# Two strings
concat('a', 'b') -> \"ab\"
# Single string
concat('only') -> \"only\"

CLI Usage:

echo '{}' | jpx 'concat(`"hello"`, `" "`, `"world"`)'

explode

Convert a string to an array of Unicode codepoints

Signature: string -> array

Examples:

# ASCII characters
explode('abc') -> [97, 98, 99]
# Unicode characters
explode('A☺') -> [65, 9786]
# Empty string
explode('') -> []

CLI Usage:

echo '{}' | jpx 'explode(`"abc"`)'

find_first

Find first occurrence of substring

Signature: string, string -> number | null

JEP: JEP-014

Examples:

# Find character
find_first('hello', 'l') -> 2
# Find substring
find_first('hello world', 'world') -> 6
# Not found
find_first('hello', 'x') -> null

CLI Usage:

echo '{}' | jpx 'find_first(`"hello"`, `"l"`)'

find_last

Find last occurrence of substring

Signature: string, string -> number | null

JEP: JEP-014

Examples:

# Find last character
find_last('hello', 'l') -> 3
# Find last substring
find_last('foo bar foo', 'foo') -> 8
# Not found
find_last('hello', 'x') -> null

CLI Usage:

echo '{}' | jpx 'find_last(`"hello"`, `"l"`)'

implode

Convert an array of Unicode codepoints to a string

Signature: array -> string

Examples:

# ASCII codepoints
implode([97, 98, 99]) -> \"abc\"
# Unicode codepoints
implode([65, 9786]) -> \"A☺\"
# Empty array
implode([]) -> \"\"

CLI Usage:

echo '{}' | jpx 'implode([97, 98, 99])'

indices

Find all indices of substring occurrences

Signature: string, string -> array

Examples:

# Multiple occurrences
indices('hello', 'l') -> [2, 3]
# Overlapping matches
indices('ababa', 'aba') -> [0, 2]
# No matches
indices('hello', 'x') -> []

CLI Usage:

echo '{}' | jpx 'indices(`"hello"`, `"l"`)'

inside

Check if search string is contained in string

Signature: string, string -> boolean

Examples:

# Found
inside('world', 'hello world') -> true
# Not found
inside('foo', 'hello world') -> false
# Empty string always matches
inside('', 'hello') -> true

CLI Usage:

echo '{}' | jpx 'inside(`"world"`, `"hello world"`)'

is_blank

Check if string is empty or whitespace-only

Signature: string -> boolean

Examples:

# Whitespace only
is_blank('   ') -> true
# Empty string
is_blank('') -> true
# Has content
is_blank('hello') -> false

CLI Usage:

echo '{}' | jpx 'is_blank(`"   "`)'

kebab_case

Convert to kebab-case

Signature: string -> string

Examples:

# From camelCase
kebab_case('helloWorld') -> \"hello-world\"
# From snake_case
kebab_case('hello_world') -> \"hello-world\"
# From title case
kebab_case('Hello World') -> \"hello-world\"

CLI Usage:

echo '{}' | jpx 'kebab_case(`"helloWorld"`)'

lower

Convert string to lowercase

Signature: string -> string

JEP: JEP-014

Examples:

# All uppercase
lower('HELLO') -> \"hello\"
# Mixed case
lower('Hello World') -> \"hello world\"
# Already lowercase
lower('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'lower(`"HELLO"`)'

pascal_case

Convert to PascalCase (also known as UpperCamelCase)

Signature: string -> string

Examples:

# From snake_case
pascal_case('hello_world') -> "HelloWorld"
# From camelCase
pascal_case('helloWorld') -> "HelloWorld"
# From kebab-case
pascal_case('hello-world') -> "HelloWorld"
# Handles acronyms
pascal_case('xml_parser') -> "XmlParser"

CLI Usage:

echo '{}' | jpx 'pascal_case(`"hello_world"`)'

ltrimstr

Remove prefix from string if present

Signature: string, string -> string

Examples:

# Remove prefix
ltrimstr('foobar', 'foo') -> \"bar\"
# Prefix not found
ltrimstr('foobar', 'bar') -> \"foobar\"
# Empty prefix
ltrimstr('hello', '') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'ltrimstr(`"foobar"`, `"foo"`)'

mask

Mask string, keeping last N characters visible

Signature: string, number?, string? -> string

Examples:

# Credit card
mask('4111111111111111', `4`) -> \"************1111\"
# Mask all
mask('secret', `0`) -> \"******\"
# Custom mask char
mask('password', `4`, '#') -> \"####word\"

CLI Usage:

echo '{}' | jpx 'mask(`"4111111111111111"`, `4`)'

normalize_whitespace

Collapse multiple whitespace to single space

Signature: string -> string

Examples:

# Multiple spaces
normalize_whitespace('a  b  c') -> \"a b c\"
# Newlines to space
normalize_whitespace('a\\n\\nb') -> \"a b\"
# Already normalized
normalize_whitespace('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'normalize_whitespace(`"a  b  c"`)'

pad_left

Pad string on the left to reach target length

Signature: string, number, string -> string

JEP: JEP-014

Examples:

# Zero-pad number
pad_left('5', `3`, '0') -> \"005\"
# Right-align text
pad_left('hi', `5`, ' ') -> \"   hi\"
# Already long enough
pad_left('hello', `3`, '0') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'pad_left(`"5"`, `3`, `"0"`)'

pad_right

Pad string on the right to reach target length

Signature: string, number, string -> string

JEP: JEP-014

Examples:

# Pad with zeros
pad_right('5', `3`, '0') -> \"500\"
# Left-align text
pad_right('hi', `5`, ' ') -> \"hi   \"
# Already long enough
pad_right('hello', `3`, '0') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'pad_right(`"5"`, `3`, `"0"`)'

redact

Redact regex pattern matches with replacement

Signature: string, string, string? -> string

Examples:

# Redact email
redact('email: test@example.com', '\\S+@\\S+', '[EMAIL]') -> \"email: [EMAIL]\"
# Redact phone
redact('call 555-1234', '\\d{3}-\\d{4}', '[PHONE]') -> \"call [PHONE]\"
# No matches
redact('no match', 'xyz', '[X]') -> \"no match\"

CLI Usage:

echo '{}' | jpx 'redact(`"email: test@example.com"`, `"\\S+@\\S+"`, `"[EMAIL]"`)'

repeat

Repeat a string n times

Signature: string, number -> string

Examples:

# Repeat 3 times
repeat('ab', `3`) -> \"ababab\"
# Create separator
repeat('-', `5`) -> \"-----\"
# Zero times
repeat('x', `0`) -> \"\"

CLI Usage:

echo '{}' | jpx 'repeat(`"ab"`, `3`)'

shouty_kebab_case

Convert to SHOUTY-KEBAB-CASE (also known as COBOL-CASE or SCREAMING-KEBAB-CASE)

Signature: string -> string

Examples:

# From camelCase
shouty_kebab_case('helloWorld') -> "HELLO-WORLD"
# From snake_case
shouty_kebab_case('hello_world') -> "HELLO-WORLD"
# From PascalCase
shouty_kebab_case('HelloWorld') -> "HELLO-WORLD"

CLI Usage:

echo '{}' | jpx 'shouty_kebab_case(`"helloWorld"`)'

shouty_snake_case

Convert to SHOUTY_SNAKE_CASE (also known as SCREAMING_SNAKE_CASE or CONSTANT_CASE)

Signature: string -> string

Examples:

# From camelCase
shouty_snake_case('helloWorld') -> "HELLO_WORLD"
# From kebab-case
shouty_snake_case('hello-world') -> "HELLO_WORLD"
# From PascalCase
shouty_snake_case('HelloWorld') -> "HELLO_WORLD"

CLI Usage:

echo '{}' | jpx 'shouty_snake_case(`"helloWorld"`)'

replace

Replace occurrences of a substring

Signature: string, string, string -> string

JEP: JEP-014

Examples:

# Replace all occurrences
replace('hello', 'l', 'L') -> \"heLLo\"
# No match
replace('hello', 'x', 'y') -> \"hello\"
# Replace all
replace('aaa', 'a', 'b') -> \"bbb\"

CLI Usage:

echo '{}' | jpx 'replace(`"hello"`, `"l"`, `"L"`)'

reverse_string

Reverse a string

Signature: string -> string

Examples:

# Reverse word
reverse_string('hello') -> \"olleh\"
# Two chars
reverse_string('ab') -> \"ba\"
# Empty string
reverse_string('') -> \"\"

CLI Usage:

echo '{}' | jpx 'reverse_string(`"hello"`)'

rtrimstr

Remove suffix from string if present

Signature: string, string -> string

Examples:

# Remove suffix
rtrimstr('foobar', 'bar') -> \"foo\"
# Remove file extension
rtrimstr('hello.txt', '.txt') -> \"hello\"
# Suffix not present
rtrimstr('hello', 'xyz') -> \"hello\"
# Only removes once
rtrimstr('barbar', 'bar') -> \"bar\"

CLI Usage:

echo '{}' | jpx 'rtrimstr(`"foobar"`, `"bar"`)'

shell_escape

Escape a string for safe use in shell commands (POSIX sh compatible, jq parity)

Signature: string -> string

Examples:

# Simple string unchanged
shell_escape('hello') -> \"hello\"
# Spaces get quoted
shell_escape('hello world') -> \"'hello world'\"
# Variables are escaped
shell_escape('$PATH') -> \"'$PATH'\"
# Single quotes escaped
shell_escape('it'\\''s') -> \"'it'\\\\''s'\"

CLI Usage:

echo '{}' | jpx 'shell_escape(`"hello"`)'

slice

Extract substring by start and end index

Signature: string, number, number -> string

Examples:

# Middle slice
slice('hello', `1`, `4`) -> \"ell\"
# From start
slice('hello', `0`, `2`) -> \"he\"
# To end
slice('hello', `3`, `5`) -> \"lo\"
# Two characters
slice('abcdef', `2`, `4`) -> \"cd\"

CLI Usage:

echo '{}' | jpx 'slice(`"hello"`, `1`, `4`)'

snake_case

Convert to snake_case

Signature: string -> string

Examples:

# From camelCase
snake_case('helloWorld') -> \"hello_world\"
# From PascalCase
snake_case('HelloWorld') -> \"hello_world\"
# From kebab-case
snake_case('hello-world') -> \"hello_world\"
# From UPPER_CASE
snake_case('HELLO_WORLD') -> \"hello_world\"

CLI Usage:

echo '{}' | jpx 'snake_case(`"helloWorld"`)'

split

Split string by delimiter

Signature: string, string -> array

JEP: JEP-014

Examples:

# Basic split
split('a,b,c', ',') -> [\"a\", \"b\", \"c\"]
# Split by space
split('hello world', ' ') -> [\"hello\", \"world\"]
# No delimiter found
split('no-delim', ',') -> [\"no-delim\"]
# Empty string
split('', ',') -> [\"\"]

CLI Usage:

echo '{}' | jpx 'split(`"a,b,c"`, `","`)'

sprintf

Printf-style string formatting

Signature: string, any... -> string

Examples:

# Float formatting
sprintf('Pi is %.2f', `3.14159`) -> \"Pi is 3.14\"
# String interpolation
sprintf('Hello %s', 'world') -> \"Hello world\"
# Integer formatting
sprintf('%d items', `42`) -> \"42 items\"
# Multiple args
sprintf('%s: %d', 'count', `5`) -> \"count: 5\"

CLI Usage:

echo '{}' | jpx 'sprintf(`"Pi is %.2f"`, `3.14159`)'

substr

Extract substring by start index and length

Signature: string, number, number -> string

Examples:

# From index 1, length 3
substr('hello', `1`, `3`) -> \"ell\"
# From start
substr('hello', `0`, `2`) -> \"he\"
# Middle portion
substr('abcdef', `2`, `2`) -> \"cd\"
# Single character
substr('hello', `4`, `1`) -> \"o\"

CLI Usage:

echo '{}' | jpx 'substr(`"hello"`, `1`, `3`)'

title

Convert to title case

Signature: string -> string

Examples:

# Basic title case
title('hello world') -> \"Hello World\"
# From uppercase
title('HELLO WORLD') -> \"Hello World\"
# Single word
title('hello') -> \"Hello\"
# Single letters
title('a b c') -> \"A B C\"

CLI Usage:

echo '{}' | jpx 'title(`"hello world"`)'

train_case

Convert to Train-Case (also known as HTTP-Header-Case)

Signature: string -> string

Examples:

# From camelCase
train_case('helloWorld') -> "Hello-World"
# From snake_case
train_case('hello_world') -> "Hello-World"
# From lowercase
train_case('content type') -> "Content-Type"

CLI Usage:

echo '{}' | jpx 'train_case(`"helloWorld"`)'

trim

Remove leading and trailing whitespace

Signature: string -> string

JEP: JEP-014

Examples:

# Remove both sides
trim('  hello  ') -> \"hello\"
# No whitespace
trim('hello') -> \"hello\"
# Only whitespace
trim('   ') -> \"\"
# Tabs and newlines
trim('\t\nhello\t\n') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'trim(`"  hello  "`)'

trim_left

Remove leading whitespace

Signature: string -> string

JEP: JEP-014

Examples:

# Remove leading spaces
trim_left('  hello') -> \"hello\"
# Trailing preserved
trim_left('hello  ') -> \"hello  \"
# Remove tabs
trim_left('\t\thello') -> \"hello\"
# No change needed
trim_left('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'trim_left(`"  hello"`)'

trim_right

Remove trailing whitespace

Signature: string -> string

JEP: JEP-014

Examples:

# Remove trailing spaces
trim_right('hello  ') -> \"hello\"
# Leading preserved
trim_right('  hello') -> \"  hello\"
# Remove tabs
trim_right('hello\t\t') -> \"hello\"
# No change needed
trim_right('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'trim_right(`"hello  "`)'

upper

Convert string to uppercase

Signature: string -> string

JEP: JEP-014

Examples:

# Basic uppercase
upper('hello') -> \"HELLO\"
# Mixed case
upper('Hello World') -> \"HELLO WORLD\"
# Already uppercase
upper('HELLO') -> \"HELLO\"
# With numbers
upper('abc123') -> \"ABC123\"

CLI Usage:

echo '{}' | jpx 'upper(`"hello"`)'

wrap

Wrap text to specified width

Signature: string, number -> string

Examples:

# Wrap at word boundary
wrap('hello world', `5`) -> \"hello\\nworld\"
# Multiple wraps
wrap('a b c d e', `3`) -> \"a b\\nc d\\ne\"
# No wrap needed
wrap('short', `10`) -> \"short\"
# Longer text
wrap('one two three', `7`) -> \"one two\\nthree\"

CLI Usage:

echo '{}' | jpx 'wrap(`"hello world"`, `5`)'

Array Functions

Functions for working with arrays: chunking, filtering, transforming, and combining array data.

Summary

FunctionSignatureDescription
bsearcharray, any -> numberBinary search in a sorted array, returns index or negative insertion point (jq parity)
cartesianarray, array? -> arrayCompute cartesian product of arrays (jq parity for N-way product)
chunkarray, number -> arraySplit array into chunks of size n
compactarray -> arrayRemove null values from array
differencearray, array -> arrayElements in first array not in second
droparray, number -> arrayDrop first n elements
find_indexarray, any -> number | nullFind index of value in array
firstarray -> anyGet first element of array
flattenarray -> arrayFlatten array one level deep
flatten_deeparray -> arrayRecursively flatten nested arrays
frequenciesarray -> objectCount occurrences of each value
group_byarray, string -> objectGroup array elements by key
includesarray, any -> booleanCheck if array contains value
index_atarray, number -> anyGet element at index (supports negative)
index_byarray, string -> objectCreate lookup map from array using key field (last value wins for duplicates)
indices_arrayarray, any -> arrayFind all indices where a value appears in an array (jq parity)
inside_arrayarray, array -> booleanCheck if all elements of first array are contained in second array (inverse of contains, jq parity)
intersectionarray, array -> arrayElements common to both arrays
lastarray -> anyGet last element of array
pairwisearray -> arrayReturn adjacent pairs from array
rangenumber, number -> arrayGenerate array of numbers
sliding_windowarray, number -> arrayCreate overlapping windows of size n (alias for window)
takearray, number -> arrayTake first n elements
transposearray -> arrayTranspose a 2D array (swap rows and columns)
unionarray, array -> arrayUnique elements from both arrays
uniquearray -> arrayRemove duplicate values
ziparray, array -> arrayZip two arrays together

Functions

bsearch

Binary search in a sorted array, returns index or negative insertion point (jq parity)

Signature: array, any -> number

Examples:

# Found at index 2
bsearch([1, 3, 5, 7, 9], `5`) -> 2
# Not found, would insert at index 2
bsearch([1, 3, 5, 7, 9], `4`) -> -3
# Not found, would insert at index 0
bsearch([1, 3, 5, 7, 9], `0`) -> -1

CLI Usage:

echo '{}' | jpx 'bsearch([1, 3, 5, 7, 9], `5`)'

cartesian

Compute cartesian product of arrays (jq parity for N-way product)

Signature: array, array? -> array

Examples:

# Two-array product
cartesian([1, 2], [3, 4]) -> [[1, 3], [1, 4], [2, 3], [2, 4]]
# N-way product (jq style)
cartesian([[1, 2], [3, 4]]) -> [[1, 3], [1, 4], [2, 3], [2, 4]]
# Three-way product
cartesian([['a', 'b'], [1, 2], ['x', 'y']]) -> [['a', 1, 'x'], ['a', 1, 'y'], ...]
# Single array
cartesian([[1, 2]]) -> [[1], [2]]
# Empty array produces empty result
cartesian([[], [1, 2]]) -> []

CLI Usage:

echo '{}' | jpx 'cartesian([1, 2], [3, 4])'

chunk

Split array into chunks of size n

Signature: array, number -> array

Examples:

# Basic chunking
chunk([1, 2, 3, 4], `2`) -> [[1, 2], [3, 4]]
# Uneven chunks
chunk([1, 2, 3, 4, 5], `2`) -> [[1, 2], [3, 4], [5]]
# Empty array
chunk([], `2`) -> []

CLI Usage:

echo '{}' | jpx 'chunk([1, 2, 3, 4], `2`)'

compact

Remove null values from array

Signature: array -> array

Examples:

# Remove nulls
compact([1, null, 2, null]) -> [1, 2]
# All nulls
compact([null, null]) -> []
# No nulls
compact([1, 2, 3]) -> [1, 2, 3]

CLI Usage:

echo '{}' | jpx 'compact([1, null, 2, null])'

difference

Elements in first array not in second

Signature: array, array -> array

Examples:

# Remove matching elements
difference([1, 2, 3], [2]) -> [1, 3]
# No overlap
difference([1, 2, 3], [4, 5]) -> [1, 2, 3]
# All removed
difference([1, 2], [1, 2]) -> []

CLI Usage:

echo '{}' | jpx 'difference([1, 2, 3], [2])'

drop

Drop first n elements

Signature: array, number -> array

Examples:

# Drop first 2
drop([1, 2, 3, 4], `2`) -> [3, 4]
# Drop none
drop([1, 2, 3], `0`) -> [1, 2, 3]
# Drop more than length
drop([1, 2], `5`) -> []

CLI Usage:

echo '{}' | jpx 'drop([1, 2, 3, 4], `2`)'

find_index

Find index of value in array

Signature: array, any -> number | null

Examples:

# Find existing value
find_index([1, 2, 3], `2`) -> 1
# Find string
find_index(['a', 'b', 'c'], 'b') -> 1
# Value not found
find_index([1, 2, 3], `99`) -> null

CLI Usage:

echo '{}' | jpx 'find_index([1, 2, 3], `2`)'

first

Get first element of array

Signature: array -> any

Examples:

# Get first number
first([1, 2, 3]) -> 1
# Get first string
first(['a', 'b']) -> 'a'
# Empty array returns null
first([]) -> null

CLI Usage:

echo '{}' | jpx 'first([1, 2, 3])'

flatten

Flatten array one level deep

Signature: array -> array

Examples:

# Flatten nested arrays
flatten([[1, 2], [3]]) -> [1, 2, 3]
# Only one level deep
flatten([[1, [2]], [3]]) -> [1, [2], 3]
# Already flat
flatten([1, 2, 3]) -> [1, 2, 3]

CLI Usage:

echo '{}' | jpx 'flatten([[1, 2], [3]])'

flatten_deep

Recursively flatten nested arrays

Signature: array -> array

Examples:

# Deeply nested
flatten_deep([[1, [2]], [3]]) -> [1, 2, 3]
# Multiple levels
flatten_deep([[[1]], [[2]], [[3]]]) -> [1, 2, 3]
# Already flat
flatten_deep([1, 2, 3]) -> [1, 2, 3]

CLI Usage:

echo '{}' | jpx 'flatten_deep([[1, [2]], [3]])'

frequencies

Count occurrences of each value

Signature: array -> object

Examples:

# Count strings
frequencies(['a', 'b', 'a']) -> {a: 2, b: 1}
# Count numbers
frequencies([1, 2, 1, 1]) -> {1: 3, 2: 1}
# Empty array
frequencies([]) -> {}

CLI Usage:

echo '{}' | jpx 'frequencies(['a', `"b"`, 'a'])'

group_by

Group array elements by key

Signature: array, string -> object

Examples:

# Group by field
group_by([{t: 'a'}, {t: 'b'}, {t: 'a'}], 't') -> {a: [{t:'a'}, {t:'a'}], b: [{t:'b'}]}
# Group users by role
group_by(users, 'role') -> {admin: [...], user: [...]}
# Empty array returns empty object
group_by([], 'key') -> {}

CLI Usage:

echo '{}' | jpx 'group_by([{t: 'a'}, {t: 'b'}, {t: 'a'}], `"t"`)'

includes

Check if array contains value

Signature: array, any -> boolean

Examples:

# Value found
includes([1, 2, 3], `2`) -> true
# Value not found
includes([1, 2, 3], `99`) -> false
# String value
includes(['a', 'b'], 'a') -> true

CLI Usage:

echo '{}' | jpx 'includes([1, 2, 3], `2`)'

index_at

Get element at index (supports negative)

Signature: array, number -> any

Examples:

# First element
index_at([1, 2, 3], `0`) -> 1
# Last element (negative index)
index_at([1, 2, 3], `-1`) -> 3
# Out of bounds
index_at([1, 2, 3], `99`) -> null

CLI Usage:

echo '{}' | jpx 'index_at([1, 2, 3], `0`)'

index_by

Create lookup map from array using key field (last value wins for duplicates)

Signature: array, string -> object

Examples:

# Index by id field
index_by([{id: 1, name: "alice"}, {id: 2, name: "bob"}], "id") -> {"1": {id: 1, name: "alice"}, "2": {id: 2, name: "bob"}}
# Index by string key
index_by([{code: "US", name: "USA"}], "code") -> {"US": {code: "US", name: "USA"}}
# Last value wins for duplicates
index_by([{t: "a", v: 1}, {t: "a", v: 2}], "t") -> {"a": {t: "a", v: 2}}
# Empty array returns empty object
index_by([], "id") -> {}

CLI Usage:

echo '{}' | jpx 'index_by([{id: 1, name: "alice"}, {id: 2, name: "bob"}], "id")'

indices_array

Find all indices where a value appears in an array (jq parity)

Signature: array, any -> array

Examples:

# Find all occurrences
indices_array([1, 2, 3, 2, 4, 2], `2`) -> [1, 3, 5]
# String values
indices_array(['a', 'b', 'a', 'c'], `'a'`) -> [0, 2]
# Not found returns empty
indices_array([1, 2, 3], `5`) -> []

CLI Usage:

echo '{}' | jpx 'indices_array([1, 2, 3, 2, 4, 2], `2`)'

inside_array

Check if all elements of first array are contained in second array (inverse of contains, jq parity)

Signature: array, array -> boolean

Examples:

# Subset check
inside_array([1, 2], [1, 2, 3, 4]) -> true
# Not a subset
inside_array([1, 5], [1, 2, 3, 4]) -> false
# Empty is subset of any
inside_array([], [1, 2, 3]) -> true

CLI Usage:

echo '{}' | jpx 'inside_array([1, 2], [1, 2, 3, 4])'

intersection

Elements common to both arrays

Signature: array, array -> array

Examples:

# Common elements
intersection([1, 2], [2, 3]) -> [2]
# No overlap
intersection([1, 2], [3, 4]) -> []
# Identical arrays
intersection([1, 2, 3], [1, 2, 3]) -> [1, 2, 3]

CLI Usage:

echo '{}' | jpx 'intersection([1, 2], [2, 3])'

last

Get last element of array

Signature: array -> any

Examples:

# Get last number
last([1, 2, 3]) -> 3
# Get last string
last(['a', 'b']) -> 'b'
# Empty array returns null
last([]) -> null

CLI Usage:

echo '{}' | jpx 'last([1, 2, 3])'

pairwise

Return adjacent pairs from array

Signature: array -> array

Examples:

# Adjacent pairs
pairwise([1, 2, 3]) -> [[1, 2], [2, 3]]
# Single pair
pairwise([1, 2]) -> [[1, 2]]
# Too few elements
pairwise([1]) -> []

CLI Usage:

echo '{}' | jpx 'pairwise([1, 2, 3])'

range

Generate array of numbers

Signature: number, number -> array

Examples:

# Range 1 to 4
range(`1`, `5`) -> [1, 2, 3, 4]
# Range from zero
range(`0`, `3`) -> [0, 1, 2]
# Empty range
range(`5`, `5`) -> []

CLI Usage:

echo '{}' | jpx 'range(`1`, `5`)'

sliding_window

Create overlapping windows of size n (alias for window)

Signature: array, number -> array

Aliases: window

Examples:

# Size 2 windows
sliding_window([1, 2, 3, 4], `2`) -> [[1, 2], [2, 3], [3, 4]]
# Size 3 windows
sliding_window([1, 2, 3, 4], `3`) -> [[1, 2, 3], [2, 3, 4]]
# Window larger than array
sliding_window([1, 2], `3`) -> []

CLI Usage:

echo '{}' | jpx 'sliding_window([1, 2, 3, 4], `2`)'

take

Take first n elements

Signature: array, number -> array

Examples:

# Take first 2
take([1, 2, 3, 4], `2`) -> [1, 2]
# Take none
take([1, 2, 3], `0`) -> []
# Take more than length
take([1, 2], `5`) -> [1, 2]

CLI Usage:

echo '{}' | jpx 'take([1, 2, 3, 4], `2`)'

transpose

Transpose a 2D array (swap rows and columns)

Signature: array -> array

Examples:

# Swap rows/columns
transpose([[1, 2], [3, 4]]) -> [[1, 3], [2, 4]]
# 2x3 to 3x2
transpose([[1, 2, 3], [4, 5, 6]]) -> [[1, 4], [2, 5], [3, 6]]
# Empty array
transpose([]) -> []

CLI Usage:

echo '{}' | jpx 'transpose([[1, 2], [3, 4]])'

union

Unique elements from both arrays

Signature: array, array -> array

Examples:

# Combine with dedup
union([1, 2], [2, 3]) -> [1, 2, 3]
# No overlap
union([1, 2], [3, 4]) -> [1, 2, 3, 4]
# Empty first array
union([], [1, 2]) -> [1, 2]

CLI Usage:

echo '{}' | jpx 'union([1, 2], [2, 3])'

unique

Remove duplicate values

Signature: array -> array

Examples:

# Basic deduplication
unique([1, 2, 1, 3]) -> [1, 2, 3]
# String values
unique(['a', 'b', 'a']) -> ['a', 'b']
# Empty array
unique([]) -> []

CLI Usage:

echo '{}' | jpx 'unique([1, 2, 1, 3])'

zip

Zip two arrays together

Signature: array, array -> array

JEP: JEP-013

Examples:

# Basic zip
zip([1, 2], ['a', 'b']) -> [[1, 'a'], [2, 'b']]
# Unequal lengths (truncates)
zip([1, 2, 3], ['a', 'b']) -> [[1, 'a'], [2, 'b']]
# Empty arrays
zip([], []) -> []

CLI Usage:

echo '{}' | jpx 'zip([1, 2], ['a', 'b'])'

Object Functions

Functions for working with JSON objects: merging, filtering keys/values, and transformations.

Summary

FunctionSignatureDescription
camel_keysany -> anyRecursively convert all keys to camelCase
chunk_by_sizearray, number -> arraySplit an array into chunks of approximately the specified byte size
compact_deeparray -> arrayRecursively compact arrays, removing nulls at all levels
completenessobject -> numberCalculate percentage of non-null fields (0-100)
data_quality_scoreany -> objectAnalyze data quality and return score with detailed issues
deep_diffobject, object -> objectStructural diff between two objects
deep_equalsany, any -> booleanDeep equality check for any two values
deep_mergeobject, object -> objectRecursively merge objects
defaultsobject, object -> objectAssign default values for missing keys (shallow)
defaults_deepobject, object -> objectRecursively assign default values for missing keys
delete_pathany, string -> anyDelete value at JSON pointer path (immutable)
estimate_sizeany -> numberEstimate the JSON serialization size in bytes
flattenobject -> objectAlias for flatten_keys - flatten nested object with dot notation keys
flatten_arrayany, string? -> objectFlatten nested objects and arrays with dot notation keys (arrays use numeric indices)
flatten_keysobject -> objectFlatten nested object with dot notation keys
from_itemsarray -> objectConvert array of [key, value] pairs to object
getany, string, any? -> anyGet value at dot-separated path with optional default
hasany, string -> booleanCheck if dot-separated path exists
has_same_shapeany, any -> booleanCheck if two values have the same structure (ignoring actual values)
infer_schemaany -> objectInfer a JSON Schema-like type description from a value
invertobject -> objectSwap keys and values
itemsobject -> arrayConvert object to array of [key, value] pairs
kebab_keysany -> anyRecursively convert all keys to kebab-case
leavesany -> arrayGet all leaf values (non-object, non-array)
pascal_keysany -> anyRecursively convert all keys to PascalCase
leaves_with_pathsany -> arrayGet all leaf values with their JSON pointer paths
maskstring, number? -> stringMask a string, showing only the last N characters
omitobject, array -> objectRemove specific keys from object
paginatearray, number, number -> objectGet a page of items from an array with metadata
pathsany -> arrayList all JSON pointer paths in value
paths_toany, string -> arrayFind all dot-notation paths to a key anywhere in structure
pickobject, array -> objectSelect specific keys from object
pluck_deepany, string -> arrayFind all values for a key anywhere in nested structure
redactany, array -> anyRecursively replace values at specified keys with [REDACTED]
redact_keysany, string -> anyRecursively redact keys matching a regex pattern
remove_emptyany -> anyRecursively remove nulls, empty strings, empty arrays, and empty objects
remove_empty_stringsany -> anyRecursively remove empty string values
remove_nullsany -> anyRecursively remove null values
rename_keysobject, object -> objectRename object keys
set_pathany, string, any -> anySet value at JSON pointer path (immutable)
shouty_kebab_keysany -> anyRecursively convert all keys to SHOUTY-KEBAB-CASE
shouty_snake_keysany -> anyRecursively convert all keys to SHOUTY_SNAKE_CASE
snake_keysany -> anyRecursively convert all keys to snake_case
train_keysany -> anyRecursively convert all keys to Train-Case
structural_diffany, any -> objectCompare two values and return their structural differences
templateobject, string -> stringExpand a template string with values from an object using {{key}} syntax
template_strictobject, string -> string | nullExpand a template string, returning null if any variable is missing
truncate_to_sizearray, number -> arrayTruncate an array to fit within approximately the specified byte size
type_consistencyarray -> objectCheck if array elements have consistent types
unflattenobject -> objectAlias for unflatten_keys - restore nested object from dot notation keys
unflatten_keysobject -> objectRestore nested object from dot notation keys
with_entriesobject, string -> objectTransform object entries using an expression (jq parity)

Functions

camel_keys

Recursively convert all keys to camelCase

Signature: any -> any

Examples:

# Snake to camel
camel_keys({user_name: "alice"}) -> {userName: "alice"}
# Kebab to camel
camel_keys({"user-name": "bob"}) -> {userName: "bob"}
# Nested
camel_keys({user_info: {first_name: "x"}}) -> {userInfo: {firstName: "x"}}

CLI Usage:

echo '{}' | jpx 'camel_keys({user_name: "alice"})'

chunk_by_size

Split an array into chunks of approximately the specified byte size

Signature: array, number -> array

Examples:

# All fit in one chunk
chunk_by_size([{a:1}, {b:2}, {c:3}], `100`) -> [[{a:1}, {b:2}, {c:3}]]
# Split into chunks
chunk_by_size([{a:1}, {b:2}], `10`) -> [[{a:1}], [{b:2}]]

CLI Usage:

echo '{}' | jpx 'chunk_by_size([{a:1}, {b:2}, {c:3}], `100`)'

compact_deep

Recursively compact arrays, removing nulls at all levels

Signature: array -> array

Examples:

# Remove nulls from nested arrays
compact_deep([[1, null], [null, 2]]) -> [[1], [2]]
# Deep nesting
compact_deep([[1, null], [null, [2, null]]]) -> [[1], [[2]]]
# Simple array
compact_deep([1, null, 3]) -> [1, 3]

CLI Usage:

echo '{}' | jpx 'compact_deep([[1, null], [null, 2]])'

completeness

Calculate percentage of non-null fields (0-100)

Signature: object -> number

Examples:

# All fields filled
completeness({a: 1, b: 2, c: 3}) -> 100
# One of three filled
completeness({a: 1, b: null, c: null}) -> 33.33
# Nested null counted
completeness({a: 1, b: {c: null}}) -> 66.67
# Empty object is complete
completeness({}) -> 100

CLI Usage:

echo '{}' | jpx 'completeness({a: 1, b: 2, c: 3})'

data_quality_score

Analyze data quality and return score with detailed issues

Signature: any -> object

Examples:

# Perfect data
data_quality_score({a: 1, b: 'hello'}).score -> 100
# Issues detected
data_quality_score({a: null, b: ''}).issues -> [{path: 'a', issue: 'null'}, ...]
# Count null fields
data_quality_score(users).null_count -> 5
# Count type mismatches
data_quality_score(data).type_inconsistencies -> 2

CLI Usage:

echo '{}' | jpx 'data_quality_score({a: 1, b: 'hello'}).score'

deep_diff

Structural diff between two objects

Signature: object, object -> object

Examples:

# Changed value
deep_diff({a: 1}, {a: 2}) -> {added: {}, removed: {}, changed: {a: {from: 1, to: 2}}}
# Added and removed
deep_diff({a: 1}, {b: 2}) -> {added: {b: 2}, removed: {a: 1}, changed: {}}
# No differences
deep_diff({a: 1}, {a: 1}) -> {added: {}, removed: {}, changed: {}}
# New key added
deep_diff({}, {a: 1}) -> {added: {a: 1}, removed: {}, changed: {}}

CLI Usage:

echo '{}' | jpx 'deep_diff({a: 1}, {a: 2})'

deep_equals

Deep equality check for any two values

Signature: any, any -> boolean

Examples:

# Equal nested objects
deep_equals({a: {b: 1}}, {a: {b: 1}}) -> true
# Different values
deep_equals({a: 1}, {a: 2}) -> false
# Equal arrays
deep_equals([1, 2], [1, 2]) -> true
# Order matters
deep_equals([1, 2], [2, 1]) -> false

CLI Usage:

echo '{}' | jpx 'deep_equals({a: {b: 1}}, {a: {b: 1}})'

deep_merge

Recursively merge objects

Signature: object, object -> object

Examples:

# Merge nested objects
deep_merge({a: {b: 1}}, {a: {c: 2}}) -> {a: {b: 1, c: 2}}
# Merge flat objects
deep_merge({a: 1}, {b: 2}) -> {a: 1, b: 2}
# Later values override
deep_merge({a: 1}, {a: 2}) -> {a: 2}
# Merge with empty object
deep_merge({}, {a: 1}) -> {a: 1}

CLI Usage:

echo '{}' | jpx 'deep_merge({a: {b: 1}}, {a: {c: 2}})'

defaults

Assign default values for missing keys (shallow)

Signature: object, object -> object

Examples:

# Keep existing, add missing
defaults({a: 1}, {a: 2, b: 3}) -> {a: 1, b: 3}
# All defaults applied
defaults({}, {a: 1, b: 2}) -> {a: 1, b: 2}
# No defaults needed
defaults({a: 1, b: 2}, {}) -> {a: 1, b: 2}
# Different keys
defaults({x: 1}, {y: 2}) -> {x: 1, y: 2}

CLI Usage:

echo '{}' | jpx 'defaults({a: 1}, {a: 2, b: 3})'

defaults_deep

Recursively assign default values for missing keys

Signature: object, object -> object

Examples:

# Merge nested defaults
defaults_deep({a: {b: 1}}, {a: {c: 2}}) -> {a: {b: 1, c: 2}}
# Keep existing nested
defaults_deep({a: {b: 1}}, {a: {b: 2}}) -> {a: {b: 1}}
# Apply all nested defaults
defaults_deep({}, {a: {b: 1}}) -> {a: {b: 1}}
# Different structure
defaults_deep({x: 1}, {y: {z: 2}}) -> {x: 1, y: {z: 2}}

CLI Usage:

echo '{}' | jpx 'defaults_deep({a: {b: 1}}, {a: {c: 2}})'

delete_path

Delete value at JSON pointer path (immutable)

Signature: any, string -> any

Examples:

# Delete top-level key
delete_path({a: 1, b: 2}, '/b') -> {a: 1}
# Delete nested key
delete_path({a: {b: 1, c: 2}}, '/a/b') -> {a: {c: 2}}
# Delete array element
delete_path([1, 2, 3], '/1') -> [1, 3]
# Path not found
delete_path({a: 1}, '/x') -> {a: 1}

CLI Usage:

echo '{}' | jpx 'delete_path({a: 1, b: 2}, `"/b"`)'

estimate_size

Estimate the JSON serialization size in bytes

Signature: any -> number

Examples:

# String with quotes
estimate_size(`"hello"`) -> 7
# Simple object
estimate_size({a: 1}) -> 7
# Simple array
estimate_size([1, 2, 3]) -> 7

CLI Usage:

echo '{}' | jpx 'estimate_size(`"hello"`)'

flatten

Alias for flatten_keys - flatten nested object with dot notation keys

Signature: object -> object

Examples:

# Simple nested
flatten({a: {b: 1}}) -> {\"a.b\": 1}
# Deep nested
flatten({a: {b: {c: 1}}}) -> {\"a.b.c\": 1}
# Flat object
flatten({a: 1, b: 2}) -> {\"a\": 1, \"b\": 2}

CLI Usage:

echo '{}' | jpx 'flatten({a: {b: 1}})'

flatten_array

Flatten nested objects and arrays with dot notation keys (arrays use numeric indices)

Signature: any, string? -> object

Examples:

# Array with indices
flatten_array({a: [1, 2]}) -> {\"a.0\": 1, \"a.1\": 2}
# Nested object with array
flatten_array({a: {b: [1, 2]}}) -> {\"a.b.0\": 1, \"a.b.1\": 2}
# Array of objects
flatten_array([{a: 1}, {b: 2}]) -> {\"0.a\": 1, \"1.b\": 2}
# Custom separator
flatten_array({a: {b: 1}}, '/') -> {\"a/b\": 1}

CLI Usage:

echo '{}' | jpx 'flatten_array({a: [1, 2]})'

flatten_keys

Flatten nested object with dot notation keys

Signature: object -> object

Examples:

# Simple nested
flatten_keys({a: {b: 1}}) -> {\"a.b\": 1}
# Deep nested
flatten_keys({a: {b: {c: 1}}}) -> {\"a.b.c\": 1}
# Flat object
flatten_keys({a: 1, b: 2}) -> {\"a\": 1, \"b\": 2}
# Mixed nesting
flatten_keys({x: {y: 1}, z: 2}) -> {\"x.y\": 1, \"z\": 2}

CLI Usage:

echo '{}' | jpx 'flatten_keys({a: {b: 1}})'

from_items

Convert array of [key, value] pairs to object

Signature: array -> object

JEP: JEP-013

Examples:

# Single pair
from_items([['a', 1]]) -> {a: 1}
# Multiple pairs
from_items([['a', 1], ['b', 2]]) -> {a: 1, b: 2}
# Empty array
from_items([]) -> {}
# String value
from_items([['x', 'hello']]) -> {x: 'hello'}

CLI Usage:

echo '{}' | jpx 'from_items([['a', 1]])'

get

Get value at dot-separated path with optional default

Signature: any, string, any? -> any

Examples:

# Nested path
get({a: {b: 1}}, 'a.b') -> 1
# Top-level key
get({a: 1}, 'a') -> 1
# Missing with default
get({a: 1}, 'x', 'default') -> 'default'
# Deep path
get({a: {b: {c: 3}}}, 'a.b.c') -> 3

CLI Usage:

echo '{}' | jpx 'get({a: {b: 1}}, `"a.b"`)'

has

Check if dot-separated path exists

Signature: any, string -> boolean

Examples:

# Nested path exists
has({a: {b: 1}}, 'a.b') -> true
# Top-level exists
has({a: 1}, 'a') -> true
# Key missing
has({a: 1}, 'b') -> false
# Nested missing
has({a: {b: 1}}, 'a.c') -> false

CLI Usage:

echo '{}' | jpx 'has({a: {b: 1}}, `"a.b"`)'

has_same_shape

Check if two values have the same structure (ignoring actual values)

Signature: any, any -> boolean

Examples:

# Same keys, different values
has_same_shape({a: 1, b: 2}, {a: 99, b: 100}) -> true
# Different keys
has_same_shape({a: 1}, {a: 1, b: 2}) -> false
# Same length arrays
has_same_shape([1, 2], [3, 4]) -> true

CLI Usage:

echo '{}' | jpx 'has_same_shape({a: 1, b: 2}, {a: 99, b: 100})'

infer_schema

Infer a JSON Schema-like type description from a value

Signature: any -> object

Examples:

# Number type
infer_schema(`42`) -> {type: "number"}
# Object schema
infer_schema({name: "alice", age: 30}) -> {type: "object", properties: {name: {type: "string"}, age: {type: "number"}}}
# Array schema
infer_schema([1, 2, 3]) -> {type: "array", items: {type: "number"}}

CLI Usage:

echo '{}' | jpx 'infer_schema(`42`)'

invert

Swap keys and values

Signature: object -> object

Examples:

# Swap key and value
invert({a: 'x'}) -> {x: 'a'}
# Multiple pairs
invert({a: 'b', c: 'd'}) -> {b: 'a', d: 'c'}
# Empty object
invert({}) -> {}
# Real-world example
invert({name: 'id', value: 'data'}) -> {id: 'name', data: 'value'}

CLI Usage:

echo '{}' | jpx 'invert({a: 'x'})'

items

Convert object to array of [key, value] pairs

Signature: object -> array

JEP: JEP-013

Examples:

# Single key
items({a: 1}) -> [[\"a\", 1]]
# Multiple keys
items({a: 1, b: 2}) -> [[\"a\", 1], [\"b\", 2]]
# Empty object
items({}) -> []
# String value
items({x: 'hello'}) -> [[\"x\", \"hello\"]]

CLI Usage:

echo '{}' | jpx 'items({a: 1})'

kebab_keys

Recursively convert all keys to kebab-case

Signature: any -> any

Examples:

# Camel to kebab
kebab_keys({userName: "alice"}) -> {"user-name": "alice"}
# Snake to kebab
kebab_keys({user_name: "bob"}) -> {"user-name": "bob"}
# Nested
kebab_keys({userInfo: {firstName: "x"}}) -> {"user-info": {"first-name": "x"}}

CLI Usage:

echo '{}' | jpx 'kebab_keys({userName: "alice"})'

leaves

Get all leaf values (non-object, non-array)

Signature: any -> array

Examples:

# Mixed structure
leaves({a: 1, b: [2, 3]}) -> [1, 2, 3]
# Nested object
leaves({a: {b: 1}}) -> [1]
# Flat array
leaves([1, 2, 3]) -> [1, 2, 3]
# Flat object
leaves({a: 1, b: 2}) -> [1, 2]

CLI Usage:

echo '{}' | jpx 'leaves({a: 1, b: [2, 3]})'

pascal_keys

Recursively convert all keys to PascalCase

Signature: any -> any

Examples:

# Camel to pascal
pascal_keys({userName: "alice"}) -> {UserName: "alice"}
# Snake to pascal
pascal_keys({user_name: "bob"}) -> {UserName: "bob"}
# Nested
pascal_keys({userInfo: {firstName: "x"}}) -> {UserInfo: {FirstName: "x"}}

CLI Usage:

echo '{}' | jpx 'pascal_keys({user_name: "alice"})'

leaves_with_paths

Get all leaf values with their JSON pointer paths

Signature: any -> array

Examples:

# Single leaf
leaves_with_paths({a: 1}) -> [{path: \"/a\", value: 1}]
# Nested path
leaves_with_paths({a: {b: 1}}) -> [{path: \"/a/b\", value: 1}]
# Array indices
leaves_with_paths([1, 2]) -> [{path: \"/0\", value: 1}, {path: \"/1\", value: 2}]
# Empty object
leaves_with_paths({}) -> []

CLI Usage:

echo '{}' | jpx 'leaves_with_paths({a: 1})'

mask

Mask a string, showing only the last N characters

Signature: string, number? -> string

Examples:

# Credit card default
mask("4111111111111111") -> "************1111"
# Phone with 3 visible
mask("555-123-4567", `3`) -> "*********567"
# Short string masked
mask("abc") -> "***"

CLI Usage:

echo '{}' | jpx 'mask("4111111111111111")'

omit

Remove specific keys from object

Signature: object, array -> object

Examples:

# Remove one key
omit({a: 1, b: 2}, ['a']) -> {b: 2}
# Remove multiple keys
omit({a: 1, b: 2, c: 3}, ['a', 'c']) -> {b: 2}
# Key not present
omit({a: 1}, ['x']) -> {a: 1}
# Empty removal list
omit({a: 1, b: 2}, []) -> {a: 1, b: 2}

CLI Usage:

echo '{}' | jpx 'omit({a: 1, b: 2}, ['a'])'

paginate

Get a page of items from an array with metadata

Signature: array, number, number -> object

Examples:

# First page
paginate([1,2,3,4,5], `2`, `1`) -> {items: [1,2], page: 1, page_size: 2, total_items: 5, total_pages: 3, has_next: true, has_prev: false}
# Last page
paginate([1,2,3,4,5], `2`, `3`) -> {items: [5], page: 3, page_size: 2, total_items: 5, total_pages: 3, has_next: false, has_prev: true}

CLI Usage:

echo '{}' | jpx 'paginate([1,2,3,4,5], `2`, `1`)'

paths

List all JSON pointer paths in value

Signature: any -> array

Examples:

# Nested object
paths({a: {b: 1}}) -> [\"/a\", \"/a/b\"]
# Flat object
paths({a: 1, b: 2}) -> [\"/a\", \"/b\"]
# Array paths
paths([1, 2]) -> [\"/0\", \"/1\"]
# Empty object
paths({}) -> []

CLI Usage:

echo '{}' | jpx 'paths({a: {b: 1}})'

paths_to

Find all dot-notation paths to a key anywhere in structure

Signature: any, string -> array

Examples:

# Find paths to id
paths_to({a: {id: 1}, b: {id: 2}}, "id") -> ["a.id", "b.id"]
# Array paths
paths_to({users: [{id: 1}]}, "id") -> ["users.0.id"]
# Key not found
paths_to({a: 1}, "x") -> []

CLI Usage:

echo '{}' | jpx 'paths_to({a: {id: 1}, b: {id: 2}}, "id")'

pick

Select specific keys from object

Signature: object, array -> object

Examples:

# Pick one key
pick({a: 1, b: 2}, ['a']) -> {a: 1}
# Pick multiple keys
pick({a: 1, b: 2, c: 3}, ['a', 'c']) -> {a: 1, c: 3}
# Key not present
pick({a: 1}, ['x']) -> {}
# Empty pick list
pick({a: 1, b: 2}, []) -> {}

CLI Usage:

echo '{}' | jpx 'pick({a: 1, b: 2}, ['a'])'

pluck_deep

Find all values for a key anywhere in nested structure

Signature: any, string -> array

Examples:

# Find all ids
pluck_deep({users: [{id: 1}, {id: 2}], meta: {id: 99}}, "id") -> [1, 2, 99]
# Nested values
pluck_deep({a: {b: {c: 1}}, d: {c: 2}}, "c") -> [1, 2]
# Key not found
pluck_deep({a: 1}, "x") -> []

CLI Usage:

echo '{}' | jpx 'pluck_deep({users: [{id: 1}, {id: 2}], meta: {id: 99}}, "id")'

redact

Recursively replace values at specified keys with [REDACTED]

Signature: any, array -> any

Examples:

# Redact password
redact({name: "alice", password: "secret"}, ["password"]) -> {name: "alice", password: "[REDACTED]"}
# Nested redact
redact({user: {name: "bob", ssn: "123"}}, ["ssn"]) -> {user: {name: "bob", ssn: "[REDACTED]"}}
# Array of objects
redact([{token: "abc"}], ["token"]) -> [{token: "[REDACTED]"}]

CLI Usage:

echo '{}' | jpx 'redact({name: "alice", password: "secret"}, ["password"])'

redact_keys

Recursively redact keys matching a regex pattern

Signature: any, string -> any

Examples:

# Multiple patterns
redact_keys({password: "x", api_key: "y"}, "password|api_key") -> {password: "[REDACTED]", api_key: "[REDACTED]"}
# Wildcard pattern
redact_keys({secret_key: "a", secret_token: "b"}, "secret.*") -> {secret_key: "[REDACTED]", secret_token: "[REDACTED]"}

CLI Usage:

echo '{}' | jpx 'redact_keys({password: "x", api_key: "y"}, "password|api_key")'

remove_empty

Recursively remove nulls, empty strings, empty arrays, and empty objects

Signature: any -> any

Examples:

# Remove all empty values
remove_empty({a: \"\", b: [], c: {}, d: null, e: \"hello\"}) -> {e: \"hello\"}
# Nested cleanup
remove_empty({a: {b: \"\", c: 1}}) -> {a: {c: 1}}
# Array cleanup
remove_empty([\"\", \"hello\", [], null]) -> [\"hello\"]

CLI Usage:

echo '{}' | jpx 'remove_empty({a: \"\", b: [], c: {}, d: null, e: \"hello\"})'

remove_empty_strings

Recursively remove empty string values

Signature: any -> any

Examples:

# Remove empty strings
remove_empty_strings({name: \"alice\", bio: \"\"}) -> {name: \"alice\"}
# From arrays
remove_empty_strings([\"hello\", \"\", \"world\"]) -> [\"hello\", \"world\"]
# Nested
remove_empty_strings({a: {b: \"\", c: \"x\"}}) -> {a: {c: \"x\"}}

CLI Usage:

echo '{}' | jpx 'remove_empty_strings({name: \"alice\", bio: \"\"})'

remove_nulls

Recursively remove null values

Signature: any -> any

Examples:

# Remove nulls from object
remove_nulls({a: 1, b: null, c: 2}) -> {a: 1, c: 2}
# Nested nulls
remove_nulls({a: {b: null, c: 1}}) -> {a: {c: 1}}
# Remove from array
remove_nulls([1, null, 2]) -> [1, 2]

CLI Usage:

echo '{}' | jpx 'remove_nulls({a: 1, b: null, c: 2})'

rename_keys

Rename object keys

Signature: object, object -> object

Examples:

# Rename one key
rename_keys({a: 1}, {a: 'b'}) -> {b: 1}
# Rename multiple
rename_keys({a: 1, b: 2}, {a: 'x', b: 'y'}) -> {x: 1, y: 2}
# No matching key
rename_keys({a: 1}, {x: 'y'}) -> {a: 1}
# Real-world rename
rename_keys({old: 'value'}, {old: 'new'}) -> {new: 'value'}

CLI Usage:

echo '{}' | jpx 'rename_keys({a: 1}, {a: 'b'})'

set_path

Set value at JSON pointer path (immutable)

Signature: any, string, any -> any

Examples:

# Add new key
set_path({a: 1}, '/b', `2`) -> {a: 1, b: 2}
# Update existing
set_path({a: 1}, '/a', `2`) -> {a: 2}
# Set nested path
set_path({a: {}}, '/a/b', `1`) -> {a: {b: 1}}
# Update array element
set_path([1, 2], '/1', `5`) -> [1, 5]

CLI Usage:

echo '{}' | jpx 'set_path({a: 1}, `"/b"`, `2`)'

shouty_kebab_keys

Recursively convert all keys to SHOUTY-KEBAB-CASE

Signature: any -> any

Examples:

# Camel to shouty kebab
shouty_kebab_keys({userName: "alice"}) -> {"USER-NAME": "alice"}
# Snake to shouty kebab
shouty_kebab_keys({user_name: "bob"}) -> {"USER-NAME": "bob"}
# Nested
shouty_kebab_keys({userInfo: {firstName: "x"}}) -> {"USER-INFO": {"FIRST-NAME": "x"}}

CLI Usage:

echo '{}' | jpx 'shouty_kebab_keys({user_name: "alice"})'

shouty_snake_keys

Recursively convert all keys to SHOUTY_SNAKE_CASE

Signature: any -> any

Examples:

# Camel to shouty snake
shouty_snake_keys({userName: "alice"}) -> {USER_NAME: "alice"}
# Kebab to shouty snake
shouty_snake_keys({"user-name": "bob"}) -> {USER_NAME: "bob"}
# Nested
shouty_snake_keys({userInfo: {firstName: "x"}}) -> {USER_INFO: {FIRST_NAME: "x"}}

CLI Usage:

echo '{}' | jpx 'shouty_snake_keys({user_name: "alice"})'

snake_keys

Recursively convert all keys to snake_case

Signature: any -> any

Examples:

# Camel to snake
snake_keys({userName: "alice"}) -> {user_name: "alice"}
# Kebab to snake
snake_keys({"user-name": "bob"}) -> {user_name: "bob"}
# Nested
snake_keys({userInfo: {firstName: "x"}}) -> {user_info: {first_name: "x"}}

CLI Usage:

echo '{}' | jpx 'snake_keys({userName: "alice"})'

train_keys

Recursively convert all keys to Train-Case

Signature: any -> any

Examples:

# Camel to train
train_keys({userName: "alice"}) -> {"User-Name": "alice"}
# Snake to train
train_keys({user_name: "bob"}) -> {"User-Name": "bob"}
# Nested
train_keys({userInfo: {firstName: "x"}}) -> {"User-Info": {"First-Name": "x"}}

CLI Usage:

echo '{}' | jpx 'train_keys({user_name: "alice"})'

structural_diff

Compare two values and return their structural differences

Signature: any, any -> object

Examples:

# Changed value
structural_diff({a: 1}, {a: 2}) -> {changed: [{path: "a", from: 1, to: 2}], added: [], removed: []}
# Added key
structural_diff({a: 1}, {a: 1, b: 2}) -> {changed: [], added: [{path: "b", value: 2}], removed: []}
# Removed key
structural_diff({a: 1, b: 2}, {a: 1}) -> {changed: [], added: [], removed: [{path: "b", value: 2}]}

CLI Usage:

echo '{}' | jpx 'structural_diff({a: 1}, {a: 2})'

template

Expand a template string with values from an object using {{key}} syntax

Signature: object, string -> string

Examples:

# Simple substitution
template({name: "Alice"}, `"Hello, {{name}}!"`) -> "Hello, Alice!"
# Nested access
template({user: {name: "Bob"}}, `"Hi {{user.name}}"`) -> "Hi Bob"
# Default value
template({}, `"Hello {{name|World}}"`) -> "Hello World"

CLI Usage:

echo '{}' | jpx 'template({name: "Alice"}, `"Hello, {{name}}!"`)'

template_strict

Expand a template string, returning null if any variable is missing

Signature: object, string -> string | null

Examples:

# All vars present
template_strict({name: "Alice"}, `"Hello, {{name}}!"`) -> "Hello, Alice!"
# Missing variable
template_strict({}, `"Hello, {{name}}!"`) -> null

CLI Usage:

echo '{}' | jpx 'template_strict({name: "Alice"}, `"Hello, {{name}}!"`)'

truncate_to_size

Truncate an array to fit within approximately the specified byte size

Signature: array, number -> array

Examples:

# All fit
truncate_to_size([{a:1}, {b:2}, {c:3}], `100`) -> [{a:1}, {b:2}, {c:3}]
# Truncated
truncate_to_size([{a:1}, {b:2}, {c:3}], `15`) -> [{a:1}, {b:2}]

CLI Usage:

echo '{}' | jpx 'truncate_to_size([{a:1}, {b:2}, {c:3}], `100`)'

type_consistency

Check if array elements have consistent types

Signature: array -> object

Examples:

# Consistent numbers
type_consistency([1, 2, 3]).consistent -> true
# Mixed types
type_consistency([1, 'two', 3]).consistent -> false
# Object field mismatch
type_consistency([{a: 1}, {a: 'x'}]).inconsistencies -> [{field: 'a', ...}]
# Empty array is consistent
type_consistency([]).consistent -> true

CLI Usage:

echo '{}' | jpx 'type_consistency([1, 2, 3]).consistent'

unflatten

Alias for unflatten_keys - restore nested object from dot notation keys

Signature: object -> object

Examples:

# Simple nested
unflatten({\"a.b\": 1}) -> {a: {b: 1}}
# Deep nested
unflatten({\"a.b.c\": 1}) -> {a: {b: {c: 1}}}
# Flat keys
unflatten({\"a\": 1, \"b\": 2}) -> {a: 1, b: 2}

CLI Usage:

echo '{}' | jpx 'unflatten({\"a.b\": 1})'

unflatten_keys

Restore nested object from dot notation keys

Signature: object -> object

Examples:

# Simple nested
unflatten_keys({\"a.b\": 1}) -> {a: {b: 1}}
# Deep nested
unflatten_keys({\"a.b.c\": 1}) -> {a: {b: {c: 1}}}
# Flat keys
unflatten_keys({\"a\": 1, \"b\": 2}) -> {a: 1, b: 2}
# Mixed nesting
unflatten_keys({\"x.y\": 1, \"z\": 2}) -> {x: {y: 1}, z: 2}

CLI Usage:

echo '{}' | jpx 'unflatten_keys({\"a.b\": 1})'

with_entries

Transform object entries using an expression (jq parity)

Signature: object, string -> object

Examples:

# Transform keys and values
with_entries({a: 1, b: 2}, '[upper(@[0]), multiply(@[1], `2`)]') -> {A: 2, B: 4}
# Transform values only
with_entries({a: 1, b: 2}, '[@[0], add(@[1], `10`)]') -> {a: 11, b: 12}
# Filter entries
with_entries({a: 1, b: 2, c: 3}, 'if(@[1] > `1`, @, `null`)') -> {b: 2, c: 3}

CLI Usage:

echo '{}' | jpx 'with_entries({a: 1, b: 2}, `"[upper(@[0]), multiply(@[1], `2`)]"`)'

Math Functions

Mathematical and statistical functions: arithmetic, rounding, statistics, and number formatting.

Summary

FunctionSignatureDescription
abs_fnnumber -> numberAbsolute value
addnumber, number -> numberAdd two numbers
ceil_fnnumber -> numberRound up to nearest integer
clampnumber, number, number -> numberClamp value to range
cosnumber -> numberCosine function
covariancearray, array -> numberCovariance between two arrays
cumulative_sumarray -> arrayCalculate running cumulative sum of a numeric array
dividenumber, number -> numberDivide first number by second
ewmaarray, number -> arrayExponential weighted moving average
floor_fnnumber -> numberRound down to nearest integer
format_numbernumber, number?, string? -> stringFormat number with separators and optional suffix
lognumber -> numberNatural logarithm
medianarray -> numberCalculate median of array
mod_fnnumber, number -> numberModulo operation
modearray -> anyFind the most common value in an array
moving_avgarray, number -> arraySimple moving average with window size
multiplynumber, number -> numberMultiply two numbers
outliers_iqrarray, number? -> arrayFind outliers using IQR method (values outside Q1-1.5IQR to Q3+1.5IQR)
outliers_zscorearray, number? -> arrayFind outliers using z-score method (values with |z-score| > threshold)
percentilearray, number -> numberCalculate percentile of array
pownumber, number -> numberRaise to power
quantilearray, number -> numberNth quantile (generalized percentile, q in [0,1])
quartilesarray -> objectCalculate quartiles (Q1, Q2, Q3) and IQR of array
rate_of_changearray -> arrayCalculate percentage change between consecutive values
roundnumber, number -> numberRound to specified decimal places
sinnumber -> numberSine function
sqrtnumber -> numberSquare root
standardizearray -> arrayStandardize array to mean=0, std=1 (z-score normalization)
stddevarray -> numberCalculate standard deviation of array
subtractnumber, number -> numberSubtract second number from first
tannumber -> numberTangent function
to_fixednumber, number -> stringFormat number with exact decimal places
trendarray -> stringDetect trend direction in a numeric array (increasing, decreasing, or stable)
trend_slopearray -> numberCalculate the linear regression slope of a numeric array
variancearray -> numberCalculate variance of array

Functions

abs_fn

Absolute value

Signature: number -> number

Examples:

# Negative to positive
abs_fn(`-5`) -> 5
# Already positive
abs_fn(`5`) -> 5
# Zero stays zero
abs_fn(`0`) -> 0
# Negative float
abs_fn(`-3.14`) -> 3.14

CLI Usage:

echo '{}' | jpx 'abs_fn(`-5`)'

add

Add two numbers

Signature: number, number -> number

Examples:

# Add integers
add(`2`, `3`) -> 5
# Add floats
add(`1.5`, `2.5`) -> 4.0
# With negative
add(`-5`, `3`) -> -2
# Add zero
add(`0`, `10`) -> 10

CLI Usage:

echo '{}' | jpx 'add(`2`, `3`)'

ceil_fn

Round up to nearest integer

Signature: number -> number

Examples:

# Round up fraction
ceil_fn(`3.2`) -> 4
# Round up high fraction
ceil_fn(`3.9`) -> 4
# Already integer
ceil_fn(`3.0`) -> 3
# Negative rounds toward zero
ceil_fn(`-3.2`) -> -3

CLI Usage:

echo '{}' | jpx 'ceil_fn(`3.2`)'

clamp

Clamp value to range

Signature: number, number, number -> number

Examples:

# Above max
clamp(`15`, `0`, `10`) -> 10
# Below min
clamp(`-5`, `0`, `10`) -> 0
# Within range
clamp(`5`, `0`, `10`) -> 5
# Custom range
clamp(`100`, `50`, `75`) -> 75

CLI Usage:

echo '{}' | jpx 'clamp(`15`, `0`, `10`)'

cos

Cosine function

Signature: number -> number

Examples:

# Cos of 0
cos(`0`) -> 1
# Cos of pi
cos(`3.14159`) -> -1
# Cos of pi/2
cos(`1.5708`) -> ~0
# Cos of 2*pi
cos(`6.28318`) -> 1

CLI Usage:

echo '{}' | jpx 'cos(`0`)'

covariance

Covariance between two arrays

Signature: array, array -> number

Examples:

# Perfect positive
covariance([1, 2, 3], [1, 2, 3]) -> 0.666...
# Perfect negative
covariance([1, 2, 3], [3, 2, 1]) -> -0.666...
# No correlation
covariance([1, 2, 3], [1, 1, 1]) -> 0
# From arrays
covariance(x_values, y_values) -> number

CLI Usage:

echo '{}' | jpx 'covariance([1, 2, 3], [1, 2, 3])'

cumulative_sum

Calculate running cumulative sum of a numeric array

Signature: array -> array

Examples:

# Running total
cumulative_sum([1, 2, 3, 4]) -> [1, 3, 6, 10]
# With negatives
cumulative_sum([10, -5, 3]) -> [10, 5, 8]
# Single element
cumulative_sum([100]) -> [100]

CLI Usage:

echo '{}' | jpx 'cumulative_sum([1, 2, 3, 4])'

divide

Divide first number by second

Signature: number, number -> number

Examples:

# Integer division
divide(`10`, `2`) -> 5
# Fractional result
divide(`7`, `2`) -> 3.5
# Repeating decimal
divide(`1`, `3`) -> 0.333...
# Negative dividend
divide(`-10`, `2`) -> -5

CLI Usage:

echo '{}' | jpx 'divide(`10`, `2`)'

ewma

Exponential weighted moving average

Signature: array, number -> array

Examples:

# Alpha 0.5
ewma([1, 2, 3, 4, 5], `0.5`) -> [1, 1.5, 2.25, ...]
# Smooth stock prices
ewma(prices, `0.3`) -> smoothed prices
# High alpha
ewma([10, 20, 30], `0.9`) -> fast response
# Low alpha
ewma([10, 20, 30], `0.1`) -> slow response

CLI Usage:

echo '{}' | jpx 'ewma([1, 2, 3, 4, 5], `0.5`)'

floor_fn

Round down to nearest integer

Signature: number -> number

Examples:

# Round down high fraction
floor_fn(`3.7`) -> 3
# Round down low fraction
floor_fn(`3.2`) -> 3
# Already integer
floor_fn(`3.0`) -> 3
# Negative rounds away from zero
floor_fn(`-3.2`) -> -4

CLI Usage:

echo '{}' | jpx 'floor_fn(`3.7`)'

format_number

Format number with separators and optional suffix

Signature: number, number?, string? -> string

Examples:

# With commas
format_number(`1234567`, `0`) -> \"1,234,567\"
# With decimals
format_number(`1234.56`, `2`) -> \"1,234.56\"
# Million
format_number(`1000000`, `0`) -> \"1,000,000\"
# Small number
format_number(`99.9`, `1`) -> \"99.9\"

CLI Usage:

echo '{}' | jpx 'format_number(`1234567`, `0`)'

log

Natural logarithm

Signature: number -> number

Examples:

# Log of e
log(`2.718`) -> ~1
# Log of 1
log(`1`) -> 0
# Log of 10
log(`10`) -> 2.302...
# Log of 100
log(`100`) -> 4.605...

CLI Usage:

echo '{}' | jpx 'log(`2.718`)'

median

Calculate median of array

Signature: array -> number

Examples:

# Odd count
median([1, 2, 3, 4, 5]) -> 3
# Even count
median([1, 2, 3, 4]) -> 2.5
# Middle value
median([10, 20, 30]) -> 20
# Single element
median([5]) -> 5

CLI Usage:

echo '{}' | jpx 'median([1, 2, 3, 4, 5])'

mod_fn

Modulo operation

Signature: number, number -> number

Examples:

# Remainder of 10/3
mod_fn(`10`, `3`) -> 1
# Evenly divisible
mod_fn(`15`, `5`) -> 0
# Odd check
mod_fn(`7`, `2`) -> 1
# Larger numbers
mod_fn(`100`, `7`) -> 2

CLI Usage:

echo '{}' | jpx 'mod_fn(`10`, `3`)'

mode

Find the most common value in an array

Signature: array -> any

Examples:

# Most frequent
mode([1, 2, 2, 3]) -> 2
# String mode
mode(['a', 'b', 'a', 'a']) -> 'a'
# Tie returns first
mode([1, 1, 2, 2, 3]) -> 1 or 2
# Single element
mode([5]) -> 5

CLI Usage:

echo '{}' | jpx 'mode([1, 2, 2, 3])'

moving_avg

Simple moving average with window size

Signature: array, number -> array

Examples:

# Window of 3
moving_avg([1, 2, 3, 4, 5], `3`) -> [null, null, 2, 3, 4]
# Window of 2
moving_avg([10, 20, 30, 40], `2`) -> [null, 15, 25, 35]
# 7-day moving avg
moving_avg(prices, `7`) -> weekly average
# Window of 1
moving_avg([1, 2, 3], `1`) -> [1, 2, 3]

CLI Usage:

echo '{}' | jpx 'moving_avg([1, 2, 3, 4, 5], `3`)'

multiply

Multiply two numbers

Signature: number, number -> number

Examples:

# Basic multiplication
multiply(`4`, `3`) -> 12
# With decimals
multiply(`2.5`, `4`) -> 10
# With negative
multiply(`-3`, `4`) -> -12
# Multiply by zero
multiply(`0`, `100`) -> 0

CLI Usage:

echo '{}' | jpx 'multiply(`4`, `3`)'

outliers_iqr

Find outliers using IQR method (values outside Q1-1.5IQR to Q3+1.5IQR)

Signature: array, number? -> array

Examples:

# Detect outlier
outliers_iqr([1, 2, 3, 4, 100]) -> [100]
# No outliers
outliers_iqr([1, 2, 3, 4, 5]) -> []
# Custom multiplier
outliers_iqr(values, `3.0`) -> extreme outliers only
# Find anomalies
outliers_iqr(measurements) -> anomalies

CLI Usage:

echo '{}' | jpx 'outliers_iqr([1, 2, 3, 4, 100])'

outliers_zscore

Find outliers using z-score method (values with |z-score| > threshold)

Signature: array, number? -> array

Examples:

# Detect outlier
outliers_zscore([1, 2, 3, 4, 100]) -> [100]
# No outliers
outliers_zscore([1, 2, 3, 4, 5]) -> []
# Custom threshold
outliers_zscore(values, `3.0`) -> extreme outliers only
# Lower threshold
outliers_zscore(measurements, `1.5`) -> more sensitive detection

CLI Usage:

echo '{}' | jpx 'outliers_zscore([1, 2, 3, 4, 100])'

percentile

Calculate percentile of array

Signature: array, number -> number

Examples:

# 50th percentile (median)
percentile([1, 2, 3, 4, 5], `50`) -> 3
# 25th percentile
percentile([1, 2, 3, 4, 5], `25`) -> 2
# 75th percentile
percentile([1, 2, 3, 4, 5], `75`) -> 4
# 90th percentile
percentile(scores, `90`) -> top 10% threshold

CLI Usage:

echo '{}' | jpx 'percentile([1, 2, 3, 4, 5], `50`)'

pow

Raise to power

Signature: number, number -> number

Examples:

# 2 cubed
pow(`2`, `3`) -> 8
# 10 squared
pow(`10`, `2`) -> 100
# 2^10
pow(`2`, `10`) -> 1024
# Square root via power
pow(`4`, `0.5`) -> 2

CLI Usage:

echo '{}' | jpx 'pow(`2`, `3`)'

quantile

Nth quantile (generalized percentile, q in [0,1])

Signature: array, number -> number

Examples:

# Median (0.5)
quantile([1, 2, 3, 4, 5], `0.5`) -> 3
# First quartile
quantile([1, 2, 3, 4, 5], `0.25`) -> 2
# Third quartile
quantile([1, 2, 3, 4, 5], `0.75`) -> 4
# 90th quantile
quantile(values, `0.9`) -> 90th quantile

CLI Usage:

echo '{}' | jpx 'quantile([1, 2, 3, 4, 5], `0.5`)'

quartiles

Calculate quartiles (Q1, Q2, Q3) and IQR of array

Signature: array -> object

Examples:

# Basic quartiles
quartiles([1, 2, 3, 4, 5]) -> {min: 1, q1: 2, q2: 3, q3: 4, max: 5, iqr: 2}
# Get IQR
quartiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).iqr -> 4.5
# Q2 equals median
quartiles(values).q2 == median(values) -> true
# Distribution analysis
quartiles(scores) -> full distribution stats

CLI Usage:

echo '{}' | jpx 'quartiles([1, 2, 3, 4, 5])'

rate_of_change

Calculate percentage change between consecutive values

Signature: array -> array

Examples:

# 10% growth
rate_of_change([100, 110, 121]) -> [10.0, 10.0]
# 50% decline
rate_of_change([100, 50, 25]) -> [-50.0, -50.0]
# Varying rate
rate_of_change([1, 2, 3]) -> [100.0, 50.0]

CLI Usage:

echo '{}' | jpx 'rate_of_change([100, 110, 121])'

round

Round to specified decimal places

Signature: number, number -> number

Examples:

# Two decimals
round(`3.14159`, `2`) -> 3.14
# Round to integer
round(`3.5`, `0`) -> 4
# Round up
round(`2.555`, `2`) -> 2.56
# One decimal
round(`123.456`, `1`) -> 123.5

CLI Usage:

echo '{}' | jpx 'round(`3.14159`, `2`)'

sin

Sine function

Signature: number -> number

Examples:

# Sin of 0
sin(`0`) -> 0
# Sin of pi/2
sin(`1.5708`) -> 1
# Sin of pi
sin(`3.14159`) -> ~0
# Sin of 2*pi
sin(`6.28318`) -> ~0

CLI Usage:

echo '{}' | jpx 'sin(`0`)'

sqrt

Square root

Signature: number -> number

Examples:

# Perfect square
sqrt(`16`) -> 4
# Irrational result
sqrt(`2`) -> 1.414...
# Larger number
sqrt(`100`) -> 10
# Zero
sqrt(`0`) -> 0

CLI Usage:

echo '{}' | jpx 'sqrt(`16`)'

standardize

Standardize array to mean=0, std=1 (z-score normalization)

Signature: array -> array

Examples:

# Basic z-scores
standardize([10, 20, 30]) -> [-1.22, 0, 1.22]
# Normalize values
standardize([1, 2, 3, 4, 5]) -> normalized
# Standardize scores
standardize(scores) -> z-scores
# Identical values
standardize([5, 5, 5]) -> [0, 0, 0]

CLI Usage:

echo '{}' | jpx 'standardize([10, 20, 30])'

stddev

Calculate standard deviation of array

Signature: array -> number

Examples:

# Basic stddev
stddev([1, 2, 3, 4, 5]) -> 1.414...
# No variation
stddev([10, 10, 10]) -> 0
# High variation
stddev([1, 100]) -> ~49.5
# Measure spread
stddev(values) -> spread measure

CLI Usage:

echo '{}' | jpx 'stddev([1, 2, 3, 4, 5])'

subtract

Subtract second number from first

Signature: number, number -> number

Examples:

# Basic subtraction
subtract(`5`, `3`) -> 2
# Negative result
subtract(`10`, `15`) -> -5
# With decimals
subtract(`3.5`, `1.5`) -> 2
# From zero
subtract(`0`, `5`) -> -5

CLI Usage:

echo '{}' | jpx 'subtract(`5`, `3`)'

tan

Tangent function

Signature: number -> number

Examples:

# Tan of 0
tan(`0`) -> 0
# Tan of pi/4
tan(`0.7854`) -> ~1
# Tan of pi
tan(`3.14159`) -> ~0
# Calculate tangent
tan(angle) -> ratio

CLI Usage:

echo '{}' | jpx 'tan(`0`)'

to_fixed

Format number with exact decimal places

Signature: number, number -> string

Examples:

# Two decimals
to_fixed(`3.14159`, `2`) -> \"3.14\"
# Pad with zeros
to_fixed(`5`, `2`) -> \"5.00\"
# Round to integer
to_fixed(`99.9`, `0`) -> \"100\"
# Extra precision
to_fixed(`1.5`, `3`) -> \"1.500\"

CLI Usage:

echo '{}' | jpx 'to_fixed(`3.14159`, `2`)'

trend

Detect trend direction in a numeric array (increasing, decreasing, or stable)

Signature: array -> string

Examples:

# Upward trend
trend([1, 2, 3, 4, 5]) -> "increasing"
# Downward trend
trend([5, 4, 3, 2, 1]) -> "decreasing"
# No change
trend([3, 3, 3, 3]) -> "stable"

CLI Usage:

echo '{}' | jpx 'trend([1, 2, 3, 4, 5])'

trend_slope

Calculate the linear regression slope of a numeric array

Signature: array -> number

Examples:

# Perfect linear increase
trend_slope([1, 2, 3, 4, 5]) -> 1.0
# Perfect linear decrease
trend_slope([5, 4, 3, 2, 1]) -> -1.0
# Noisy upward
trend_slope([1, 3, 2, 4]) -> ~0.7

CLI Usage:

echo '{}' | jpx 'trend_slope([1, 2, 3, 4, 5])'

variance

Calculate variance of array

Signature: array -> number

Examples:

# Basic variance
variance([1, 2, 3, 4, 5]) -> 2
# No variation
variance([10, 10, 10]) -> 0
# High variation
variance([1, 100]) -> 2450.25
# Squared spread
variance(values) -> spread^2

CLI Usage:

echo '{}' | jpx 'variance([1, 2, 3, 4, 5])'

Date/Time Functions

Functions for working with dates and times: parsing, formatting, arithmetic, and timezone handling.

Summary

FunctionSignatureDescription
business_days_betweennumber, number -> numberCount business days (weekdays) between two timestamps
date_addnumber, number, string -> numberAdd time to timestamp
date_diffnumber, number, string -> numberDifference between timestamps
duration_sincenumber|string -> objectGet detailed duration object from timestamp to now
end_of_daynumber|string -> stringGet ISO 8601 string for end of day (23:59:59)
epoch_ms-> numberCurrent Unix timestamp in milliseconds (alias for now_ms)
format_datenumber, string -> stringFormat timestamp to string
from_epochnumber -> stringConvert Unix timestamp (seconds) to ISO 8601 string
from_epoch_msnumber -> stringConvert Unix timestamp (milliseconds) to ISO 8601 string
is_afternumber|string, number|string -> booleanCheck if first date is after second date (accepts timestamps or date strings)
is_beforenumber|string, number|string -> booleanCheck if first date is before second date (accepts timestamps or date strings)
is_betweennumber|string, number|string, number|string -> booleanCheck if date is between start and end (inclusive, accepts timestamps or date strings)
is_same_daynumber|string, number|string -> booleanCheck if two timestamps/dates are on the same day
is_weekdaynumber -> booleanCheck if timestamp falls on weekday (Monday-Friday)
is_weekendnumber -> booleanCheck if timestamp falls on weekend (Saturday or Sunday)
parse_datestring, string? -> numberParse date string to timestamp
quarternumber -> numberGet quarter of year (1-4) from timestamp
relative_timenumber -> stringHuman-readable relative time from timestamp
start_of_daynumber|string -> stringGet ISO 8601 string for start of day (00:00:00)
start_of_monthnumber|string -> stringGet ISO 8601 string for start of month
start_of_weeknumber|string -> stringGet ISO 8601 string for start of week (Monday 00:00:00)
start_of_yearnumber|string -> stringGet ISO 8601 string for start of year
time_agonumber|string -> stringHuman-readable time since date (accepts timestamps or date strings)
timezone_convertstring, string, string -> stringConvert timestamp between timezones (IANA timezone names)
to_epochnumber|string -> numberConvert date string or timestamp to Unix timestamp (seconds)
to_epoch_msnumber|string -> numberConvert date string or timestamp to Unix timestamp (milliseconds)

Functions

business_days_between

Count business days (weekdays) between two timestamps

Signature: number, number -> number

Examples:

# Count weekdays
business_days_between(`1704067200`, `1705276800`) -> 10
# Between two timestamps
business_days_between(start_ts, end_ts) -> count
# One week = 5 days
business_days_between(`0`, `604800`) -> 5
# Same day
business_days_between(`0`, `0`) -> 0

CLI Usage:

echo '{}' | jpx 'business_days_between(`1704067200`, `1705276800`)'

date_add

Add time to timestamp

Signature: number, number, string -> number

Examples:

# Add 1 day
date_add(`0`, `1`, 'days') -> 86400
# Add 2 hours
date_add(`0`, `2`, 'hours') -> 7200
# Add 1 week
date_add(timestamp, `7`, 'days') -> next week
# Add 30 minutes
date_add(`0`, `30`, 'minutes') -> 1800

CLI Usage:

echo '{}' | jpx 'date_add(`0`, `1`, `"days"`)'

date_diff

Difference between timestamps

Signature: number, number, string -> number

Examples:

# Diff in days
date_diff(`86400`, `0`, 'days') -> 1
# Diff in hours
date_diff(`7200`, `0`, 'hours') -> 2
# Diff in minutes
date_diff(end_ts, start_ts, 'minutes') -> minutes
# Diff in weeks
date_diff(`604800`, `0`, 'weeks') -> 1

CLI Usage:

echo '{}' | jpx 'date_diff(`86400`, `0`, `"days"`)'

duration_since

Get detailed duration object from timestamp to now

Signature: number|string -> object

Examples:

# From timestamp
duration_since(`1702396800`) -> {days: 1, hours: 0, ...}
# From date string
duration_since('2023-01-01') -> {days: N, ...}
# Time since creation
duration_since(created_at) -> elapsed time
# One hour ago
duration_since(now() - 3600) -> {hours: 1, ...}

CLI Usage:

echo '{}' | jpx 'duration_since(`1702396800`)'

end_of_day

Get ISO 8601 string for end of day (23:59:59)

Signature: number|string -> string

Examples:

# From ISO string
end_of_day('2023-12-13T15:30:00Z') -> \"2023-12-13T23:59:59Z\"
# From timestamp
end_of_day(`1702483200`) -> end of that day
# From date only
end_of_day('2024-01-01') -> last second of day
# End of today
end_of_day(now()) -> today end

CLI Usage:

echo '{}' | jpx 'end_of_day(`"2023-12-13T15:30:00Z"`)'

epoch_ms

Current Unix timestamp in milliseconds (alias for now_ms)

Signature: -> number

Examples:

# Current time in ms
epoch_ms() -> 1702483200000
# Convert to seconds
epoch_ms() / `1000` -> seconds
# Calculate duration
epoch_ms() - start_ms -> elapsed

CLI Usage:

echo '{}' | jpx 'epoch_ms()'

format_date

Format timestamp to string

Signature: number, string -> string

Examples:

# ISO date format
format_date(`1705276800`, '%Y-%m-%d') -> \"2024-01-15\"
# Long date format
format_date(ts, '%B %d, %Y') -> \"January 15, 2024\"
# Time only
format_date(ts, '%H:%M:%S') -> \"10:30:00\"
# Full ISO 8601
format_date(ts, '%Y-%m-%dT%H:%M:%SZ') -> ISO string

CLI Usage:

echo '{}' | jpx 'format_date(`1705276800`, `"%Y-%m-%d"`)'

from_epoch

Convert Unix timestamp (seconds) to ISO 8601 string

Signature: number -> string

Examples:

# Convert seconds
from_epoch(`1702483200`) -> \"2023-12-13T16:00:00Z\"
# Unix epoch
from_epoch(`0`) -> \"1970-01-01T00:00:00Z\"
# Convert field
from_epoch(created_at) -> ISO string
# Current time
from_epoch(now()) -> current ISO

CLI Usage:

echo '{}' | jpx 'from_epoch(`1702483200`)'

from_epoch_ms

Convert Unix timestamp (milliseconds) to ISO 8601 string

Signature: number -> string

Examples:

# From milliseconds
from_epoch_ms(`1702483200000`) -> \"2023-12-13T16:00:00Z\"
# Unix epoch
from_epoch_ms(`0`) -> \"1970-01-01T00:00:00Z\"
# JS timestamp
from_epoch_ms(js_timestamp) -> ISO
# Current time
from_epoch_ms(epoch_ms()) -> current

CLI Usage:

echo '{}' | jpx 'from_epoch_ms(`1702483200000`)'

is_after

Check if first date is after second date (accepts timestamps or date strings)

Signature: number|string, number|string -> boolean

Examples:

# String comparison
is_after('2024-07-15', '2024-01-01') -> true
# Timestamp comparison
is_after(`1705276800`, `1704067200`) -> true
# Field comparison
is_after(end_date, start_date) -> true/false
# Same date
is_after('2024-01-01', '2024-01-01') -> false

CLI Usage:

echo '{}' | jpx 'is_after(`"2024-07-15"`, `"2024-01-01"`)'

is_before

Check if first date is before second date (accepts timestamps or date strings)

Signature: number|string, number|string -> boolean

Examples:

# String comparison
is_before('2024-01-01', '2024-07-15') -> true
# Timestamp comparison
is_before(`1704067200`, `1705276800`) -> true
# Field comparison
is_before(start_date, end_date) -> true/false
# Same date
is_before('2024-01-01', '2024-01-01') -> false

CLI Usage:

echo '{}' | jpx 'is_before(`"2024-01-01"`, `"2024-07-15"`)'

is_between

Check if date is between start and end (inclusive, accepts timestamps or date strings)

Signature: number|string, number|string, number|string -> boolean

Examples:

# Within range
is_between('2024-06-15', '2024-01-01', '2024-12-31') -> true
# Before range
is_between('2023-06-15', '2024-01-01', '2024-12-31') -> false
# Check event date
is_between(event_date, start, end) -> true/false
# Inclusive start
is_between('2024-01-01', '2024-01-01', '2024-12-31') -> true

CLI Usage:

echo '{}' | jpx 'is_between(`"2024-06-15"`, `"2024-01-01"`, `"2024-12-31"`)'

is_same_day

Check if two timestamps/dates are on the same day

Signature: number|string, number|string -> boolean

Examples:

# Same day, different time
is_same_day('2023-12-13T10:00:00Z', '2023-12-13T23:00:00Z') -> true
# Different days
is_same_day('2023-12-13', '2023-12-14') -> false
# Compare fields
is_same_day(created_at, updated_at) -> true/false
# Today check
is_same_day(now(), event_time) -> true/false

CLI Usage:

echo '{}' | jpx 'is_same_day(`"2023-12-13T10:00:00Z"`, `"2023-12-13T23:00:00Z"`)'

is_weekday

Check if timestamp falls on weekday (Monday-Friday)

Signature: number -> boolean

Examples:

# Monday is weekday
is_weekday(`1705276800`) -> true
# Check current day
is_weekday(now()) -> true/false
# Check event day
is_weekday(event_timestamp) -> true/false
# Saturday is not weekday
is_weekday(`1705104000`) -> false

CLI Usage:

echo '{}' | jpx 'is_weekday(`1705276800`)'

is_weekend

Check if timestamp falls on weekend (Saturday or Sunday)

Signature: number -> boolean

Examples:

# Saturday is weekend
is_weekend(`1705104000`) -> true
# Check current day
is_weekend(now()) -> true/false
# Monday is not weekend
is_weekend(`1705276800`) -> false
# Check event day
is_weekend(event_timestamp) -> true/false

CLI Usage:

echo '{}' | jpx 'is_weekend(`1705104000`)'

parse_date

Parse date string to timestamp

Signature: string, string? -> number

Examples:

# ISO date format
parse_date('2024-01-15', '%Y-%m-%d') -> 1705276800
# US date format
parse_date('01/15/2024', '%m/%d/%Y') -> timestamp
# Named month
parse_date('15-Jan-2024', '%d-%b-%Y') -> timestamp
# With time
parse_date('2024-01-15T10:30:00', '%Y-%m-%dT%H:%M:%S') -> timestamp

CLI Usage:

echo '{}' | jpx 'parse_date(`"2024-01-15"`, `"%Y-%m-%d"`)'

quarter

Get quarter of year (1-4) from timestamp

Signature: number -> number

Examples:

# April is Q2
quarter(`1713139200`) -> 2
# January is Q1
quarter(`1704067200`) -> 1
# July is Q3
quarter(`1719792000`) -> 3
# November is Q4
quarter(`1730419200`) -> 4

CLI Usage:

echo '{}' | jpx 'quarter(`1713139200`)'

relative_time

Human-readable relative time from timestamp

Signature: number -> string

Examples:

# One hour ago
relative_time(now() - 3600) -> \"1 hour ago\"
# One minute ago
relative_time(now() - 60) -> \"1 minute ago\"
# One day ago
relative_time(now() - 86400) -> \"1 day ago\"
# From field
relative_time(created_at) -> human readable

CLI Usage:

echo '{}' | jpx 'relative_time(now() - 3600)'

start_of_day

Get ISO 8601 string for start of day (00:00:00)

Signature: number|string -> string

Examples:

# From ISO string
start_of_day('2023-12-13T15:30:00Z') -> \"2023-12-13T00:00:00Z\"
# From timestamp
start_of_day(`1702483200`) -> start of that day
# Today start
start_of_day(now()) -> today midnight
# From date only
start_of_day('2024-01-15') -> midnight

CLI Usage:

echo '{}' | jpx 'start_of_day(`"2023-12-13T15:30:00Z"`)'

start_of_month

Get ISO 8601 string for start of month

Signature: number|string -> string

Examples:

# From ISO string
start_of_month('2023-12-13T15:30:00Z') -> \"2023-12-01T00:00:00Z\"
# Current month start
start_of_month(now()) -> first of month
# From timestamp
start_of_month(`1702483200`) -> month start
# March start
start_of_month('2024-03-15') -> \"2024-03-01T00:00:00Z\"

CLI Usage:

echo '{}' | jpx 'start_of_month(`"2023-12-13T15:30:00Z"`)'

start_of_week

Get ISO 8601 string for start of week (Monday 00:00:00)

Signature: number|string -> string

Examples:

# Wednesday to Monday
start_of_week('2023-12-13T15:30:00Z') -> \"2023-12-11T00:00:00Z\"
# Current week start
start_of_week(now()) -> this Monday
# From timestamp
start_of_week(`1702483200`) -> week start
# Monday stays Monday
start_of_week('2024-01-15') -> \"2024-01-15T00:00:00Z\"

CLI Usage:

echo '{}' | jpx 'start_of_week(`"2023-12-13T15:30:00Z"`)'

start_of_year

Get ISO 8601 string for start of year

Signature: number|string -> string

Examples:

# From ISO string
start_of_year('2023-12-13T15:30:00Z') -> \"2023-01-01T00:00:00Z\"
# Current year start
start_of_year(now()) -> Jan 1st
# From timestamp
start_of_year(`1702483200`) -> year start
# Mid-year to Jan 1
start_of_year('2024-06-15') -> \"2024-01-01T00:00:00Z\"

CLI Usage:

echo '{}' | jpx 'start_of_year(`"2023-12-13T15:30:00Z"`)'

time_ago

Human-readable time since date (accepts timestamps or date strings)

Signature: number|string -> string

Examples:

# From date string
time_ago('2020-01-01') -> \"4 years ago\"
# One hour ago
time_ago(now() - 3600) -> \"1 hour ago\"
# From field
time_ago(created_at) -> human readable
# Months ago
time_ago('2023-12-01') -> \"X months ago\"

CLI Usage:

echo '{}' | jpx 'time_ago(`"2020-01-01"`)'

timezone_convert

Convert timestamp between timezones (IANA timezone names)

Signature: string, string, string -> string

Examples:

# NY to London
timezone_convert('2024-01-15T10:00:00', 'America/New_York', 'Europe/London') -> \"2024-01-15T15:00:00\"
# UTC to Pacific
timezone_convert(time, 'UTC', 'America/Los_Angeles') -> PST time
# Tokyo to UTC
timezone_convert(time, 'Asia/Tokyo', 'UTC') -> UTC time
# Paris to Chicago
timezone_convert(time, 'Europe/Paris', 'America/Chicago') -> CST time

CLI Usage:

echo '{}' | jpx 'timezone_convert(`"2024-01-15T10:00:00"`, `"America/New_York"`, `"Europe/London"`)'

to_epoch

Convert date string or timestamp to Unix timestamp (seconds)

Signature: number|string -> number

Examples:

# From ISO string
to_epoch('2023-12-13T16:00:00Z') -> 1702483200
# From date only
to_epoch('2024-01-01') -> timestamp
# Convert field
to_epoch(date_field) -> seconds
# Unix epoch
to_epoch('1970-01-01T00:00:00Z') -> 0

CLI Usage:

echo '{}' | jpx 'to_epoch(`"2023-12-13T16:00:00Z"`)'

to_epoch_ms

Convert date string or timestamp to Unix timestamp (milliseconds)

Signature: number|string -> number

Examples:

# From ISO string
to_epoch_ms('2023-12-13T16:00:00Z') -> 1702483200000
# From date only
to_epoch_ms('2024-01-01') -> timestamp_ms
# Convert field
to_epoch_ms(date_field) -> milliseconds
# Unix epoch
to_epoch_ms('1970-01-01T00:00:00Z') -> 0

CLI Usage:

echo '{}' | jpx 'to_epoch_ms(`"2023-12-13T16:00:00Z"`)'

Duration Functions

Functions for working with time durations.

Summary

FunctionSignatureDescription
duration_hoursnumber -> numberConvert seconds to hours
duration_minutesnumber -> numberConvert seconds to minutes
duration_secondsnumber -> numberGet seconds component
format_durationnumber -> stringFormat seconds as duration string
parse_durationstring -> numberParse duration string to seconds

Functions

duration_hours

Convert seconds to hours

Signature: number -> number

Examples:

# 2 hours
duration_hours(`7200`) -> 2
# 1 hour
duration_hours(`3600`) -> 1
# 1.5 hours
duration_hours(`5400`) -> 1.5

CLI Usage:

echo '{}' | jpx 'duration_hours(`7200`)'

duration_minutes

Convert seconds to minutes

Signature: number -> number

Examples:

# 2 minutes
duration_minutes(`120`) -> 2
# 1 minute
duration_minutes(`60`) -> 1
# 1.5 minutes
duration_minutes(`90`) -> 1.5

CLI Usage:

echo '{}' | jpx 'duration_minutes(`120`)'

duration_seconds

Get seconds component

Signature: number -> number

Examples:

# 65 seconds mod 60
duration_seconds(`65`) -> 5
# Exact minutes
duration_seconds(`120`) -> 0
# 1 hour 1 min 1 sec
duration_seconds(`3661`) -> 1

CLI Usage:

echo '{}' | jpx 'duration_seconds(`65`)'

format_duration

Format seconds as duration string

Signature: number -> string

Examples:

# 1.5 hours
format_duration(`5400`) -> \"1h30m\"
# 1 hour 1 min 1 sec
format_duration(`3661`) -> \"1h1m1s\"
# 1 minute
format_duration(`60`) -> \"1m\"

CLI Usage:

echo '{}' | jpx 'format_duration(`5400`)'

parse_duration

Parse duration string to seconds

Signature: string -> number

Examples:

# 1.5 hours
parse_duration('1h30m') -> 5400
# 2 hours
parse_duration('2h') -> 7200
# 30 seconds
parse_duration('30s') -> 30

CLI Usage:

echo '{}' | jpx 'parse_duration(`"1h30m"`)'

Hash Functions

Cryptographic hash functions: MD5, SHA family, and other hash algorithms.

Summary

FunctionSignatureDescription
crc32string -> numberCalculate CRC32 checksum
hmac_md5string, string -> stringCalculate HMAC-MD5 signature
hmac_sha1string, string -> stringCalculate HMAC-SHA1 signature
hmac_sha256string, string -> stringCalculate HMAC-SHA256 signature
hmac_sha512string, string -> stringCalculate HMAC-SHA512 signature
md5string -> stringCalculate MD5 hash
sha1string -> stringCalculate SHA-1 hash
sha256string -> stringCalculate SHA-256 hash
sha512string -> stringCalculate SHA-512 hash

Functions

crc32

Calculate CRC32 checksum

Signature: string -> number

Examples:

# Simple string
crc32('hello') -> 907060870
# Empty string
crc32('') -> 0
# Data with space
crc32('test data') -> 2706273274

CLI Usage:

echo '{}' | jpx 'crc32(`"hello"`)'

hmac_md5

Calculate HMAC-MD5 signature

Signature: string, string -> string

Examples:

# With secret key
hmac_md5('hello', 'secret') -> \"e17e4e4a205c55782dce5b6ff41e6e19\"
# Different message
hmac_md5('message', 'key') -> \"a24c903c3a7e7b741ea77bd467b98bca\"

CLI Usage:

echo '{}' | jpx 'hmac_md5(`"hello"`, `"secret"`)'

hmac_sha1

Calculate HMAC-SHA1 signature

Signature: string, string -> string

Examples:

# With secret key
hmac_sha1('hello', 'secret') -> \"5112055c36b16a6693045d75a054332e4555b52f\"
# Different data
hmac_sha1('data', 'key') -> \"104152c5bfdca07bc633eebd46199f0255c9f49d\"

CLI Usage:

echo '{}' | jpx 'hmac_sha1(`"hello"`, `"secret"`)'

hmac_sha256

Calculate HMAC-SHA256 signature

Signature: string, string -> string

Examples:

# With secret key
hmac_sha256('hello', 'secret') -> \"88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b\"
# Different data
hmac_sha256('data', 'key') -> \"5031fe3d989c6d1537a013fa6e739da23463fdaec3b70137d828e36ace221bd0\"

CLI Usage:

echo '{}' | jpx 'hmac_sha256(`"hello"`, `"secret"`)'

hmac_sha512

Calculate HMAC-SHA512 signature

Signature: string, string -> string

Examples:

# With secret key
hmac_sha512('hello', 'secret') -> \"d05888a20ae...\"
# Different data
hmac_sha512('data', 'key') -> \"3c5953a18...\"

CLI Usage:

echo '{}' | jpx 'hmac_sha512(`"hello"`, `"secret"`)'

md5

Calculate MD5 hash

Signature: string -> string

Examples:

# Simple string
md5('hello') -> \"5d41402abc4b2a76b9719d911017c592\"
# Empty string
md5('') -> \"d41d8cd98f00b204e9800998ecf8427e\"
# Another string
md5('test') -> \"098f6bcd4621d373cade4e832627b4f6\"

CLI Usage:

echo '{}' | jpx 'md5(`"hello"`)'

sha1

Calculate SHA-1 hash

Signature: string -> string

Examples:

# Simple string
sha1('hello') -> \"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"
# Empty string
sha1('') -> \"da39a3ee5e6b4b0d3255bfef95601890afd80709\"
# Another string
sha1('test') -> \"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\"

CLI Usage:

echo '{}' | jpx 'sha1(`"hello"`)'

sha256

Calculate SHA-256 hash

Signature: string -> string

Examples:

# Simple string
sha256('hello') -> \"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824\"
# Empty string
sha256('') -> \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"
# Another string
sha256('test') -> \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\"

CLI Usage:

echo '{}' | jpx 'sha256(`"hello"`)'

sha512

Calculate SHA-512 hash

Signature: string -> string

Examples:

# Simple string
sha512('hello') -> \"9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043\"
# Another string
sha512('test') -> \"ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff\"

CLI Usage:

echo '{}' | jpx 'sha512(`"hello"`)'

Encoding Functions

Encoding and decoding functions: Base64, hex, URL encoding, and more.

Summary

FunctionSignatureDescription
base64_decodestring -> stringDecode base64 string
base64_encodestring -> stringEncode string to base64
hex_decodestring -> stringDecode hex string
hex_encodestring -> stringEncode string to hex
html_escapestring -> stringEscape HTML special characters
html_unescapestring -> stringUnescape HTML entities
jwt_decodestring -> objectDecode JWT payload (claims) without verification
jwt_headerstring -> objectDecode JWT header without verification

Functions

base64_decode

Decode base64 string

Signature: string -> string

Examples:

# Decode hello
base64_decode('aGVsbG8=') -> \"hello\"
# Decode test
base64_decode('dGVzdA==') -> \"test\"
# Empty string
base64_decode('') -> \"\"

CLI Usage:

echo '{}' | jpx 'base64_decode(`"aGVsbG8="`)'

base64_encode

Encode string to base64

Signature: string -> string

Examples:

# Encode hello
base64_encode('hello') -> \"aGVsbG8=\"
# Encode test
base64_encode('test') -> \"dGVzdA==\"
# Empty string
base64_encode('') -> \"\"

CLI Usage:

echo '{}' | jpx 'base64_encode(`"hello"`)'

hex_decode

Decode hex string

Signature: string -> string

Examples:

# Decode hello
hex_decode('68656c6c6f') -> \"hello\"
# Decode test
hex_decode('74657374') -> \"test\"
# Empty string
hex_decode('') -> \"\"

CLI Usage:

echo '{}' | jpx 'hex_decode(`"68656c6c6f"`)'

hex_encode

Encode string to hex

Signature: string -> string

Examples:

# Encode hello
hex_encode('hello') -> \"68656c6c6f\"
# Encode test
hex_encode('test') -> \"74657374\"
# Empty string
hex_encode('') -> \"\"

CLI Usage:

echo '{}' | jpx 'hex_encode(`"hello"`)'

html_escape

Escape HTML special characters

Signature: string -> string

Examples:

# Escape tags
html_escape('<div>') -> \"&lt;div&gt;\"
# Escape ampersand
html_escape('a & b') -> \"a &amp; b\"
# Escape quotes
html_escape('\"quoted\"') -> \"&quot;quoted&quot;\"
# Sanitize input
html_escape(user_input) -> safe for HTML

CLI Usage:

echo '{}' | jpx 'html_escape(`"<div>"`)'

html_unescape

Unescape HTML entities

Signature: string -> string

Examples:

# Unescape tags
html_unescape('&lt;div&gt;') -> \"<div>\"
# Unescape ampersand
html_unescape('a &amp; b') -> \"a & b\"
# Unescape quotes
html_unescape('&quot;quoted&quot;') -> \"\\\"quoted\\\"\"
# Decode entities
html_unescape(escaped) -> original

CLI Usage:

echo '{}' | jpx 'html_unescape(`"&lt;div&gt;"`)'

jwt_decode

Decode JWT payload (claims) without verification

Signature: string -> object

Examples:

# Extract subject claim
jwt_decode('eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyJ9.sig').sub -> \"user_123\"
# Extract name claim
jwt_decode('eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiJ9.sig').name -> 'John'

CLI Usage:

echo '{}' | jpx 'jwt_decode(`"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyJ9.sig"`).sub'

jwt_header

Decode JWT header without verification

Signature: string -> object

Examples:

# Extract algorithm
jwt_header('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig').alg -> \"HS256\"
# Extract type
jwt_header('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig').typ -> \"JWT\"

CLI Usage:

echo '{}' | jpx 'jwt_header(`"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig"`).alg'

Validation Functions

Functions for validating data: email, URL, UUID, and format validation.

Summary

FunctionSignatureDescription
is_base64string -> booleanCheck if valid Base64 encoding
is_credit_cardstring -> booleanValidate credit card number (Luhn check + length)
is_emailstring -> booleanValidate email address format
is_hexstring -> booleanCheck if valid hexadecimal string
is_ipv4string -> booleanValidate IPv4 address format
is_ipv6string -> booleanValidate IPv6 address format
is_iso_datestring -> booleanValidate ISO 8601 date format
is_jsonstring -> booleanCheck if string is valid JSON
is_jwtstring -> booleanCheck if valid JWT structure (3 base64url parts)
is_phonestring -> booleanValidate phone number format
is_urlstring -> booleanValidate URL format
is_uuidstring -> booleanValidate UUID format
luhn_checkstring -> booleanGeneric Luhn algorithm check

Functions

is_base64

Check if valid Base64 encoding

Signature: string -> boolean

Examples:

# Valid base64
is_base64('SGVsbG8=') -> true
# Invalid chars
is_base64('not valid!') -> false
# Empty is valid
is_base64('') -> true

CLI Usage:

echo '{}' | jpx 'is_base64(`"SGVsbG8="`)'

is_credit_card

Validate credit card number (Luhn check + length)

Signature: string -> boolean

Examples:

# Valid Visa test number
is_credit_card('4111111111111111') -> true
# Invalid number
is_credit_card('1234567890123456') -> false
# Valid Mastercard test
is_credit_card('5500000000000004') -> true

CLI Usage:

echo '{}' | jpx 'is_credit_card(`"4111111111111111"`)'

is_email

Validate email address format

Signature: string -> boolean

Examples:

# Valid email
is_email('user@example.com') -> true
# Missing @
is_email('invalid-email') -> false
# Subdomain
is_email('user@sub.domain.com') -> true

CLI Usage:

echo '{}' | jpx 'is_email(`"user@example.com"`)'

is_hex

Check if valid hexadecimal string

Signature: string -> boolean

Examples:

# Valid hex
is_hex('deadbeef') -> true
# Uppercase hex
is_hex('ABCDEF') -> true
# Invalid chars
is_hex('ghijkl') -> false

CLI Usage:

echo '{}' | jpx 'is_hex(`"deadbeef"`)'

is_ipv4

Validate IPv4 address format

Signature: string -> boolean

Examples:

# Valid IPv4
is_ipv4('192.168.1.1') -> true
# Out of range
is_ipv4('256.1.1.1') -> false
# Private IP
is_ipv4('10.0.0.1') -> true

CLI Usage:

echo '{}' | jpx 'is_ipv4(`"192.168.1.1"`)'

is_ipv6

Validate IPv6 address format

Signature: string -> boolean

Examples:

# Loopback
is_ipv6('::1') -> true
# Full IPv6
is_ipv6('2001:db8::1') -> true
# IPv4 is not IPv6
is_ipv6('192.168.1.1') -> false

CLI Usage:

echo '{}' | jpx 'is_ipv6(`"::1"`)'

is_iso_date

Validate ISO 8601 date format

Signature: string -> boolean

Examples:

# Full ISO format
is_iso_date('2023-12-13T15:30:00Z') -> true
# Date only
is_iso_date('2023-12-13') -> true
# US format invalid
is_iso_date('12/13/2023') -> false

CLI Usage:

echo '{}' | jpx 'is_iso_date(`"2023-12-13T15:30:00Z"`)'

is_json

Check if string is valid JSON

Signature: string -> boolean

Examples:

# Valid JSON object
is_json('{\"a\": 1}') -> true
# Valid JSON array
is_json('[1, 2, 3]') -> true
# Invalid JSON
is_json('not json') -> false

CLI Usage:

echo '{}' | jpx 'is_json(`"{\"a\": 1}"`)'

is_jwt

Check if valid JWT structure (3 base64url parts)

Signature: string -> boolean

Examples:

# Valid JWT
is_jwt('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U') -> true
# Three parts with dots
is_jwt('eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiJ9.signature') -> true
# Four parts
is_jwt('not.a.valid.jwt') -> false
# No dots
is_jwt('invalid') -> false

CLI Usage:

echo '{}' | jpx 'is_jwt(`"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"`)'

is_phone

Validate phone number format

Signature: string -> boolean

Examples:

# US format with country code
is_phone('+1-555-123-4567') -> true
# US format without country code
is_phone('555-123-4567') -> true
# UK format
is_phone('+44 20 7946 0958') -> true
# Invalid phone
is_phone('invalid') -> false

CLI Usage:

echo '{}' | jpx 'is_phone(`"+1-555-123-4567"`)'

is_url

Validate URL format

Signature: string -> boolean

Examples:

# Simple HTTPS URL
is_url('https://example.com') -> true
# URL with port and path
is_url('http://localhost:8080/path') -> true
# FTP URL
is_url('ftp://files.example.com') -> true
# Invalid URL
is_url('not a url') -> false

CLI Usage:

echo '{}' | jpx 'is_url(`"https://example.com"`)'

is_uuid

Validate UUID format

Signature: string -> boolean

Examples:

# Valid UUID v4
is_uuid('550e8400-e29b-41d4-a716-446655440000') -> true
# Valid UUID v1
is_uuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8') -> true
# Nil UUID
is_uuid('00000000-0000-0000-0000-000000000000') -> true
# Invalid format
is_uuid('not-a-uuid') -> false

CLI Usage:

echo '{}' | jpx 'is_uuid(`"550e8400-e29b-41d4-a716-446655440000"`)'

luhn_check

Generic Luhn algorithm check

Signature: string -> boolean

Examples:

# Valid Luhn number
luhn_check('79927398713') -> true
# Valid credit card number
luhn_check('4532015112830366') -> true
# Invalid Luhn
luhn_check('1234567890') -> false
# Single zero
luhn_check('0') -> true

CLI Usage:

echo '{}' | jpx 'luhn_check(`"79927398713"`)'

Expression Functions

Higher-order functions that work with JMESPath expressions as arguments.

Summary

FunctionSignatureDescription
all_exprarray, expression -> booleanReturn true if every element satisfies the expression (short-circuits on false)
any_exprarray, expression -> booleanReturn true if any element satisfies the expression (short-circuits)
applyobject|string, ...any -> anyApply a partial function or invoke a function by name with arguments
count_bystring, array -> objectCount occurrences grouped by expression result
count_exprarray, expression -> numberCount how many elements satisfy the expression
drop_whilestring, array -> arrayDrop elements from array while expression is truthy
everystring, array -> booleanCheck if all elements match (alias for all_expr)
filter_exprarray, expression -> arrayKeep elements where JMESPath expression evaluates to truthy value
find_exprarray, expression -> anyReturn first element where expression is truthy, or null if none match
find_index_exprarray, expression -> number | nullReturn zero-based index of first matching element, or -1 if none match
flat_map_exprarray, expression -> arrayApply expression to each element and flatten all results into one array
group_by_exprarray, expression -> objectGroup elements into object keyed by expression result
map_exprarray, expression -> arrayApply a JMESPath expression to each element, returning transformed array
map_keysstring, object -> objectTransform object keys using expression
map_valuesstring, object -> objectTransform object values using expression
max_by_exprarray, expression -> anyReturn element with largest expression result, or null for empty array
min_by_exprarray, expression -> anyReturn element with smallest expression result, or null for empty array
order_byarray, array[[string, string]] -> arraySort array by multiple fields with ascending/descending control
partialstring, ...any -> objectCreate a partial function with some arguments pre-filled
partition_exprarray, expression -> arraySplit array into [matches, non-matches] based on expression
recurseany -> arrayCollect all nested values recursively (jq parity)
recurse_withany, expression -> arrayRecursive descent with expression filter (jq parity)
reduce_exprstring, array, any -> anyReduce array to single value using accumulator expression
rejectstring, array -> arrayKeep elements where expression is falsy (inverse of filter_expr)
scan_exprstring, array, any -> arrayLike reduce but returns array of intermediate accumulator values
somestring, array -> booleanCheck if any element matches (alias for any_expr)
sort_by_exprarray, expression -> arraySort array by expression result in ascending order
take_whilestring, array -> arrayTake elements from array while expression is truthy
unique_by_exprarray, expression -> arrayRemove duplicates by expression result, keeping first occurrence
until_exprany, expression, expression -> arrayLoop until condition becomes true, collecting intermediate values (jq parity)
walkstring, any -> anyRecursively apply expression to all components of a value (bottom-up)
while_exprany, expression, expression -> arrayLoop while condition is true, collecting intermediate values (jq parity)
zip_withstring, array, array -> arrayZip two arrays with a custom combiner expression

Functions

all_expr

Return true if every element satisfies the expression (short-circuits on false)

Signature: array, expression -> boolean

Aliases: every

Examples:

# All positive
all_expr([1, 2, 3], &@ > `0`) -> true
# Not all > 2
all_expr([1, 2, 3], &@ > `2`) -> false
# Empty array is true
all_expr([], &@ > `0`) -> true
# Check all adults
all_expr(users, &age >= `18`) -> true/false

CLI Usage:

echo '{}' | jpx 'all_expr([1, 2, 3], &@ > `0`)'

any_expr

Return true if any element satisfies the expression (short-circuits)

Signature: array, expression -> boolean

Aliases: some

Examples:

# At least one > 2
any_expr([1, 2, 3], &@ > `2`) -> true
# None > 5
any_expr([1, 2, 3], &@ > `5`) -> false
# Empty array is false
any_expr([], &@ > `0`) -> false
# Any active user
any_expr(users, &status == 'active') -> true/false

CLI Usage:

echo '{}' | jpx 'any_expr([1, 2, 3], &@ > `2`)'

apply

Apply a partial function or invoke a function by name with arguments

Signature: object|string, ...any -> any

Examples:

# Apply partial function
apply(partial('join', `\"-\"`), `[\"a\", \"b\"]`) -> 'a-b' 
# Call function by name
apply('length', 'hello') -> 5
# Call with multiple args
apply('add', `1`, `2`) -> 3
# Partial with contains
apply(partial('contains', 'hello'), 'ell') -> true

CLI Usage:

echo '{}' | jpx 'apply(partial(`"join"`, `\"-\"`), `[\"a\", \"b\"]`)'

count_by

Count occurrences grouped by expression result

Signature: string, array -> object

Examples:

# Count by field
count_by('type', [{type: 'a'}, {type: 'b'}, {type: 'a'}]) -> {a: 2, b: 1}
# Count orders by status
count_by('status', orders) -> {pending: 5, shipped: 3}
# Count by condition
count_by('@ > `50`', [30, 60, 70, 40]) -> {true: 2, false: 2}
# Empty array
count_by('category', []) -> {}

CLI Usage:

echo '{}' | jpx 'count_by(`"type"`, [{type: 'a'}, {type: 'b'}, {type: 'a'}])'

count_expr

Count how many elements satisfy the expression

Signature: array, expression -> number

Examples:

# Count > 1
count_expr([1, 2, 3], &@ > `1`) -> 2
# None match
count_expr([1, 2, 3], &@ > `5`) -> 0
# Count active users
count_expr(users, &active == `true`) -> 5
# Empty array
count_expr([], &@ > `0`) -> 0

CLI Usage:

echo '{}' | jpx 'count_expr([1, 2, 3], &@ > `1`)'

drop_while

Drop elements from array while expression is truthy

Signature: string, array -> array

Examples:

# Drop while < 4
drop_while('@ < `4`', [1, 2, 3, 5, 1]) -> [5, 1]
# None dropped
drop_while('@ < `0`', [1, 2, 3]) -> [1, 2, 3]
# All dropped
drop_while('@ < `10`', [1, 2, 3]) -> []
# Drop short strings
drop_while('length(@) < `3`', ['a', 'ab', 'abc', 'x']) -> ['abc', 'x']

CLI Usage:

echo '{}' | jpx 'drop_while(`"@ < `4`"`, [1, 2, 3, 5, 1])'

every

Check if all elements match (alias for all_expr)

Signature: string, array -> boolean

Examples:

# All positive
every('@ > `0`', [1, 2, 3]) -> true
# Not all > 2
every('@ > `2`', [1, 2, 3]) -> false
# All non-empty
every('length(@) > `0`', ['a', 'b']) -> true
# Empty is vacuously true
every('@ > `0`', []) -> true

CLI Usage:

echo '{}' | jpx 'every(`"@ > `0`"`, [1, 2, 3])'

filter_expr

Keep elements where JMESPath expression evaluates to truthy value

Signature: array, expression -> array

Aliases: filter

Examples:

# Filter numbers
filter_expr([1, 2, 3], &@ > `1`) -> [2, 3]
# Filter objects by field
filter_expr(users, &age >= `18`) -> [adult users]
# Filter empty strings
filter_expr(['a', '', 'b'], &length(@) > `0`) -> ['a', 'b']
# Empty array returns empty
filter_expr([], &@ > `0`) -> []

CLI Usage:

echo '{}' | jpx 'filter_expr([1, 2, 3], &@ > `1`)'

find_expr

Return first element where expression is truthy, or null if none match

Signature: array, expression -> any

Examples:

# First > 1
find_expr([1, 2, 3], &@ > `1`) -> 2
# None found
find_expr([1, 2, 3], &@ > `5`) -> null
# Find user
find_expr(users, &name == 'alice') -> {name: 'alice', ...}
# Empty array
find_expr([], &@ > `0`) -> null

CLI Usage:

echo '{}' | jpx 'find_expr([1, 2, 3], &@ > `1`)'

find_index_expr

Return zero-based index of first matching element, or -1 if none match

Signature: array, expression -> number | null

Examples:

# Index of first > 1
find_index_expr([1, 2, 3], &@ > `1`) -> 1
# Not found
find_index_expr([1, 2, 3], &@ > `5`) -> -1
# Exact match
find_index_expr([5, 10, 15], &@ == `10`) -> 1
# Empty array
find_index_expr([], &@ > `0`) -> -1

CLI Usage:

echo '{}' | jpx 'find_index_expr([1, 2, 3], &@ > `1`)'

flat_map_expr

Apply expression to each element and flatten all results into one array

Signature: array, expression -> array

Examples:

# Flatten nested arrays
flat_map_expr([[1], [2]], &@) -> [1, 2]
# Duplicate and transform
flat_map_expr([1, 2], &[@, @ * `2`]) -> [1, 2, 2, 4]
# Flatten object arrays
flat_map_expr(users, &tags) -> all tags flattened
# Empty array
flat_map_expr([], &@) -> []

CLI Usage:

echo '{}' | jpx 'flat_map_expr([[1], [2]], &@)'

group_by_expr

Group elements into object keyed by expression result

Signature: array, expression -> object

Examples:

# Group by field
group_by_expr([{t: 'a'}, {t: 'b'}], &t) -> {a: [...], b: [...]}
# Group by condition
group_by_expr([1, 2, 3, 4], &@ > `2`) -> {true: [3, 4], false: [1, 2]}
# Group users
group_by_expr(users, &department) -> {eng: [...], sales: [...]}
# Empty array
group_by_expr([], &@) -> {}

CLI Usage:

echo '{}' | jpx 'group_by_expr([{t: 'a'}, {t: 'b'}], &t)'

map_expr

Apply a JMESPath expression to each element, returning transformed array

Signature: array, expression -> array

Examples:

# Double each number
map_expr([1, 2], &@ * `2`) -> [2, 4]
# Extract field from objects
map_expr(users, &name) -> ['alice', 'bob']
# Transform strings
map_expr(['hello', 'world'], &upper(@)) -> ['HELLO', 'WORLD']
# Empty array returns empty
map_expr([], &@ * `2`) -> []

CLI Usage:

echo '{}' | jpx 'map_expr([1, 2], &@ * `2`)'

map_keys

Transform object keys using expression

Signature: string, object -> object

Examples:

# Uppercase keys
map_keys('upper(@)', {a: 1}) -> {A: 1}
# Lowercase keys
map_keys('lower(@)', {A: 1, B: 2}) -> {a: 1, b: 2}
# Add suffix
map_keys('concat(@, `"_suffix"`)', {x: 1}) -> {x_suffix: 1}
# Empty object
map_keys('upper(@)', {}) -> {}

CLI Usage:

echo '{}' | jpx 'map_keys(`"upper(@)"`, {a: 1})'

map_values

Transform object values using expression

Signature: string, object -> object

Examples:

# Double values
map_values('@ * `2`', {a: 1, b: 2}) -> {a: 2, b: 4}
# Uppercase strings
map_values('upper(@)', {a: 'x', b: 'y'}) -> {a: 'X', b: 'Y'}
# Add to values
map_values('@ + `10`', {x: 5}) -> {x: 15}
# Empty object
map_values('@ * `2`', {}) -> {}

CLI Usage:

echo '{}' | jpx 'map_values(`"@ * `2`"`, {a: 1, b: 2})'

max_by_expr

Return element with largest expression result, or null for empty array

Signature: array, expression -> any

Examples:

# Max by field
max_by_expr([{a: 2}, {a: 1}], &a) -> {a: 2}
# Longest string
max_by_expr(['a', 'abc', 'ab'], &length(@)) -> 'abc'
# Max price product
max_by_expr(products, &price) -> most expensive
# Empty array
max_by_expr([], &a) -> null

CLI Usage:

echo '{}' | jpx 'max_by_expr([{a: 2}, {a: 1}], &a)'

min_by_expr

Return element with smallest expression result, or null for empty array

Signature: array, expression -> any

Examples:

# Min by field
min_by_expr([{a: 2}, {a: 1}], &a) -> {a: 1}
# Shortest string
min_by_expr(['a', 'abc', 'ab'], &length(@)) -> 'a'
# Min price product
min_by_expr(products, &price) -> cheapest
# Empty array
min_by_expr([], &a) -> null

CLI Usage:

echo '{}' | jpx 'min_by_expr([{a: 2}, {a: 1}], &a)'

order_by

Sort array by multiple fields with ascending/descending control

Signature: array, array[[string, string]] -> array

Examples:

# Multi-field sort
order_by(items, [['name', 'asc'], ['price', 'desc']]) -> sorted
# Single field ascending
order_by(users, [['age', 'asc']]) -> youngest first
# Descending sort
order_by(products, [['price', 'desc']]) -> most expensive first
# Empty array
order_by([], [['a', 'asc']]) -> []

CLI Usage:

echo '{}' | jpx 'order_by(items, [['name', 'asc'], ['price', 'desc']])'

partial

Create a partial function with some arguments pre-filled

Signature: string, ...any -> object

Examples:

# Partial contains
partial('contains', `\"hello\"`) -> {__partial__: true, ...}
# Partial addition
partial('add', `10`) -> partial add 10
# Partial join
partial('join', '-') -> partial join with dash
# Partial multiply
partial('multiply', `2`) -> partial multiply by 2

CLI Usage:

echo '{}' | jpx 'partial(`"contains"`, `\"hello\"`)'

partition_expr

Split array into [matches, non-matches] based on expression

Signature: array, expression -> array

Examples:

# Split by condition
partition_expr([1, 2, 3], &@ > `1`) -> [[2, 3], [1]]
# Even vs odd
partition_expr([1, 2, 3, 4], &@ % `2` == `0`) -> [[2, 4], [1, 3]]
# Partition users
partition_expr(users, &active) -> [active, inactive]
# Empty array
partition_expr([], &@ > `0`) -> [[], []]

CLI Usage:

echo '{}' | jpx 'partition_expr([1, 2, 3], &@ > `1`)'

recurse

Collect all nested values recursively (jq parity)

Signature: any -> array

Examples:

# Nested object
recurse({a: {b: 1}}) -> [{a: {b: 1}}, {b: 1}, 1]
# Nested array
recurse([1, [2, 3]]) -> [[1, [2, 3]], 1, [2, 3], 2, 3]
# Scalar value
recurse(`5`) -> [5]
# All descendants
recurse({a: 1, b: {c: 2}}) -> all nested values

CLI Usage:

echo '{}' | jpx 'recurse({a: {b: 1}})'

recurse_with

Recursive descent with expression filter (jq parity)

Signature: any, expression -> array

Examples:

# Follow 'a' key recursively
recurse_with({a: {a: 1}}, &a) -> [{a: 1}, 1]
# Follow index recursively
recurse_with([1, [2, [3]]], &[1]) -> [[2, [3]], [3]]
# Tree traversal
recurse_with({children: [{children: []}]}, &children) -> nested children

CLI Usage:

echo '{}' | jpx 'recurse_with({a: {a: 1}}, &a)'

reduce_expr

Reduce array to single value using accumulator expression

Signature: string, array, any -> any

Aliases: fold

Examples:

# Sum numbers
reduce_expr('add(accumulator, current)', [1, 2, 3], `0`) -> 6
# Product
reduce_expr('multiply(accumulator, current)', [2, 3, 4], `1`) -> 24
# Find max
reduce_expr('max(accumulator, current)', [3, 1, 4], `0`) -> 4
# Concat strings
reduce_expr('concat(accumulator, current)', ['a', 'b'], '') -> 'ab'

CLI Usage:

echo '{}' | jpx 'reduce_expr(`"add(accumulator, current)"`, [1, 2, 3], `0`)'

reject

Keep elements where expression is falsy (inverse of filter_expr)

Signature: string, array -> array

Examples:

# Reject > 2
reject('@ > `2`', [1, 2, 3, 4]) -> [1, 2]
# Reject negatives
reject('@ < `0`', [1, -1, 2, -2]) -> [1, 2]
# Reject active users
reject('active', users) -> inactive users
# Empty array
reject('@ > `0`', []) -> []

CLI Usage:

echo '{}' | jpx 'reject(`"@ > `2`"`, [1, 2, 3, 4])'

scan_expr

Like reduce but returns array of intermediate accumulator values

Signature: string, array, any -> array

Examples:

# Running sum
scan_expr('add(accumulator, current)', [1, 2, 3], `0`) -> [1, 3, 6]
# Running product
scan_expr('multiply(accumulator, current)', [2, 3, 4], `1`) -> [2, 6, 24]
# Running max
scan_expr('max(accumulator, current)', [3, 1, 4], `0`) -> [3, 3, 4]
# Empty array
scan_expr('add(accumulator, current)', [], `0`) -> []

CLI Usage:

echo '{}' | jpx 'scan_expr(`"add(accumulator, current)"`, [1, 2, 3], `0`)'

some

Check if any element matches (alias for any_expr)

Signature: string, array -> boolean

Examples:

# Some > 2
some('@ > `2`', [1, 2, 3]) -> true
# None > 5
some('@ > `5`', [1, 2, 3]) -> false
# Any active user
some('active', users) -> true/false
# Empty is false
some('@ > `0`', []) -> false

CLI Usage:

echo '{}' | jpx 'some(`"@ > `2`"`, [1, 2, 3])'

sort_by_expr

Sort array by expression result in ascending order

Signature: array, expression -> array

Examples:

# Sort by field
sort_by_expr([{a: 2}, {a: 1}], &a) -> [{a: 1}, {a: 2}]
# Sort by length
sort_by_expr(['bb', 'a', 'ccc'], &length(@)) -> ['a', 'bb', 'ccc']
# Sort users by name
sort_by_expr(users, &name) -> alphabetical
# Empty array
sort_by_expr([], &a) -> []

CLI Usage:

echo '{}' | jpx 'sort_by_expr([{a: 2}, {a: 1}], &a)'

take_while

Take elements from array while expression is truthy

Signature: string, array -> array

Examples:

# Take while < 4
take_while('@ < `4`', [1, 2, 3, 5, 1]) -> [1, 2, 3]
# Take while positive
take_while('@ > `0`', [3, 2, 1, 0, 5]) -> [3, 2, 1]
# Take short strings
take_while('length(@) < `3`', ['a', 'ab', 'abc']) -> ['a', 'ab']
# None taken
take_while('@ < `0`', [1, 2, 3]) -> []

CLI Usage:

echo '{}' | jpx 'take_while(`"@ < `4`"`, [1, 2, 3, 5, 1])'

unique_by_expr

Remove duplicates by expression result, keeping first occurrence

Signature: array, expression -> array

Examples:

# Unique by field
unique_by_expr([{a: 1}, {a: 1}], &a) -> [{a: 1}]
# First wins
unique_by_expr([{id: 1, v: 'a'}, {id: 1, v: 'b'}], &id) -> [{id: 1, v: 'a'}]
# Unique by length
unique_by_expr(['aa', 'b', 'cc'], &length(@)) -> ['aa', 'b']
# Empty array
unique_by_expr([], &a) -> []

CLI Usage:

echo '{}' | jpx 'unique_by_expr([{a: 1}, {a: 1}], &a)'

until_expr

Loop until condition becomes true, collecting intermediate values (jq parity)

Signature: any, expression, expression -> array

Examples:

# Count until >= 5
until_expr(`1`, &@ >= `5`, &add(@, `1`)) -> [1, 2, 3, 4, 5]
# Double until >= 100
until_expr(`2`, &@ >= `100`, &multiply(@, `2`)) -> [2, 4, 8, 16, 32, 64, 128]
# Halve until <= 1
until_expr(`100`, &@ <= `1`, &divide(@, `2`)) -> [100, 50, 25, 12, 6, 3, 1]

CLI Usage:

echo '{}' | jpx 'until_expr(`1`, &@ >= `5`, &add(@, `1`))'

walk

Recursively apply expression to all components of a value (bottom-up)

Signature: string, any -> any

Examples:

# Identity transform
walk('@', data) -> data unchanged
# Get type of result
walk('type(@)', {a: 1}) -> 'object'
# Lengths of nested arrays
walk('length(@)', [[], []]) -> 2
# Keys at each level
walk('keys(@)', {a: {b: 1}}) -> ['a']

CLI Usage:

echo '{}' | jpx 'walk(`"@"`, data)'

while_expr

Loop while condition is true, collecting intermediate values (jq parity)

Signature: any, expression, expression -> array

Examples:

# Count from 1 while < 5
while_expr(`1`, &@ < `5`, &add(@, `1`)) -> [1, 2, 3, 4]
# Double while < 100
while_expr(`2`, &@ < `100`, &multiply(@, `2`)) -> [2, 4, 8, 16, 32, 64]
# Halve while > 1
while_expr(`10`, &@ > `1`, &divide(@, `2`)) -> [10, 5, 2]

CLI Usage:

echo '{}' | jpx 'while_expr(`1`, &@ < `5`, &add(@, `1`))'

zip_with

Zip two arrays with a custom combiner expression

Signature: string, array, array -> array

Examples:

# Add pairs
zip_with('add([0], [1])', [1, 2], [10, 20]) -> [11, 22]
# Multiply pairs
zip_with('multiply([0], [1])', [2, 3], [4, 5]) -> [8, 15]
# Concat pairs
zip_with('concat([0], [1])', ['a', 'b'], ['1', '2']) -> ['a1', 'b2']
# Empty arrays
zip_with('add([0], [1])', [], []) -> []

CLI Usage:

echo '{}' | jpx 'zip_with(`"add([0], [1])"`, [1, 2], [10, 20])'

Type Functions

Type conversion and checking functions.

Summary

FunctionSignatureDescription
auto_parseany -> anyIntelligently parse strings to numbers, booleans, and nulls
is_arrayany -> booleanCheck if value is an array
is_booleanany -> booleanCheck if value is a boolean
is_emptyany -> booleanCheck if value is empty
is_nullany -> booleanCheck if value is null
is_numberany -> booleanCheck if value is a number
is_objectany -> booleanCheck if value is an object
is_stringany -> booleanCheck if value is a string
parse_booleansany -> anyRecursively convert boolean strings to booleans
parse_nullsany -> anyRecursively convert null-like strings to null
parse_numbersany -> anyRecursively convert numeric strings to numbers
to_booleanany -> booleanConvert value to boolean
type_ofany -> stringGet the type of a value

Functions

auto_parse

Intelligently parse strings to numbers, booleans, and nulls

Signature: any -> any

Examples:

# Parse mixed types
auto_parse({num: \"42\", bool: \"true\", nil: \"null\"}) -> {num: 42, bool: true, nil: null}
# Parse array
auto_parse([\"42\", \"true\", \"hello\"]) -> [42, true, \"hello\"]
# Nested parsing
auto_parse({a: {b: \"123\"}}) -> {a: {b: 123}}

CLI Usage:

echo '{}' | jpx 'auto_parse({num: \"42\", bool: \"true\", nil: \"null\"})'

is_array

Check if value is an array

Signature: any -> boolean

Examples:

# Array returns true
is_array([1, 2]) -> true
# String returns false
is_array('hello') -> false
# Object returns false
is_array({}) -> false
# Empty array is array
is_array([]) -> true

CLI Usage:

echo '{}' | jpx 'is_array([1, 2])'

is_boolean

Check if value is a boolean

Signature: any -> boolean

Examples:

# True is boolean
is_boolean(`true`) -> true
# False is boolean
is_boolean(`false`) -> true
# String is not boolean
is_boolean('true') -> false
# Number is not boolean
is_boolean(`1`) -> false

CLI Usage:

echo '{}' | jpx 'is_boolean(`true`)'

is_empty

Check if value is empty

Signature: any -> boolean

Examples:

# Empty array
is_empty([]) -> true
# Empty string
is_empty('') -> true
# Empty object
is_empty({}) -> true
# Non-empty array
is_empty([1, 2]) -> false

CLI Usage:

echo '{}' | jpx 'is_empty([])'

is_null

Check if value is null

Signature: any -> boolean

Examples:

# Null returns true
is_null(`null`) -> true
# Empty string is not null
is_null('') -> false
# Zero is not null
is_null(`0`) -> false
# Missing field is null
is_null(missing_field) -> true

CLI Usage:

echo '{}' | jpx 'is_null(`null`)'

is_number

Check if value is a number

Signature: any -> boolean

Examples:

# Integer is number
is_number(`42`) -> true
# Float is number
is_number(`3.14`) -> true
# String is not number
is_number('42') -> false
# Null is not number
is_number(`null`) -> false

CLI Usage:

echo '{}' | jpx 'is_number(`42`)'

is_object

Check if value is an object

Signature: any -> boolean

Examples:

# Object returns true
is_object({a: 1}) -> true
# Empty object is object
is_object({}) -> true
# Array is not object
is_object([]) -> false
# String is not object
is_object('hello') -> false

CLI Usage:

echo '{}' | jpx 'is_object({a: 1})'

is_string

Check if value is a string

Signature: any -> boolean

Examples:

# String returns true
is_string('hello') -> true
# Empty string is string
is_string('') -> true
# Number is not string
is_string(`42`) -> false
# Array is not string
is_string([]) -> false

CLI Usage:

echo '{}' | jpx 'is_string(`"hello"`)'

parse_booleans

Recursively convert boolean strings to booleans

Signature: any -> any

Examples:

# Parse true
parse_booleans({active: "true"}) -> {active: true}
# Parse yes
parse_booleans({flag: "YES"}) -> {flag: true}
# Parse 1
parse_booleans({enabled: "1"}) -> {enabled: true}
# Multiple booleans
parse_booleans({off: "false", on: "true"}) -> {off: false, on: true}

CLI Usage:

echo '{}' | jpx 'parse_booleans({active: "true"})'

parse_nulls

Recursively convert null-like strings to null

Signature: any -> any

Examples:

# Parse null
parse_nulls({a: "null"}) -> {a: null}
# Parse None
parse_nulls({a: "None"}) -> {a: null}
# Parse nil
parse_nulls({a: "nil"}) -> {a: null}
# Parse undefined
parse_nulls({a: "undefined"}) -> {a: null}

CLI Usage:

echo '{}' | jpx 'parse_nulls({a: "null"})'

parse_numbers

Recursively convert numeric strings to numbers

Signature: any -> any

Examples:

# Parse integer
parse_numbers({count: "42"}) -> {count: 42}
# Parse float
parse_numbers({price: "19.99"}) -> {price: 19.99}
# Nested parsing
parse_numbers({a: {b: "123"}}) -> {a: {b: 123}}
# Non-numeric stays string
parse_numbers({val: "42abc"}) -> {val: "42abc"}

CLI Usage:

echo '{}' | jpx 'parse_numbers({count: "42"})'

to_boolean

Convert value to boolean

Signature: any -> boolean

Examples:

# String 'true'
to_boolean('true') -> true
# String 'false'
to_boolean('false') -> false
# Non-zero is true
to_boolean(`1`) -> true
# Zero is false
to_boolean(`0`) -> false

CLI Usage:

echo '{}' | jpx 'to_boolean(`"true"`)'

type_of

Get the type of a value

Signature: any -> string

Examples:

# Number type
type_of(`42`) -> \"number\"
# String type
type_of('hello') -> \"string\"
# Array type
type_of([1, 2]) -> \"array\"
# Object type
type_of({a: 1}) -> \"object\"

CLI Usage:

echo '{}' | jpx 'type_of(`42`)'

Regular Expression Functions

Regular expression functions: matching, replacing, splitting, and pattern extraction.

Summary

FunctionSignatureDescription
regex_extractstring, string -> arrayExtract regex matches
regex_matchstring, string -> booleanTest if string matches regex
regex_replacestring, string, string -> stringReplace regex matches

Functions

regex_extract

Extract regex matches

Signature: string, string -> array

Examples:

# Extract numbers
regex_extract('a1b2', '\\\\d+') -> [\"1\", \"2\"]
# Extract words
regex_extract('hello world', '\\\\w+') -> [\"hello\", \"world\"]
# No matches
regex_extract('abc', '\\d+') -> []

CLI Usage:

echo '{}' | jpx 'regex_extract(`"a1b2"`, `"\\\\d+"`)'

regex_match

Test if string matches regex

Signature: string, string -> boolean

Examples:

# Full match
regex_match('hello', '^h.*o$') -> true
# Contains digits
regex_match('test123', '\\d+') -> true
# No match
regex_match('abc', '^\\d+$') -> false

CLI Usage:

echo '{}' | jpx 'regex_match(`"hello"`, `"^h.*o$"`)'

regex_replace

Replace regex matches

Signature: string, string, string -> string

Examples:

# Replace digits
regex_replace('a1b2', '\\\\d+', 'X') -> \"aXbX\"
# Replace spaces
regex_replace('hello world', '\\\\s+', '-') -> \"hello-world\"
# No match unchanged
regex_replace('abc', 'x', 'y') -> \"abc\"

CLI Usage:

echo '{}' | jpx 'regex_replace(`"a1b2"`, `"\\\\d+"`, `"X"`)'

URL Functions

Functions for parsing and manipulating URLs and their components.

Summary

FunctionSignatureDescription
url_decodestring -> stringURL decode a string
url_encodestring -> stringURL encode a string
url_parsestring -> objectParse URL into components

Functions

url_decode

URL decode a string

Signature: string -> string

Examples:

# Decode space
url_decode('hello%20world') -> \"hello world\"
# Decode plus sign
url_decode('a%2Bb') -> \"a+b\"
# Decode percent
url_decode('100%25') -> \"100%\"
# No encoding
url_decode('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'url_decode(`"hello%20world"`)'

url_encode

URL encode a string

Signature: string -> string

Examples:

# Encode space
url_encode('hello world') -> \"hello%20world\"
# Encode plus
url_encode('a+b') -> \"a%2Bb\"
# Encode percent
url_encode('100%') -> \"100%25\"
# No special chars
url_encode('hello') -> \"hello\"

CLI Usage:

echo '{}' | jpx 'url_encode(`"hello world"`)'

url_parse

Parse URL into components

Signature: string -> object

Examples:

# Parse full URL
url_parse('https://example.com/path') -> {scheme: 'https', ...}
# With auth and port
url_parse('http://user:pass@host:8080') -> components
# With query string
url_parse('https://api.example.com/v1?key=val') -> with query
# Relative URL
url_parse('/path/to/file') -> relative path

CLI Usage:

echo '{}' | jpx 'url_parse(`"https://example.com/path"`)'

Semantic Versioning Functions

Semantic versioning functions: parsing, comparing, and manipulating version strings.

Summary

FunctionSignatureDescription
semver_comparestring, string -> numberCompare versions (-1, 0, 1)
semver_is_validstring -> booleanCheck if string is valid semver
semver_majorstring -> numberGet major version
semver_minorstring -> numberGet minor version
semver_parsestring -> objectParse semantic version
semver_patchstring -> numberGet patch version
semver_satisfiesstring, string -> booleanCheck if version matches constraint

Functions

semver_compare

Compare versions (-1, 0, 1)

Signature: string, string -> number

Examples:

# Less than
semver_compare('1.0.0', '2.0.0') -> -1
# Greater than
semver_compare('2.0.0', '1.0.0') -> 1
# Equal
semver_compare('1.0.0', '1.0.0') -> 0

CLI Usage:

echo '{}' | jpx 'semver_compare(`"1.0.0"`, `"2.0.0"`)'

semver_is_valid

Check if string is valid semver

Signature: string -> boolean

Examples:

# Valid semver
semver_is_valid('1.2.3') -> true
# With prerelease
semver_is_valid('1.2.3-alpha') -> true
# Invalid format
semver_is_valid('invalid') -> false

CLI Usage:

echo '{}' | jpx 'semver_is_valid(`"1.2.3"`)'

semver_major

Get major version

Signature: string -> number

Examples:

# Major version
semver_major('1.2.3') -> 1
# Double digit
semver_major('10.0.0') -> 10
# Zero major
semver_major('0.1.0') -> 0

CLI Usage:

echo '{}' | jpx 'semver_major(`"1.2.3"`)'

semver_minor

Get minor version

Signature: string -> number

Examples:

# Minor version
semver_minor('1.2.3') -> 2
# Double digit
semver_minor('1.10.0') -> 10
# Zero minor
semver_minor('1.0.0') -> 0

CLI Usage:

echo '{}' | jpx 'semver_minor(`"1.2.3"`)'

semver_parse

Parse semantic version

Signature: string -> object

Examples:

# Basic version
semver_parse('1.2.3') -> {major: 1, minor: 2, patch: 3}
# With prerelease
semver_parse('1.2.3-alpha').pre -> 'alpha'

CLI Usage:

echo '{}' | jpx 'semver_parse(`"1.2.3"`)'

semver_patch

Get patch version

Signature: string -> number

Examples:

# Patch version
semver_patch('1.2.3') -> 3
# Double digit
semver_patch('1.2.10') -> 10
# Zero patch
semver_patch('1.2.0') -> 0

CLI Usage:

echo '{}' | jpx 'semver_patch(`"1.2.3"`)'

semver_satisfies

Check if version matches constraint

Signature: string, string -> boolean

Examples:

# Caret range
semver_satisfies('1.2.3', '^1.0.0') -> true
# Outside range
semver_satisfies('2.0.0', '^1.0.0') -> false
# Comparison
semver_satisfies('1.5.0', '>=1.0.0') -> true

CLI Usage:

echo '{}' | jpx 'semver_satisfies(`"1.2.3"`, `"^1.0.0"`)'

JSON Patch Functions

JSON Patch (RFC 6902) functions: applying patches, generating diffs, and path operations.

Summary

FunctionSignatureDescription
json_diffobject, object -> arrayGenerate JSON Patch (RFC 6902) that transforms first object into second
json_merge_patchobject, object -> objectApply JSON Merge Patch (RFC 7396) to an object
json_patchobject, array -> objectApply JSON Patch (RFC 6902) operations to an object

Functions

json_diff

Generate JSON Patch (RFC 6902) that transforms first object into second

Signature: object, object -> array

Examples:

# Replace value
json_diff({a: 1}, {a: 2}) -> [{op: 'replace', path: '/a', value: 2}]
# Add field
json_diff({a: 1}, {a: 1, b: 2}) -> [{op: 'add', path: '/b', value: 2}]
# Remove field
json_diff({a: 1, b: 2}, {a: 1}) -> [{op: 'remove', path: '/b'}]

CLI Usage:

echo '{}' | jpx 'json_diff({a: 1}, {a: 2})'

json_merge_patch

Apply JSON Merge Patch (RFC 7396) to an object

Signature: object, object -> object

Examples:

# Merge objects
json_merge_patch({a: 1, b: 2}, {b: 3, c: 4}) -> {a: 1, b: 3, c: 4}
# Null removes field
json_merge_patch({a: 1}, {a: `null`}) -> {}
# Add to empty
json_merge_patch({}, {a: 1}) -> {a: 1}

CLI Usage:

echo '{}' | jpx 'json_merge_patch({a: 1, b: 2}, {b: 3, c: 4})'

json_patch

Apply JSON Patch (RFC 6902) operations to an object

Signature: object, array -> object

Examples:

# Add operation
json_patch({a: 1}, [{op: 'add', path: '/b', value: 2}]) -> {a: 1, b: 2}
# Replace operation
json_patch({a: 1}, [{op: 'replace', path: '/a', value: 2}]) -> {a: 2}
# Remove operation
json_patch({a: 1, b: 2}, [{op: 'remove', path: '/b'}]) -> {a: 1}

CLI Usage:

echo '{}' | jpx 'json_patch({a: 1}, [{op: 'add', path: '/b', value: 2}])'

Multi-Match Functions

Functions for matching multiple patterns or expressions in a single operation.

Summary

FunctionSignatureDescription
extract_allstring, array[string] -> array[object]Extract all pattern matches with positions (Aho-Corasick)
extract_betweenstring, string, string -> string|nullExtract text between two delimiters
match_allstring, array[string] -> booleanCheck if string contains all of the patterns (Aho-Corasick)
match_anystring, array[string] -> booleanCheck if string contains any of the patterns (Aho-Corasick)
match_countstring, array[string] -> numberCount total pattern matches in string (Aho-Corasick)
match_positionsstring, array[string] -> array[object]Get start/end positions of all pattern matches (Aho-Corasick)
match_whichstring, array[string] -> array[string]Return array of patterns that match the string (Aho-Corasick)
replace_manystring, object -> stringReplace multiple patterns simultaneously (Aho-Corasick)
split_keepstring, string -> array[string]Split string keeping delimiters in result
tokenizestring, object? -> array[string]Smart word tokenization with optional lowercase and min_length

Functions

extract_all

Extract all pattern matches with positions (Aho-Corasick)

Signature: string, array[string] -> array[object]

Examples:

# Multiple patterns
extract_all('error warning', ['error', 'warning']) -> [{pattern: 'error', match: 'error', start: 0, end: 5}, ...]
# Overlapping matches
extract_all('abab', ['a', 'b']) -> [{pattern: 'a', match: 'a', start: 0, end: 1}, ...]

CLI Usage:

echo '{}' | jpx 'extract_all(`"error warning"`, ['error', 'warning'])'

extract_between

Extract text between two delimiters

Signature: string, string, string -> string|null

Examples:

# HTML tag content
extract_between('<title>Page</title>', '<title>', '</title>') -> \"Page\"
# Bracketed content
extract_between('Hello [world]!', '[', ']') -> \"world\"
# No delimiters found
extract_between('no match', '[', ']') -> null

CLI Usage:

echo '{}' | jpx 'extract_between(`"<title>Page</title>"`, `"<title>"`, `"</title>"`)'

match_all

Check if string contains all of the patterns (Aho-Corasick)

Signature: string, array[string] -> boolean

Examples:

# All patterns found
match_all('hello world', ['hello', 'world']) -> true
# Missing pattern
match_all('hello world', ['hello', 'foo']) -> false
# Empty patterns
match_all('abc', []) -> true

CLI Usage:

echo '{}' | jpx 'match_all(`"hello world"`, ['hello', 'world'])'

match_any

Check if string contains any of the patterns (Aho-Corasick)

Signature: string, array[string] -> boolean

Examples:

# One pattern found
match_any('hello world', ['world', 'foo']) -> true
# No patterns found
match_any('hello world', ['foo', 'bar']) -> false
# Empty patterns
match_any('abc', []) -> false

CLI Usage:

echo '{}' | jpx 'match_any(`"hello world"`, ['world', 'foo'])'

match_count

Count total pattern matches in string (Aho-Corasick)

Signature: string, array[string] -> number

Examples:

# Count all matches
match_count('abcabc', ['a', 'b']) -> 4
# Repeated pattern
match_count('hello', ['l']) -> 2
# No matches
match_count('abc', ['x']) -> 0

CLI Usage:

echo '{}' | jpx 'match_count(`"abcabc"`, ['a', 'b'])'

match_positions

Get start/end positions of all pattern matches (Aho-Corasick)

Signature: string, array[string] -> array[object]

Examples:

# Find positions
match_positions('The quick fox', ['quick', 'fox']) -> [{pattern: 'quick', start: 4, end: 9}, ...]
# Multiple occurrences
match_positions('abab', ['ab']) -> [{pattern: 'ab', start: 0, end: 2}, {pattern: 'ab', start: 2, end: 4}]

CLI Usage:

echo '{}' | jpx 'match_positions(`"The quick fox"`, ['quick', 'fox'])'

match_which

Return array of patterns that match the string (Aho-Corasick)

Signature: string, array[string] -> array[string]

Examples:

# Find matching patterns
match_which('hello world', ['hello', 'foo', 'world']) -> [\"hello\", \"world\"]
# Partial matches
match_which('abc', ['a', 'b', 'x']) -> [\"a\", \"b\"]
# No matches
match_which('abc', ['x', 'y']) -> []

CLI Usage:

echo '{}' | jpx 'match_which(`"hello world"`, ['hello', `"foo"`, 'world'])'

replace_many

Replace multiple patterns simultaneously (Aho-Corasick)

Signature: string, object -> string

Examples:

# Multiple replacements
replace_many('hello world', {hello: 'hi', world: 'earth'}) -> \"hi earth\"
# Repeated pattern
replace_many('aaa', {a: 'b'}) -> \"bbb\"
# No matches
replace_many('abc', {x: 'y'}) -> \"abc\"

CLI Usage:

echo '{}' | jpx 'replace_many(`"hello world"`, {hello: 'hi', world: 'earth'})'

split_keep

Split string keeping delimiters in result

Signature: string, string -> array[string]

Examples:

# Keep dashes
split_keep('a-b-c', '-') -> [\"a\", \"-\", \"b\", \"-\", \"c\"]
# Keep spaces
split_keep('hello world', ' ') -> [\"hello\", \" \", \"world\"]
# No delimiter
split_keep('abc', '-') -> [\"abc\"]

CLI Usage:

echo '{}' | jpx 'split_keep(`"a-b-c"`, `"-"`)'

tokenize

Smart word tokenization with optional lowercase and min_length

Signature: string, object? -> array[string]

Examples:

# Basic tokenization
tokenize('Hello, World!') -> [\"Hello\", \"World\"]
# Lowercase option
tokenize('Hello, World!', {lowercase: `true`}) -> [\"hello\", \"world\"]
# Minimum length
tokenize('a bb ccc', {min_length: `2`}) -> [\"bb\", \"ccc\"]

CLI Usage:

echo '{}' | jpx 'tokenize(`"Hello, World!"`)'

UUID Functions

Functions for generating and working with UUIDs.

Summary

FunctionSignatureDescription
uuid-> stringGenerate a UUID v4

Functions

uuid

Generate a UUID v4

Signature: -> string

Examples:

# Generate UUID
uuid() -> \"550e8400-e29b-41d4-a716-446655440000\"
# Each call is unique
uuid() -> random unique ID
# Add ID to object
{id: uuid(), name: 'item'} -> with UUID

CLI Usage:

echo '{}' | jpx 'uuid()'

ID Generation Functions

Functions for generating various types of unique identifiers.

Summary

FunctionSignatureDescription
nanoidnumber? -> stringGenerate nanoid
ulid-> stringGenerate ULID
ulid_timestampstring -> numberExtract timestamp from ULID

Functions

nanoid

Generate nanoid

Signature: number? -> string

Examples:

# Default 21 chars
nanoid() -> \"V1StGXR8_Z5jdHi6B-myT\"
# Custom length
nanoid(`10`) -> \"IRFa-VaY2b\"

CLI Usage:

echo '{}' | jpx 'nanoid()'

ulid

Generate ULID

Signature: -> string

Examples:

# Generate ULID
ulid() -> \"01ARZ3NDEKTSV4RRFFQ69G5FAV\"
# Always 26 chars
length(ulid()) -> 26

CLI Usage:

echo '{}' | jpx 'ulid()'

ulid_timestamp

Extract timestamp from ULID

Signature: string -> number

Examples:

# Extract timestamp
ulid_timestamp('01ARZ3NDEKTSV4RRFFQ69G5FAV') -> 1469918176385
# Another ULID
ulid_timestamp('01BX5ZZKBKACTAV9WEVGEMMVRY') -> 1484581420610

CLI Usage:

echo '{}' | jpx 'ulid_timestamp(`"01ARZ3NDEKTSV4RRFFQ69G5FAV"`)'

Random Functions

Functions for generating random values: numbers, strings, and selections.

Summary

FunctionSignatureDescription
random-> numberGenerate random number between 0 and 1
samplearray, number -> arrayRandom sample from array
shufflearray -> arrayRandomly shuffle array

Functions

random

Generate random number between 0 and 1

Signature: -> number

Examples:

# Random float
random() -> 0.123456...
# Random 0-99
floor(multiply(random(), `100`)) -> 42

CLI Usage:

echo '{}' | jpx 'random()'

sample

Random sample from array

Signature: array, number -> array

Examples:

# Sample 2 items
sample([1, 2, 3, 4], `2`) -> [3, 1]
# Sample 1 item
sample(['a', 'b', 'c'], `1`) -> ['b']
# Always returns n items
length(sample([1, 2, 3], `2`)) -> 2

CLI Usage:

echo '{}' | jpx 'sample([1, 2, 3, 4], `2`)'

shuffle

Randomly shuffle array

Signature: array -> array

Examples:

# Shuffle numbers
shuffle([1, 2, 3]) -> [2, 3, 1]
# Preserves length
length(shuffle([1, 2, 3])) -> 3

CLI Usage:

echo '{}' | jpx 'shuffle([1, 2, 3])'

Text Functions

Text analysis and processing functions for NLP pipelines.

Summary

FunctionSignatureDescription
bigramsstring -> arrayGenerate word bigrams (2-grams)
char_countstring -> numberCount characters in text
char_frequenciesstring -> objectCount character frequencies
collapse_whitespacestring -> stringNormalize whitespace
is_stopwordstring, string? -> booleanCheck if word is a stopword
ngramsstring, number, string? -> arrayGenerate n-grams from text (word or character)
normalize_unicodestring, string? -> stringUnicode normalization (NFC/NFD/NFKC/NFKD)
paragraph_countstring -> numberCount paragraphs in text
reading_timestring -> stringEstimate reading time
reading_time_secondsstring -> numberEstimate reading time in seconds
remove_accentsstring -> stringStrip diacritics from text
remove_stopwordsarray, string? -> arrayFilter stopwords from token array
sentence_countstring -> numberCount sentences in text
stemstring, string? -> stringStem a word (17 languages)
stemsarray, string? -> arrayStem array of tokens
stopwordsstring? -> arrayGet stopword list for language
tokenizestring, object? -> arrayConfigurable tokenization
tokensstring -> arraySimple word tokenization
trigramsstring -> arrayGenerate word trigrams (3-grams)
word_countstring -> numberCount words in text
word_frequenciesstring -> objectCount word frequencies

Functions

bigrams

Generate word bigrams (2-grams)

Signature: string -> array

Examples:

# Basic bigrams
bigrams('a b c') -> [['a', 'b'], ['b', 'c']]
# Sentence bigrams
bigrams('the quick brown fox') -> [['the', 'quick'], ['quick', 'brown'], ['brown', 'fox']]
# Single word
bigrams('single') -> []

CLI Usage:

echo '{}' | jpx "bigrams('a b c')"

char_count

Count characters in text

Signature: string -> number

Examples:

# Simple word
char_count('hello') -> 5
# With space
char_count('hello world') -> 11
# Empty string
char_count('') -> 0

CLI Usage:

echo '{}' | jpx "char_count('hello')"

char_frequencies

Count character frequencies

Signature: string -> object

Examples:

# Count repeated chars
char_frequencies('aab') -> {a: 2, b: 1}
# Word frequencies
char_frequencies('hello') -> {e: 1, h: 1, l: 2, o: 1}
# Empty string
char_frequencies('') -> {}

CLI Usage:

echo '{}' | jpx "char_frequencies('aab')"

collapse_whitespace

Normalize whitespace by collapsing multiple spaces, tabs, and newlines into single spaces.

Signature: string -> string

Examples:

# Multiple spaces
collapse_whitespace('hello    world') -> 'hello world'
# Tabs and newlines
collapse_whitespace('a\t\nb') -> 'a b'
# Leading/trailing whitespace
collapse_whitespace('  hello  ') -> 'hello'

CLI Usage:

echo '"hello    world"' | jpx 'collapse_whitespace(@)'

is_stopword

Check if a word is a stopword in the specified language.

Signature: string, string? -> boolean

Parameters:

  • word - The word to check
  • lang - Language code (default: “en”). Supports 30+ languages.

Examples:

# English stopword
is_stopword('the') -> true
# Not a stopword
is_stopword('algorithm') -> false
# Spanish
is_stopword('el', 'es') -> true
# German
is_stopword('und', 'de') -> true

CLI Usage:

echo '{}' | jpx "is_stopword('the')"

ngrams

Generate n-grams from text (word or character)

Signature: string, number, string? -> array

Examples:

# Character trigrams
ngrams('hello', `3`, 'char') -> ['hel', 'ell', 'llo']
# Word bigrams
ngrams('a b c d', `2`, 'word') -> [['a', 'b'], ['b', 'c'], ['c', 'd']]
# Text shorter than n
ngrams('ab', `3`, 'char') -> []

CLI Usage:

echo '{}' | jpx "ngrams('hello', \`3\`, 'char')"

normalize_unicode

Apply Unicode normalization to text.

Signature: string, string? -> string

Parameters:

  • text - The text to normalize
  • form - Normalization form: “nfc” (default), “nfd”, “nfkc”, or “nfkd”

Examples:

# NFC (composed, default)
normalize_unicode('café') -> 'café'
# NFD (decomposed)
normalize_unicode('é', 'nfd') -> 'é'  # e + combining acute
# NFKC (compatibility composed)
normalize_unicode('fi', 'nfkc') -> 'fi'

CLI Usage:

echo '"café"' | jpx "normalize_unicode(@)"

paragraph_count

Count paragraphs in text

Signature: string -> number

Examples:

# Two paragraphs
paragraph_count('A\n\nB') -> 2
# Single paragraph
paragraph_count('Single paragraph') -> 1
# Three paragraphs
paragraph_count('A\n\nB\n\nC') -> 3

CLI Usage:

echo '{}' | jpx "paragraph_count('A\n\nB')"

reading_time

Estimate reading time

Signature: string -> string

Examples:

# Short text
reading_time('The quick brown fox') -> "1 min read"
# Empty text minimum
reading_time('') -> "1 min read"

CLI Usage:

echo '{}' | jpx "reading_time('The quick brown fox')"

reading_time_seconds

Estimate reading time in seconds

Signature: string -> number

Examples:

# Short sentence
reading_time_seconds('The quick brown fox jumps over the lazy dog') -> 2
# Empty text
reading_time_seconds('') -> 0

CLI Usage:

echo '{}' | jpx "reading_time_seconds('The quick brown fox jumps over the lazy dog')"

remove_accents

Strip diacritical marks (accents) from text.

Signature: string -> string

Examples:

# French
remove_accents('café') -> 'cafe'
# Spanish
remove_accents('señor') -> 'senor'
# German
remove_accents('über') -> 'uber'
# Mixed
remove_accents('naïve résumé') -> 'naive resume'

CLI Usage:

echo '"café"' | jpx 'remove_accents(@)'

remove_stopwords

Filter stopwords from an array of tokens.

Signature: array, string? -> array

Parameters:

  • tokens - Array of word tokens
  • lang - Language code (default: “en”)

Examples:

# English
remove_stopwords(['the', 'quick', 'brown', 'fox']) -> ['quick', 'brown', 'fox']
# Complete sentence
remove_stopwords(['i', 'am', 'learning', 'rust']) -> ['learning', 'rust']
# Spanish
remove_stopwords(['el', 'gato', 'negro'], 'es') -> ['gato', 'negro']

CLI Usage:

echo '["the", "quick", "brown", "fox"]' | jpx "remove_stopwords(@)"

sentence_count

Count sentences in text

Signature: string -> number

Examples:

# Two sentences
sentence_count('Hello. World!') -> 2
# Single sentence
sentence_count('One sentence') -> 1
# Different terminators
sentence_count('What? Yes! No.') -> 3

CLI Usage:

echo '{}' | jpx "sentence_count('Hello. World!')"

stem

Stem a single word using the Snowball stemmer.

Signature: string, string? -> string

Parameters:

  • word - The word to stem
  • lang - Language code (default: “en”). Supports 17 languages: ar, da, de, el, en, es, fi, fr, hu, it, nl, no, pt, ro, ru, sv, tr.

Examples:

# English
stem('running') -> 'run'
stem('connections') -> 'connect'
stem('happiness') -> 'happi'
# Spanish
stem('corriendo', 'es') -> 'corr'
# German
stem('laufend', 'de') -> 'lauf'

CLI Usage:

echo '{}' | jpx "stem('running')"

stems

Stem an array of tokens.

Signature: array, string? -> array

Parameters:

  • tokens - Array of word tokens
  • lang - Language code (default: “en”)

Examples:

# Stem multiple words
stems(['running', 'jumps', 'walked']) -> ['run', 'jump', 'walk']
# Spanish
stems(['corriendo', 'saltando'], 'es') -> ['corr', 'salt']

CLI Usage:

echo '["running", "jumps", "walked"]' | jpx "stems(@)"

stopwords

Get the list of stopwords for a language.

Signature: string? -> array

Parameters:

  • lang - Language code (default: “en”). Supports 30+ languages.

Examples:

# English stopwords (first few)
stopwords() | slice(@, `0`, `5`) -> ['i', 'me', 'my', 'myself', 'we']
# Check count
stopwords() | length(@) -> 179
# Spanish
stopwords('es') | length(@) -> 313
# German
stopwords('de') | slice(@, `0`, `3`) -> ['aber', 'alle', 'allem']

CLI Usage:

echo '{}' | jpx "stopwords() | length(@)"

tokenize

Tokenize text with configurable options.

Signature: string, object? -> array

Parameters:

  • text - The text to tokenize
  • options - Optional configuration object:
    • case: “lower” (default) or “preserve”
    • punctuation: “strip” (default) or “keep”

Examples:

# Default (lowercase, strip punctuation)
tokenize('Hello, World!') -> ['hello', 'world']
# Preserve case
tokenize('Hello World', `{"case": "preserve"}`) -> ['Hello', 'World']
# Keep punctuation
tokenize('Hello, World!', `{"punctuation": "keep"}`) -> ['hello,', 'world!']
# Both options
tokenize('Hello!', `{"case": "preserve", "punctuation": "keep"}`) -> ['Hello!']

CLI Usage:

echo '"Hello, World!"' | jpx 'tokenize(@)'

tokens

Simple word tokenization (normalized, lowercase, punctuation stripped).

Signature: string -> array

Examples:

# Basic tokenization
tokens('Hello, World!') -> ['hello', 'world']
# Multiple words
tokens('The quick brown fox') -> ['the', 'quick', 'brown', 'fox']
# Numbers included
tokens('Test 123') -> ['test', '123']

CLI Usage:

echo '"Hello, World!"' | jpx 'tokens(@)'

trigrams

Generate word trigrams (3-grams)

Signature: string -> array

Examples:

# Basic trigrams
trigrams('a b c d') -> [['a', 'b', 'c'], ['b', 'c', 'd']]
# Sentence trigrams
trigrams('the quick brown fox jumps') -> [['the', 'quick', 'brown'], ['quick', 'brown', 'fox'], ['brown', 'fox', 'jumps']]
# Too few words
trigrams('a b') -> []

CLI Usage:

echo '{}' | jpx "trigrams('a b c d')"

word_count

Count words in text

Signature: string -> number

Examples:

# Two words
word_count('hello world') -> 2
# Single word
word_count('one') -> 1
# Empty string
word_count('') -> 0

CLI Usage:

echo '{}' | jpx "word_count('hello world')"

word_frequencies

Count word frequencies

Signature: string -> object

Examples:

# Count repeated words
word_frequencies('a a b') -> {a: 2, b: 1}
# Unique words
word_frequencies('the quick brown fox') -> {brown: 1, fox: 1, quick: 1, the: 1}
# Empty string
word_frequencies('') -> {}

CLI Usage:

echo '{}' | jpx "word_frequencies('a a b')"

Language Functions

Natural language processing functions.

Summary

FunctionSignatureDescription
detect_languagestring -> string | nullDetect the language of text, returning the native language name
detect_language_confidencestring -> number | nullDetect language and return confidence score (0.0-1.0)
detect_language_infostring -> object | nullDetect language and return full detection info object
detect_language_isostring -> string | nullDetect the language of text, returning the ISO 639-3 code
detect_scriptstring -> string | nullDetect the script (writing system) of text

Functions

detect_language

Detect the language of text, returning the native language name

Signature: string -> string | null

Examples:

# Detect English
detect_language('This is English text.') -> \"English\"
# Detect Spanish (native name)
detect_language('Esto es texto en español.') -> \"Español\"
# Detect French (native name)
detect_language('Ceci est du texte français.') -> \"Français\"
# Empty or undetectable returns null
detect_language('') -> null

CLI Usage:

echo '{}' | jpx 'detect_language(`"This is English text."`)'

detect_language_confidence

Detect language and return confidence score (0.0-1.0)

Signature: string -> number | null

Examples:

# High confidence
detect_language_confidence('This is definitely English text.') -> 0.95
# Low confidence for short text
detect_language_confidence('Hi') -> 0.3
# Empty returns null
detect_language_confidence('') -> null

CLI Usage:

echo '{}' | jpx 'detect_language_confidence(`"This is definitely English text."`)'

detect_language_info

Detect language and return full detection info object

Signature: string -> object | null

Examples:

# Full detection info
detect_language_info('This is a test.') -> {language: 'English', code: 'eng', script: 'Latin', confidence: 0.9, reliable: true}
# Access language field
detect_language_info('Bonjour').language -> 'French'
# Check reliability
detect_language_info('Hola').reliable -> true/false
# Empty returns null
detect_language_info('') -> null

CLI Usage:

echo '{}' | jpx 'detect_language_info(`"This is a test."`)'

detect_language_iso

Detect the language of text, returning the ISO 639-3 code

Signature: string -> string | null

Examples:

# English ISO code
detect_language_iso('This is English text.') -> \"eng\"
# Spanish ISO code
detect_language_iso('Esto es texto en español.') -> \"spa\"
# German ISO code
detect_language_iso('Dies ist deutscher Text.') -> \"deu\"
# Empty or undetectable returns null
detect_language_iso('') -> null

CLI Usage:

echo '{}' | jpx 'detect_language_iso(`"This is English text."`)'

detect_script

Detect the script (writing system) of text

Signature: string -> string | null

Examples:

# Latin script
detect_script('Hello world') -> \"Latin\"
# Cyrillic script
detect_script('Привет мир') -> \"Cyrillic\"
# Arabic script
detect_script('مرحبا بالعالم') -> \"Arabic\"
# Japanese Hiragana
detect_script('こんにちは') -> \"Hiragana\"

CLI Usage:

echo '{}' | jpx 'detect_script(`"Hello world"`)'

Fuzzy Functions

Fuzzy matching and string similarity functions.

Summary

FunctionSignatureDescription
damerau_levenshteinstring, string -> numberDamerau-Levenshtein distance
hammingstring, string -> number|nullHamming distance (number of differing positions). Returns null if strings have different lengths
jarostring, string -> numberJaro similarity (0-1)
jaro_winklerstring, string -> numberJaro-Winkler similarity (0-1)
levenshteinstring, string -> numberLevenshtein edit distance
normalized_damerau_levenshteinstring, string -> numberNormalized Damerau-Levenshtein similarity (0-1)
normalized_levenshteinstring, string -> numberNormalized Levenshtein (0-1)
osa_distancestring, string -> numberOptimal String Alignment distance (like Levenshtein but allows adjacent transpositions)
sorensen_dicestring, string -> numberSorensen-Dice coefficient (0-1)

Functions

damerau_levenshtein

Damerau-Levenshtein distance

Signature: string, string -> number

Examples:

# Single transposition
damerau_levenshtein('ab', 'ba') -> 1
# Identical strings
damerau_levenshtein('hello', 'hello') -> 0
# Multiple edits
damerau_levenshtein('ca', 'abc') -> 2

CLI Usage:

echo '{}' | jpx 'damerau_levenshtein(`"ab"`, `"ba"`)'

hamming

Hamming distance (number of differing positions). Returns null if strings have different lengths

Signature: string, string -> number|null

Examples:

# Three differing positions
hamming('karolin', 'kathrin') -> 3
# Identical strings
hamming('hello', 'hello') -> 0
# Different lengths
hamming('hello', 'hi') -> null

CLI Usage:

echo '{}' | jpx 'hamming(`"karolin"`, `"kathrin"`)'

jaro

Jaro similarity (0-1)

Signature: string, string -> number

Examples:

# Similar words
jaro('hello', 'hallo') -> 0.866...
# Identical strings
jaro('hello', 'hello') -> 1.0
# Completely different
jaro('abc', 'xyz') -> 0.0

CLI Usage:

echo '{}' | jpx 'jaro(`"hello"`, `"hallo"`)'

jaro_winkler

Jaro-Winkler similarity (0-1)

Signature: string, string -> number

Examples:

# Similar words
jaro_winkler('hello', 'hallo') -> 0.88
# Identical strings
jaro_winkler('hello', 'hello') -> 1.0
# Completely different
jaro_winkler('abc', 'xyz') -> 0.0

CLI Usage:

echo '{}' | jpx 'jaro_winkler(`"hello"`, `"hallo"`)'

levenshtein

Levenshtein edit distance

Signature: string, string -> number

Examples:

# Classic example
levenshtein('kitten', 'sitting') -> 3
# Identical strings
levenshtein('hello', 'hello') -> 0
# All different
levenshtein('abc', 'def') -> 3

CLI Usage:

echo '{}' | jpx 'levenshtein(`"kitten"`, `"sitting"`)'

normalized_damerau_levenshtein

Normalized Damerau-Levenshtein similarity (0-1)

Signature: string, string -> number

Examples:

# Identical strings
normalized_damerau_levenshtein('hello', 'hello') -> 1.0
# Transposition
normalized_damerau_levenshtein('ab', 'ba') -> 0.5
# Completely different
normalized_damerau_levenshtein('abc', 'xyz') -> 0.0

CLI Usage:

echo '{}' | jpx 'normalized_damerau_levenshtein(`"hello"`, `"hello"`)'

normalized_levenshtein

Normalized Levenshtein (0-1)

Signature: string, string -> number

Examples:

# One edit
normalized_levenshtein('ab', 'abc') -> 0.666...
# Identical
normalized_levenshtein('hello', 'hello') -> 0.0
# All different
normalized_levenshtein('abc', 'xyz') -> 1.0

CLI Usage:

echo '{}' | jpx 'normalized_levenshtein(`"ab"`, `"abc"`)'

osa_distance

Optimal String Alignment distance (like Levenshtein but allows adjacent transpositions)

Signature: string, string -> number

Examples:

# Single transposition
osa_distance('ab', 'ba') -> 1
# Identical strings
osa_distance('hello', 'hello') -> 0
# Multiple edits
osa_distance('ca', 'abc') -> 3

CLI Usage:

echo '{}' | jpx 'osa_distance(`"ab"`, `"ba"`)'

sorensen_dice

Sorensen-Dice coefficient (0-1)

Signature: string, string -> number

Examples:

# Similar words
sorensen_dice('night', 'nacht') -> 0.25
# Identical strings
sorensen_dice('hello', 'hello') -> 1.0
# No common bigrams
sorensen_dice('abc', 'xyz') -> 0.0

CLI Usage:

echo '{}' | jpx 'sorensen_dice(`"night"`, `"nacht"`)'

Discovery Functions

Functions for searching and matching within collections, useful for tool discovery and fuzzy filtering.

Summary

FunctionSignatureDescription
fuzzy_searcharray, string|object, string -> arraySearch array of objects by multiple fields with relevance scoring
fuzzy_matchstring, string -> objectCheck if a string matches a query, with match details
fuzzy_scorestring, string -> numberGet numeric match score between value and query

Functions

Search an array of objects by multiple fields, returning matches sorted by relevance score.

Signature: array, string|object, string -> array

The second argument specifies which fields to search:

  • String: Comma-separated field names with equal weight: "name,description"
  • Object: Field names with custom weights: `{"name": 10, "description": 5}`

Examples:

# Search tools by name and description fields
fuzzy_search(tools, `"name,description"`, `"user"`)
# -> [{item: {...}, score: 100, match_type: "exact", matched_field: "name"}, ...]

# Search with weighted fields (name matches worth more)
fuzzy_search(tools, `{"name": 10, "description": 5}`, `"cache"`)

# Get the name of the top match
fuzzy_search(tools, `"name,tags"`, `"redis"`)[0].item.name

CLI Usage:

# Search a list of tools for "backup"
cat tools.json | jpx 'fuzzy_search(@, `"name,description"`, `"backup"`)'

Return format:

Each result contains:

  • item: The original object that matched
  • score: Relevance score (higher = better match)
  • match_type: How it matched ("exact", "prefix", "contains", "fuzzy")
  • matched_field: Which field produced the match

fuzzy_match

Check if a single string value matches a query, returning detailed match information.

Signature: string, string -> object

Match types (in priority order):

  • exact (score: 1000): Exact match after normalization
  • prefix (score: 800): Query is a prefix of the value
  • contains (score: 600): Query appears within the value
  • fuzzy (score: varies): Similarity-based match
  • none (score: 0): No meaningful match

Examples:

# Exact match
fuzzy_match(`"get_user"`, `"get_user"`)
# -> {matches: true, score: 1000, match_type: "exact"}

# Prefix match
fuzzy_match(`"get_user_info"`, `"get"`)
# -> {matches: true, score: 800, match_type: "prefix"}

# Contains match
fuzzy_match(`"hello_world"`, `"world"`)
# -> {matches: true, score: 600, match_type: "contains"}

# No match (returns similarity score for near-misses)
fuzzy_match(`"hello"`, `"xyz"`)
# -> {matches: false, score: 0, match_type: "none", similarity: 0.0}

CLI Usage:

echo '{}' | jpx 'fuzzy_match(`"database_backup"`, `"backup"`)'

fuzzy_score

Get the numeric match score between a value and query. This is a simpler alternative to fuzzy_match when you only need the score.

Signature: string, string -> number

Score ranges:

  • 1000: Exact match
  • 800: Prefix match
  • 600: Contains match
  • 1-599: Fuzzy similarity match
  • 0: No match

Examples:

# Exact match - highest score
fuzzy_score(`"hello"`, `"hello"`) -> 1000

# Prefix match
fuzzy_score(`"hello_world"`, `"hello"`) -> 800

# Contains match
fuzzy_score(`"say_hello"`, `"hello"`) -> 600

# No match
fuzzy_score(`"hello"`, `"goodbye"`) -> 0

CLI Usage:

# Score a potential match
echo '{}' | jpx 'fuzzy_score(`"enterprise_database_create"`, `"database"`)'

Use with filtering:

# Filter to items with score > 500
tools[?fuzzy_score(name, `"user"`) > `500`]

# Sort by match quality
sort_by(tools, &fuzzy_score(name, `"cache"`)) | reverse(@)

Use Cases

Tool Discovery

Find relevant tools from a large list:

fuzzy_search(mcp_tools, `"name,description"`, `"backup database"`)
  | [?score > `100`]
  | [:5]
  | [*].{name: item.name, score: score, why: match_type}

Autocomplete

Filter options as user types:

commands[?fuzzy_score(name, query) > `0`]
  | sort_by(@, &fuzzy_score(name, query))
  | reverse(@)
  | [:10]

Deduplication

Find similar entries:

items[?fuzzy_match(title, other_title).match_type == `"fuzzy"` 
      && fuzzy_match(title, other_title).score > `400`]

Phonetic Functions

Phonetic encoding functions for sound-based string matching.

Summary

FunctionSignatureDescription
caverphonestring -> stringCaverphone code
caverphone2string -> stringCaverphone 2 code
double_metaphonestring -> objectDouble Metaphone codes
match_rating_codexstring -> stringMatch Rating codex
metaphonestring -> stringMetaphone phonetic code
nysiisstring -> stringNYSIIS phonetic code
phonetic_matchstring, string, string -> booleanCheck phonetic match with algorithm
soundexstring -> stringSoundex phonetic code
sounds_likestring, string -> booleanCheck if strings sound similar

Functions

caverphone

Caverphone code

Signature: string -> string

Examples:

# Common name
caverphone('Smith') -> \"SMT1111111\"
# Another name
caverphone('Thompson') -> \"TMPSN11111\"

CLI Usage:

echo '{}' | jpx 'caverphone(`"Smith"`)'

caverphone2

Caverphone 2 code

Signature: string -> string

Examples:

# Common name
caverphone2('Smith') -> \"SMT1111111\"
# Another name
caverphone2('Thompson') -> \"TMPSN11111\"

CLI Usage:

echo '{}' | jpx 'caverphone2(`"Smith"`)'

double_metaphone

Double Metaphone codes

Signature: string -> object

Examples:

# Common name
double_metaphone('Smith') -> {primary: 'SM0', secondary: 'XMT'}
# German variant
double_metaphone('Schmidt') -> {primary: 'XMT', secondary: 'SMT'}

CLI Usage:

echo '{}' | jpx 'double_metaphone(`"Smith"`)'

match_rating_codex

Match Rating codex

Signature: string -> string

Examples:

# Common name
match_rating_codex('Smith') -> \"SMTH\"
# Another name
match_rating_codex('Johnson') -> \"JHNSN\"

CLI Usage:

echo '{}' | jpx 'match_rating_codex(`"Smith"`)'

metaphone

Metaphone phonetic code

Signature: string -> string

Examples:

# Common name
metaphone('Smith') -> \"SM0\"
# Ph sound
metaphone('phone') -> \"FN\"

CLI Usage:

echo '{}' | jpx 'metaphone(`"Smith"`)'

nysiis

NYSIIS phonetic code

Signature: string -> string

Examples:

# Common name
nysiis('Smith') -> \"SNAT\"
# Another name
nysiis('Johnson') -> \"JANSAN\"

CLI Usage:

echo '{}' | jpx 'nysiis(`"Smith"`)'

phonetic_match

Check phonetic match with algorithm

Signature: string, string, string -> boolean

Examples:

# Soundex match
phonetic_match('Smith', 'Smyth', 'soundex') -> true
# Metaphone match
phonetic_match('Robert', 'Rupert', 'metaphone') -> true
# No match
phonetic_match('John', 'Jane', 'soundex') -> false

CLI Usage:

echo '{}' | jpx 'phonetic_match(`"Smith"`, `"Smyth"`, `"soundex"`)'

soundex

Soundex phonetic code

Signature: string -> string

Examples:

# Common name
soundex('Robert') -> \"R163\"
# Same code as Robert
soundex('Rupert') -> \"R163\"
# Another name
soundex('Smith') -> \"S530\"

CLI Usage:

echo '{}' | jpx 'soundex(`"Robert"`)'

sounds_like

Check if strings sound similar

Signature: string, string -> boolean

Examples:

# Similar sounding
sounds_like('Robert', 'Rupert') -> true
# Spelling variants
sounds_like('Smith', 'Smyth') -> true
# Different names
sounds_like('John', 'Mary') -> false

CLI Usage:

echo '{}' | jpx 'sounds_like(`"Robert"`, `"Rupert"`)'

Network Functions

Network-related functions: IP addresses, CIDR notation, and network utilities.

Summary

FunctionSignatureDescription
cidr_broadcaststring -> stringGet broadcast address from CIDR
cidr_containsstring, string -> booleanCheck if IP is in CIDR range
cidr_networkstring -> stringGet network address from CIDR
cidr_prefixstring -> numberGet prefix length from CIDR
int_to_ipnumber -> stringConvert integer to IP address
ip_to_intstring -> numberConvert IP address to integer
is_private_ipstring -> booleanCheck if IP is in private range

Functions

cidr_broadcast

Get broadcast address from CIDR

Signature: string -> string

Examples:

# /24 network
cidr_broadcast('192.168.1.0/24') -> \"192.168.1.255\"
# /8 network
cidr_broadcast('10.0.0.0/8') -> \"10.255.255.255\"
# /16 network
cidr_broadcast('172.16.0.0/16') -> \"172.16.255.255\"

CLI Usage:

echo '{}' | jpx 'cidr_broadcast(`"192.168.1.0/24"`)'

cidr_contains

Check if IP is in CIDR range

Signature: string, string -> boolean

Examples:

# IP in range
cidr_contains('192.168.0.0/16', '192.168.1.1') -> true
# IP not in range
cidr_contains('10.0.0.0/8', '192.168.1.1') -> false
# Catch-all range
cidr_contains('0.0.0.0/0', '8.8.8.8') -> true

CLI Usage:

echo '{}' | jpx 'cidr_contains(`"192.168.0.0/16"`, `"192.168.1.1"`)'

cidr_network

Get network address from CIDR

Signature: string -> string

Examples:

# /24 network
cidr_network('192.168.1.0/24') -> \"192.168.1.0\"
# /8 network
cidr_network('10.0.0.0/8') -> \"10.0.0.0\"
# Host in network
cidr_network('192.168.1.100/24') -> \"192.168.1.0\"

CLI Usage:

echo '{}' | jpx 'cidr_network(`"192.168.1.0/24"`)'

cidr_prefix

Get prefix length from CIDR

Signature: string -> number

Examples:

# /24 prefix
cidr_prefix('192.168.1.0/24') -> 24
# /8 prefix
cidr_prefix('10.0.0.0/8') -> 8
# Default route
cidr_prefix('0.0.0.0/0') -> 0

CLI Usage:

echo '{}' | jpx 'cidr_prefix(`"192.168.1.0/24"`)'

int_to_ip

Convert integer to IP address

Signature: number -> string

Examples:

# Private IP
int_to_ip(`3232235777`) -> \"192.168.1.1\"
# Zero
int_to_ip(`0`) -> \"0.0.0.0\"
# Max value
int_to_ip(`4294967295`) -> \"255.255.255.255\"

CLI Usage:

echo '{}' | jpx 'int_to_ip(`3232235777`)'

ip_to_int

Convert IP address to integer

Signature: string -> number

Examples:

# Private IP
ip_to_int('192.168.1.1') -> 3232235777
# Zero
ip_to_int('0.0.0.0') -> 0
# Max value
ip_to_int('255.255.255.255') -> 4294967295

CLI Usage:

echo '{}' | jpx 'ip_to_int(`"192.168.1.1"`)'

is_private_ip

Check if IP is in private range

Signature: string -> boolean

Examples:

# Class C private
is_private_ip('192.168.1.1') -> true
# Class A private
is_private_ip('10.0.0.1') -> true
# Public IP
is_private_ip('8.8.8.8') -> false

CLI Usage:

echo '{}' | jpx 'is_private_ip(`"192.168.1.1"`)'

Geolocation Functions

Geolocation functions: distance calculation, coordinate parsing, and geographic utilities.

Summary

FunctionSignatureDescription
geo_bearingnumber, number, number, number -> numberBearing between coordinates
geo_distancenumber, number, number, number -> numberHaversine distance in meters
geo_distance_kmnumber, number, number, number -> numberHaversine distance in kilometers
geo_distance_milesnumber, number, number, number -> numberHaversine distance in miles

Functions

geo_bearing

Bearing between coordinates

Signature: number, number, number, number -> number

Examples:

# NYC to London
geo_bearing(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`) -> 51.2
# Due east
geo_bearing(`0`, `0`, `0`, `90`) -> 90.0
# Due north
geo_bearing(`0`, `0`, `90`, `0`) -> 0.0

CLI Usage:

echo '{}' | jpx 'geo_bearing(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`)'

geo_distance

Haversine distance in meters

Signature: number, number, number, number -> number

Examples:

# NYC to London
geo_distance(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`) -> 5570222
# LA to SF
geo_distance(`34.0522`, `-118.2437`, `37.7749`, `-122.4194`) -> 559044
# Same point
geo_distance(`0`, `0`, `0`, `0`) -> 0

CLI Usage:

echo '{}' | jpx 'geo_distance(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`)'

geo_distance_km

Haversine distance in kilometers

Signature: number, number, number, number -> number

Examples:

# NYC to London
geo_distance_km(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`) -> 5570.2
# LA to SF
geo_distance_km(`34.0522`, `-118.2437`, `37.7749`, `-122.4194`) -> 559.0
# Paris to London
geo_distance_km(`48.8566`, `2.3522`, `51.5074`, `-0.1278`) -> 343.5

CLI Usage:

echo '{}' | jpx 'geo_distance_km(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`)'

geo_distance_miles

Haversine distance in miles

Signature: number, number, number, number -> number

Examples:

# NYC to London
geo_distance_miles(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`) -> 3461.0
# LA to SF
geo_distance_miles(`34.0522`, `-118.2437`, `37.7749`, `-122.4194`) -> 347.4
# Paris to London
geo_distance_miles(`48.8566`, `2.3522`, `51.5074`, `-0.1278`) -> 213.4

CLI Usage:

echo '{}' | jpx 'geo_distance_miles(`40.7128`, `-74.0060`, `51.5074`, `-0.1278`)'

Path Functions

File path manipulation functions.

Summary

FunctionSignatureDescription
path_basenamestring -> stringGet filename from path
path_dirnamestring -> stringGet directory from path
path_extstring -> stringGet file extension
path_joinstring... -> stringJoin path segments

Functions

path_basename

Get filename from path

Signature: string -> string

Examples:

# Unix path
path_basename('/foo/bar.txt') -> \"bar.txt\"
# Directory path
path_basename('/foo/bar/') -> \"bar\"
# Just filename
path_basename('file.txt') -> \"file.txt\"

CLI Usage:

echo '{}' | jpx 'path_basename(`"/foo/bar.txt"`)'

path_dirname

Get directory from path

Signature: string -> string

Examples:

# Unix path
path_dirname('/foo/bar.txt') -> \"/foo\"
# Nested path
path_dirname('/foo/bar/baz') -> \"/foo/bar\"
# No directory
path_dirname('file.txt') -> \"\"

CLI Usage:

echo '{}' | jpx 'path_dirname(`"/foo/bar.txt"`)'

path_ext

Get file extension

Signature: string -> string

Examples:

# Text file
path_ext('/foo/bar.txt') -> \"txt\"
# Image file
path_ext('image.png') -> \"png\"
# No extension
path_ext('noext') -> \"\"

CLI Usage:

echo '{}' | jpx 'path_ext(`"/foo/bar.txt"`)'

path_join

Join path segments

Signature: string... -> string

Examples:

# Multiple segments
path_join('/foo', 'bar', 'baz') -> \"/foo/bar/baz\"
# Full path
path_join('/home', 'user', 'file.txt') -> \"/home/user/file.txt\"
# Relative path
path_join('a', 'b') -> \"a/b\"

CLI Usage:

echo '{}' | jpx 'path_join(`"/foo"`, `"bar"`, `"baz"`)'

Format Functions

Data formatting functions for numbers, currencies, and other values.

Summary

FunctionSignatureDescription
from_csvstring -> arrayParse CSV string into array of arrays (jq parity)
from_tsvstring -> arrayParse TSV string into array of arrays (jq parity)
to_csvarray -> stringConvert array to CSV row string (RFC 4180 compliant)
to_csv_rowsarray -> stringConvert array of arrays to multi-line CSV string
to_csv_tablearray, array? -> stringConvert array of objects to CSV with header row
to_tsvarray -> stringConvert array to TSV row string (tab-separated)

Functions

from_csv

Parse CSV string into array of arrays (jq parity)

Signature: string -> array

Examples:

# Basic CSV parsing
from_csv("a,b,c\\n1,2,3") -> [["a", "b", "c"], ["1", "2", "3"]]
# Quoted fields with commas
from_csv("\"hello, world\",test") -> [["hello, world", "test"]]
# Empty string returns empty array
from_csv("") -> []

CLI Usage:

echo '{}' | jpx 'from_csv("a,b,c\\n1,2,3")'

from_tsv

Parse TSV string into array of arrays (jq parity)

Signature: string -> array

Examples:

# Basic TSV parsing
from_tsv("a\\tb\\tc\\n1\\t2\\t3") -> [["a", "b", "c"], ["1", "2", "3"]]
# Spaces preserved in fields
from_tsv("hello world\\ttest") -> [["hello world", "test"]]
# Empty string returns empty array
from_tsv("") -> []

CLI Usage:

echo '{}' | jpx 'from_tsv("a\\tb\\tc\\n1\\t2\\t3")'

to_csv

Convert array to CSV row string (RFC 4180 compliant)

Signature: array -> string

Examples:

# Simple strings
to_csv(['a', 'b', 'c']) -> "a,b,c"
# Mixed types
to_csv(['hello', `42`, `true`, `null`]) -> "hello,42,true,"
# Value with comma is quoted
to_csv(['hello, world', 'test']) -> "\"hello, world\",test"
# Quotes are doubled
to_csv(['say "hello"', 'test']) -> "\"say \"\"hello\"\"\",test"

CLI Usage:

echo '{}' | jpx 'to_csv(['a', `"b"`, 'c'])'

to_csv_rows

Convert array of arrays to multi-line CSV string

Signature: array -> string

Examples:

# Numeric rows
to_csv_rows([[`1`, `2`, `3`], [`4`, `5`, `6`]]) -> "1,2,3\n4,5,6"
# String rows
to_csv_rows([['a', 'b'], ['c', 'd']]) -> "a,b\nc,d"
# Empty array
to_csv_rows([]) -> ""

CLI Usage:

echo '{}' | jpx 'to_csv_rows([[`1`, `2`, `3`], [`4`, `5`, `6`]])'

to_csv_table

Convert array of objects to CSV with header row

Signature: array, array? -> string

Examples:

# Keys sorted alphabetically
to_csv_table([{name: 'alice', age: `30`}]) -> "age,name\n30,alice"
# Explicit column order
to_csv_table([{name: 'alice', age: `30`}], ['name', 'age']) -> "name,age\nalice,30"
# Missing fields become empty
to_csv_table([{a: `1`}, {a: `2`, b: `3`}], ['a', 'b']) -> "a,b\n1,\n2,3"
# Empty array
to_csv_table([]) -> ""

CLI Usage:

echo '{}' | jpx 'to_csv_table([{name: 'alice', age: `30`}])'

to_tsv

Convert array to TSV row string (tab-separated)

Signature: array -> string

Examples:

# Simple strings
to_tsv(['a', 'b', 'c']) -> "a\tb\tc"
# Mixed types
to_tsv(['hello', `42`, `true`]) -> "hello\t42\ttrue"

CLI Usage:

echo '{}' | jpx 'to_tsv(['a', `"b"`, 'c'])'

Color Functions

Color manipulation and conversion functions.

Summary

FunctionSignatureDescription
color_complementstring -> stringGet complementary color
color_grayscalestring -> stringConvert to grayscale
color_invertstring -> stringInvert a color
color_mixstring, string, number -> stringMix two colors
darkenstring, number -> stringDarken a color by percentage
hex_to_rgbstring -> objectConvert hex color to RGB
lightenstring, number -> stringLighten a color by percentage
rgb_to_hexnumber, number, number -> stringConvert RGB to hex color

Functions

color_complement

Get complementary color

Signature: string -> string

Examples:

# Red to cyan
color_complement('#ff0000') -> \"#00ffff\"
# Green to magenta
color_complement('#00ff00') -> \"#ff00ff\"
# Blue to yellow
color_complement('#0000ff') -> \"#ffff00\"

CLI Usage:

echo '{}' | jpx 'color_complement(`"#ff0000"`)'

color_grayscale

Convert to grayscale

Signature: string -> string

Examples:

# Red to gray
color_grayscale('#ff0000') -> \"#4c4c4c\"
# Green to gray
color_grayscale('#00ff00') -> \"#969696\"
# White unchanged
color_grayscale('#ffffff') -> \"#ffffff\"

CLI Usage:

echo '{}' | jpx 'color_grayscale(`"#ff0000"`)'

color_invert

Invert a color

Signature: string -> string

Examples:

# Invert red
color_invert('#ff0000') -> \"#00ffff\"
# Black to white
color_invert('#000000') -> \"#ffffff\"
# White to black
color_invert('#ffffff') -> \"#000000\"

CLI Usage:

echo '{}' | jpx 'color_invert(`"#ff0000"`)'

color_mix

Mix two colors

Signature: string, string, number -> string

Examples:

# Red and blue to purple
color_mix('#ff0000', '#0000ff', `50`) -> \"#800080\"
# Red and green
color_mix('#ff0000', '#00ff00', `50`) -> \"#808000\"
# Black and white to gray
color_mix('#000000', '#ffffff', `50`) -> \"#808080\"

CLI Usage:

echo '{}' | jpx 'color_mix(`"#ff0000"`, `"#0000ff"`, `50`)'

darken

Darken a color by percentage

Signature: string, number -> string

Examples:

# Darken blue by 20%
darken('#3366cc', `20`) -> \"#2952a3\"
# Darken red by 50%
darken('#ff0000', `50`) -> \"#800000\"
# White to black
darken('#ffffff', `100`) -> \"#000000\"

CLI Usage:

echo '{}' | jpx 'darken(`"#3366cc"`, `20`)'

hex_to_rgb

Convert hex color to RGB

Signature: string -> object

Examples:

# Orange
hex_to_rgb('#ff5500') -> {b: 0, g: 85, r: 255}
# Black
hex_to_rgb('#000000') -> {b: 0, g: 0, r: 0}
# White
hex_to_rgb('#ffffff') -> {b: 255, g: 255, r: 255}

CLI Usage:

echo '{}' | jpx 'hex_to_rgb(`"#ff5500"`)'

lighten

Lighten a color by percentage

Signature: string, number -> string

Examples:

# Lighten blue by 20%
lighten('#3366cc', `20`) -> \"#5c85d6\"
# Lighten dark red
lighten('#800000', `50`) -> \"#ff0000\"
# Black to white
lighten('#000000', `100`) -> \"#ffffff\"

CLI Usage:

echo '{}' | jpx 'lighten(`"#3366cc"`, `20`)'

rgb_to_hex

Convert RGB to hex color

Signature: number, number, number -> string

Examples:

# Orange
rgb_to_hex(`255`, `85`, `0`) -> \"#ff5500\"
# Black
rgb_to_hex(`0`, `0`, `0`) -> \"#000000\"
# White
rgb_to_hex(`255`, `255`, `255`) -> \"#ffffff\"

CLI Usage:

echo '{}' | jpx 'rgb_to_hex(`255`, `85`, `0`)'

Computing Functions

Computing-related utility functions.

Summary

FunctionSignatureDescription
bit_andnumber, number -> numberBitwise AND
bit_notnumber -> numberBitwise NOT
bit_ornumber, number -> numberBitwise OR
bit_shift_leftnumber, number -> numberBitwise left shift
bit_shift_rightnumber, number -> numberBitwise right shift
bit_xornumber, number -> numberBitwise XOR
format_bytesnumber -> stringFormat bytes (decimal)
format_bytes_binarynumber -> stringFormat bytes (binary)
parse_bytesstring -> numberParse byte size string

Functions

bit_and

Bitwise AND

Signature: number, number -> number

Examples:

# 1100 AND 1010 = 1000
bit_and(`12`, `10`) -> 8
# Mask lower 4 bits
bit_and(`255`, `15`) -> 15
# 0111 AND 0011 = 0011
bit_and(`7`, `3`) -> 3

CLI Usage:

echo '{}' | jpx 'bit_and(`12`, `10`)'

bit_not

Bitwise NOT

Signature: number -> number

Examples:

# Invert zero
bit_not(`0`) -> -1
# Invert all ones
bit_not(`-1`) -> 0
# Invert one
bit_not(`1`) -> -2

CLI Usage:

echo '{}' | jpx 'bit_not(`0`)'

bit_or

Bitwise OR

Signature: number, number -> number

Examples:

# 1100 OR 1010 = 1110
bit_or(`12`, `10`) -> 14
# 0001 OR 0010 = 0011
bit_or(`1`, `2`) -> 3
# Zero OR any = any
bit_or(`0`, `255`) -> 255

CLI Usage:

echo '{}' | jpx 'bit_or(`12`, `10`)'

bit_shift_left

Bitwise left shift

Signature: number, number -> number

Examples:

# Shift 1 left by 4
bit_shift_left(`1`, `4`) -> 16
# Shift by 0 unchanged
bit_shift_left(`1`, `0`) -> 1
# Multiply by 4
bit_shift_left(`5`, `2`) -> 20

CLI Usage:

echo '{}' | jpx 'bit_shift_left(`1`, `4`)'

bit_shift_right

Bitwise right shift

Signature: number, number -> number

Examples:

# Divide by 4
bit_shift_right(`16`, `2`) -> 4
# Shift right by 4
bit_shift_right(`255`, `4`) -> 15
# Shift by 0 unchanged
bit_shift_right(`8`, `0`) -> 8

CLI Usage:

echo '{}' | jpx 'bit_shift_right(`16`, `2`)'

bit_xor

Bitwise XOR

Signature: number, number -> number

Examples:

# 1100 XOR 1010 = 0110
bit_xor(`12`, `10`) -> 6
# Same values = 0
bit_xor(`255`, `255`) -> 0
# 0101 XOR 0011 = 0110
bit_xor(`5`, `3`) -> 6

CLI Usage:

echo '{}' | jpx 'bit_xor(`12`, `10`)'

format_bytes

Format bytes (decimal)

Signature: number -> string

Examples:

# Gigabytes
format_bytes(`1500000000`) -> \"1.50 GB\"
# Kilobytes
format_bytes(`1000`) -> \"1.00 KB\"
# Bytes
format_bytes(`500`) -> \"500 B\"

CLI Usage:

echo '{}' | jpx 'format_bytes(`1500000000`)'

format_bytes_binary

Format bytes (binary)

Signature: number -> string

Examples:

# Gibibytes
format_bytes_binary(`1073741824`) -> \"1.00 GiB\"
# Kibibytes
format_bytes_binary(`1024`) -> \"1.00 KiB\"
# Mebibytes
format_bytes_binary(`1048576`) -> \"1.00 MiB\"

CLI Usage:

echo '{}' | jpx 'format_bytes_binary(`1073741824`)'

parse_bytes

Parse byte size string

Signature: string -> number

Examples:

# Gigabytes
parse_bytes('1.5 GB') -> 1500000000
# Kilobytes
parse_bytes('1 KB') -> 1000
# Gibibytes
parse_bytes('1 GiB') -> 1073741824

CLI Usage:

echo '{}' | jpx 'parse_bytes(`"1.5 GB"`)'

Utility Functions

General utility functions that don’t fit other categories.

Summary

FunctionSignatureDescription
coalesceany... -> anyReturn first non-null value
defaultany, any -> anyReturn default value if null
env-> objectGet all environment variables as an object
get_envstring -> string | nullGet a single environment variable by name
ifboolean, any, any -> anyConditional expression
json_decodestring -> anyParse JSON string
json_encodeany -> stringSerialize value to JSON string
json_pointerany, string -> anyAccess value using JSON Pointer (RFC 6901)
now-> numberCurrent Unix timestamp in seconds
now_ms-> numberCurrent Unix timestamp in milliseconds
prettyany, number? -> stringPretty-print value as formatted JSON string

Functions

coalesce

Return first non-null value

Signature: any... -> any

Examples:

# Skip nulls
coalesce(`null`, `null`, 'value') -> \"value\"
# Field fallback
coalesce(field1, field2, 'default') -> first non-null
# First wins
coalesce('first', 'second') -> 'first'
# All null
coalesce(`null`, `null`) -> null

CLI Usage:

echo '{}' | jpx 'coalesce(`null`, `null`, `"value"`)'

default

Return default value if null

Signature: any, any -> any

Examples:

# Null uses default
default(`null`, 'fallback') -> \"fallback\"
# Non-null keeps value
default('value', 'fallback') -> 'value'
# Missing field default
default(missing_field, `0`) -> 0
# Empty string not null
default('', 'fallback') -> ''

CLI Usage:

echo '{}' | jpx 'default(`null`, `"fallback"`)'

env

Get all environment variables as an object

Signature: -> object

Examples:

# All env vars
env() -> {HOME: '/Users/...', PATH: '...', ...}
# Access specific var
env().HOME -> home directory
# List all var names
keys(env()) -> list of var names

CLI Usage:

echo '{}' | jpx 'env()'

get_env

Get a single environment variable by name

Signature: string -> string | null

Examples:

# Get home directory
get_env('HOME') -> \"/Users/josh\"
# Get PATH
get_env('PATH') -> system PATH
# Returns null if not set
get_env('MISSING') -> null
# With default value
default(get_env('PORT'), '8080') -> with fallback

CLI Usage:

echo '{}' | jpx 'get_env(`"HOME"`)'

if

Conditional expression

Signature: boolean, any, any -> any

Examples:

# True branch
if(`true`, 'yes', 'no') -> \"yes\"
# False branch
if(`false`, 'yes', 'no') -> \"no\"
# Conditional logic
if(age >= `18`, 'adult', 'minor') -> depends
# Safe access
if(is_empty(arr), 'none', first(arr)) -> value or none

CLI Usage:

echo '{}' | jpx 'if(`true`, `"yes"`, `"no"`)'

json_decode

Parse JSON string

Signature: string -> any

Examples:

# Parse object
json_decode('{\"a\": 1}') -> {a: 1}
# Parse array
json_decode('[1, 2, 3]') -> [1, 2, 3]
# Parse string
json_decode('\"hello\"') -> \"hello\"
# Parse field
json_decode(json_field) -> parsed

CLI Usage:

echo '{}' | jpx 'json_decode(`"{\"a\": 1}"`)'

json_encode

Serialize value to JSON string

Signature: any -> string

Examples:

# Encode object
json_encode({a: 1}) -> \"{\\\"a\\\":1}\"
# Encode array
json_encode([1, 2, 3]) -> \"[1,2,3]\"
# Encode string
json_encode('hello') -> \"\\\"hello\\\"\"
# Encode any value
json_encode(data) -> JSON string

CLI Usage:

echo '{}' | jpx 'json_encode({a: 1})'

json_pointer

Access value using JSON Pointer (RFC 6901)

Signature: any, string -> any

Examples:

# Nested access
json_pointer({foo: {bar: 1}}, '/foo/bar') -> 1
# Array access
json_pointer(data, '/0/name') -> first item name
# Top-level access
json_pointer({a: 1}, '/a') -> 1
# Missing path
json_pointer(data, '/missing') -> null

CLI Usage:

echo '{}' | jpx 'json_pointer({foo: {bar: 1}}, `"/foo/bar"`)'

now

Current Unix timestamp in seconds

Signature: -> number

Examples:

# Current timestamp
now() -> 1699900000
# Subtract seconds
now() - 3600 -> one hour ago
# Add one day
now() + 86400 -> tomorrow
# Convert to string
from_epoch(now()) -> current ISO

CLI Usage:

echo '{}' | jpx 'now()'

now_ms

Current Unix timestamp in milliseconds

Signature: -> number

Examples:

# Current time in ms
now_ms() -> 1699900000000
# Calculate duration
now_ms() - start_ms -> elapsed ms
# Convert to seconds
now_ms() / `1000` -> seconds
# Convert to string
from_epoch_ms(now_ms()) -> current ISO

CLI Usage:

echo '{}' | jpx 'now_ms()'

pretty

Pretty-print value as formatted JSON string

Signature: any, number? -> string

Examples:

# Default 2-space indent
pretty({a: 1}) -> \"{\n  \\\"a\\\": 1\n}\"
# Custom indent
pretty({a: 1}, `4`) -> 4-space indent
# Format for display
pretty(config) -> readable JSON
# Embed in output
sprintf('Config:\n%s', pretty(@)) -> embedded

CLI Usage:

echo '{}' | jpx 'pretty({a: 1})'

jmespath-extensions

The core Rust library providing 400+ extended JMESPath functions.

Installation

Add to your Cargo.toml:

[dependencies]
jmespath-extensions = "0.8"

Quick Start

use jmespath_extensions::search;
use serde_json::json;

fn main() {
    let data = json!({"name": "hello world"});
    let result = search("upper(name)", &data).unwrap();
    assert_eq!(result, json!("HELLO WORLD"));
}

Features

The library uses feature flags to control which function categories are included:

  • full - All functions (default)
  • core - String, array, object, math, type, utility (no external deps)
  • Individual categories: string, array, hash, datetime, regex, etc.
# Minimal build with just core functions
jmespath-extensions = { version = "0.8", default-features = false, features = ["core"] }

# Just what you need
jmespath-extensions = { version = "0.8", default-features = false, features = ["string", "datetime"] }

API Documentation

Full API documentation is available on docs.rs:

jmespath-extensions on docs.rs

The rustdoc includes:

  • All public functions with examples
  • Function signatures and parameter types
  • Category organization
  • Feature flag requirements for each function

jpx-engine

Protocol-agnostic engine for JMESPath evaluation, function discovery, and JSON utilities. This is the core logic used by the MCP server and can be embedded in other applications.

Installation

Add to your Cargo.toml:

[dependencies]
jpx-engine = "0.1"

Quick Start

use jpx_engine::JpxEngine;
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = JpxEngine::new();
    
    // Evaluate expressions with extended functions
    let data = json!({"items": [1, 2, 3, 4, 5]});
    let result = engine.evaluate("sum(items)", &data)?;
    println!("{}", result); // 15
    
    // Discover available functions
    let functions = engine.functions(Some("String"))?;
    println!("String functions: {}", functions.len());
    
    // Search functions by keyword
    let results = engine.search_functions("date")?;
    for r in results {
        println!("{}: {}", r.name, r.description);
    }
    
    Ok(())
}

Features

Evaluation

  • evaluate() - Evaluate JMESPath expression against JSON
  • evaluate_str() - Evaluate with JSON string input
  • batch_evaluate() - Multiple expressions against same input
  • validate() - Check expression syntax without executing

Function Discovery

  • functions() - List all functions, optionally by category
  • categories() - List all function categories
  • describe_function() - Get detailed function info
  • search_functions() - Fuzzy search by keyword
  • similar_functions() - Find related functions

JSON Utilities

  • format() - Pretty-print JSON
  • diff() - Generate JSON Patch (RFC 6902)
  • patch() - Apply JSON Patch
  • merge() - Apply JSON Merge Patch (RFC 7396)
  • stats() - Analyze JSON structure
  • paths() - Extract all paths in dot notation
  • keys() - Extract object keys

Query Store

  • define_query() - Store named queries
  • run_query() - Execute stored queries
  • list_queries() - List all stored queries

Strict Mode

#![allow(unused)]
fn main() {
let engine = JpxEngine::strict();
// Only standard JMESPath functions available
}

API Documentation

Full API documentation is available on docs.rs:

jpx-engine on docs.rs

Thread Safety

JpxEngine is Send + Sync and can be safely shared across threads. The query store uses interior mutability with RwLock for concurrent access.

Installation

From PyPI

pip install jmespath-extensions

From Source

# Clone the repository
git clone https://github.com/joshrotenberg/jmespath-extensions
cd jmespath-extensions/crates/jmespath-extensions-py

# Install maturin if needed
pip install maturin

# Build and install
maturin develop --release

Verify Installation

import jmespath_extensions

# Check it works
result = jmespath_extensions.search("length(@)", [1, 2, 3])
print(result)  # 3

# List available functions
functions = jmespath_extensions.list_functions()
print(f"Available functions: {len(functions)}")

Requirements

  • Python 3.8+
  • No runtime dependencies (Rust extensions are statically linked)

Usage

Basic Searching

The primary function is search(), which evaluates a JMESPath expression against data:

import jmespath_extensions as jpx

data = {
    "users": [
        {"name": "alice", "age": 30, "active": True},
        {"name": "bob", "age": 25, "active": False},
        {"name": "carol", "age": 35, "active": True}
    ]
}

# Extract active users
active = jpx.search("users[?active].name", data)
print(active)  # ['alice', 'carol']

# Use extended functions
result = jpx.search("users | sort_by(@, &age) | [*].name", data)
print(result)  # ['bob', 'alice', 'carol']

Compiled Expressions

For repeated use, compile the expression once:

import jmespath_extensions as jpx

# Compile once
expr = jpx.compile_expr("users[?age > `30`].name")

# Use many times
for dataset in datasets:
    result = expr.search(dataset)
    print(result)

Exploring Functions

List All Functions

import jmespath_extensions as jpx

# All functions
all_funcs = jpx.list_functions()
print(f"Total: {len(all_funcs)} functions")

# Filter by category
string_funcs = jpx.list_functions("string")
print(f"String functions: {string_funcs}")

List Categories

categories = jpx.list_categories()
print(categories)
# ['standard', 'string', 'array', 'object', 'math', 'datetime', ...]

Get Function Details

info = jpx.describe("split")
print(info)
# {
#   'name': 'split',
#   'category': 'String',
#   'signature': 'string, string -> array',
#   'description': 'Split string by delimiter...',
#   'example': 'split(`"a,b,c"`, `","`) -> ["a", "b", "c"]'
# }

Query Libraries

Parse and use .jpx query library files:

import jmespath_extensions as jpx

# Parse a query library
content = '''
-- :name active-users
-- :desc Get active users
users[?active]

-- :name user-count
length(users)
'''

library = jpx.parse_query_library(content)

# List queries
for query in library.list():
    print(f"{query.name}: {query.description}")

# Get a specific query
query = library.get("active-users")
if query:
    result = jpx.search(query.expression, data)
    
# Get all query names
names = library.names()

Check if Content is a Query Library

# Detect format
if jpx.is_query_library(content):
    library = jpx.parse_query_library(content)
else:
    # Plain expression
    result = jpx.search(content.strip(), data)

Extended Functions Examples

String Functions

# Split and join
jpx.search('split(`"a,b,c"`, `","`)', {})  # ['a', 'b', 'c']
jpx.search('join(`"-"`, `["a","b","c"]`)', {})  # 'a-b-c'

# Case conversion
jpx.search('upper(`"hello"`)', {})  # 'HELLO'
jpx.search('snake_case(`"helloWorld"`)', {})  # 'hello_world'

Date/Time Functions

# Current time
jpx.search('now()', {})  # 1699900000 (Unix timestamp)
jpx.search('now_iso()', {})  # '2024-01-15T10:30:00Z'

# Parse and format dates
jpx.search('parse_datetime(`"2024-01-15"`, `"%Y-%m-%d"`)', {})

Math Functions

data = {"values": [1, 2, 3, 4, 5]}

jpx.search('mean(values)', data)  # 3.0
jpx.search('std_dev(values)', data)  # 1.414...
jpx.search('percentile(values, `50`)', data)  # 3

Fuzzy Matching

tools = [
    {"name": "create_user", "description": "Create a new user"},
    {"name": "delete_user", "description": "Delete an existing user"},
    {"name": "list_users", "description": "List all users"}
]

# Fuzzy search
results = jpx.search(
    'fuzzy_search(@, `"name,description"`, `"user create"`)',
    tools
)

Error Handling

import jmespath_extensions as jpx

try:
    # Invalid expression
    jpx.search("invalid[", {})
except ValueError as e:
    print(f"Parse error: {e}")

try:
    # Runtime error
    jpx.search("length(@)", "not an array")
except Exception as e:
    print(f"Evaluation error: {e}")

API Reference

Functions

search

def search(expression: str, data: Any) -> Any

Evaluate a JMESPath expression against data.

Parameters:

  • expression: JMESPath expression string
  • data: Python data (dict, list, str, int, float, bool, None)

Returns: The result of the expression evaluation

Raises: ValueError for parse errors, Exception for runtime errors

Example:

result = jpx.search("users[0].name", {"users": [{"name": "alice"}]})
# Returns: "alice"

compile_expr

def compile_expr(expression: str) -> CompiledExpression

Compile a JMESPath expression for repeated use.

Parameters:

  • expression: JMESPath expression string

Returns: CompiledExpression object

Raises: ValueError for parse errors

Example:

expr = jpx.compile_expr("length(@)")
result = expr.search([1, 2, 3])  # 3

list_functions

def list_functions(category: Optional[str] = None) -> List[str]

List available function names.

Parameters:

  • category: Optional category filter (e.g., “string”, “math”, “datetime”)

Returns: List of function names

Example:

all_funcs = jpx.list_functions()
string_funcs = jpx.list_functions("string")

list_categories

def list_categories() -> List[str]

List all function categories.

Returns: List of category names

Example:

categories = jpx.list_categories()
# ['standard', 'string', 'array', 'object', 'math', ...]

describe

def describe(name: str) -> Optional[Dict]

Get detailed information about a function.

Parameters:

  • name: Function name or alias

Returns: Dictionary with function info, or None if not found

Return fields:

  • name: Function name
  • category: Category name
  • signature: Type signature
  • description: Description text
  • example: Example usage

Example:

info = jpx.describe("split")
print(info["signature"])  # "string, string -> array"

parse_query_library

def parse_query_library(content: str) -> QueryLibrary

Parse a query library from string content.

Parameters:

  • content: Query library content in .jpx format

Returns: QueryLibrary object

Raises: ValueError for parse errors

Example:

library = jpx.parse_query_library('''
-- :name my-query
-- :desc A sample query
users[?active]
''')

is_query_library

def is_query_library(content: str) -> bool

Check if content appears to be a query library format.

Parameters:

  • content: String content to check

Returns: True if content starts with query library directives

Example:

if jpx.is_query_library(content):
    library = jpx.parse_query_library(content)

Classes

CompiledExpression

A compiled JMESPath expression for efficient repeated evaluation.

Methods

search
def search(self, data: Any) -> Any

Evaluate this expression against data.

Parameters:

  • data: Python data to evaluate against

Returns: Evaluation result


QueryLibrary

A collection of named queries parsed from a .jpx file.

Methods

get
def get(self, name: str) -> Optional[NamedQuery]

Get a query by name.

Parameters:

  • name: Query name

Returns: NamedQuery or None

list
def list(self) -> List[NamedQuery]

Get all queries in the library.

Returns: List of NamedQuery objects

names
def names(self) -> List[str]

Get all query names.

Returns: List of query names


NamedQuery

A single named query from a query library.

Properties

  • name: str - Query name
  • description: Optional[str] - Query description
  • expression: str - JMESPath expression
  • line_number: int - Line number in source file

Example:

query = library.get("my-query")
if query:
    print(f"{query.name}: {query.description}")
    result = jpx.search(query.expression, data)

Real-World Datasets

Learn jpx by working with real data from public APIs. Each example includes:

  • How to fetch the data
  • Data structure overview
  • Progressive examples from basic to advanced
  • Practical use cases

Available Guides

GuideDescriptionKey Features
Standard JMESPath OnlyPortable queries using only spec functions26 built-in functions, no extensions
NLP Text ProcessingText analysis pipelinesTokenization, stemming, stopwords, normalization
Hacker NewsTech discussions via Algolia APINLP on real content, topic detection, vocabulary analysis
USGS EarthquakesReal-time seismic dataGeo functions, statistics, filtering
Nobel Prize APILaureates and prizesMultilingual data, text processing, dates
NASA Near Earth ObjectsAsteroids and cometsNested data, unit conversions, risk analysis
Project ManagementSynthetic project dataComprehensive function coverage, all categories

Quick Start

# Fetch earthquake data
curl -s "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&limit=20&minmagnitude=5" > quakes.json

# Try a query
jpx 'features[*].{mag: properties.mag, place: properties.place}' quakes.json

What You’ll Learn

Filtering & Selection

  • Complex filter expressions with multiple conditions
  • Nested field access patterns
  • Text-based filtering with contains, starts_with

Statistics & Aggregation

  • avg, median, stddev for numeric analysis
  • min, max, min_by, max_by for extremes
  • length and counting patterns

Geographic Calculations

  • geo_distance_km for distance calculations
  • Coordinate extraction and formatting
  • Distance-based sorting

Date/Time Operations

  • Unix timestamp conversion with from_unixtime
  • Date formatting with format_datetime
  • Date range filtering

Data Transformation

  • Reshaping nested structures
  • Flattening for export
  • CSV/TSV output for spreadsheets

Pipeline Patterns

  • Multi-step transformations
  • Sorting and limiting results
  • Building summary reports

Tips for Working with APIs

  1. Save data locally for faster iteration:

    curl -s "API_URL" > data.json
    jpx 'expression' data.json
    
  2. Explore structure first:

    jpx 'keys(@)' data.json          # Top-level keys
    jpx '@[0]' data.json             # First element (arrays)
    jpx 'type(@)' data.json          # Data type
    
  3. Use --compact for pipelines:

    jpx -c 'expression' data.json | jpx 'next_expression'
    
  4. Export for analysis:

    jpx --csv 'transform' data.json > output.csv
    

More Data Sources

Looking for more datasets to practice with? Check out:

Standard JMESPath Only

This guide demonstrates jpx using only standard JMESPath functions - no extensions. This is useful when:

  • You need queries portable to other JMESPath implementations
  • You’re learning JMESPath fundamentals
  • You want to understand what’s possible without extensions

You can also use jpx --strict to enforce standard-only mode, which will error if you accidentally use an extension function.

Standard Functions Reference

JMESPath defines 26 built-in functions:

FunctionDescription
abs(n)Absolute value
avg(arr)Average of numbers
ceil(n)Round up
contains(subject, search)Check if contains value
ends_with(str, suffix)Check string suffix
floor(n)Round down
join(sep, arr)Join array to string
keys(obj)Get object keys
length(val)Length of array/string/object
map(&expr, arr)Apply expression to each element
max(arr)Maximum value
max_by(arr, &expr)Element with max expression value
merge(obj1, obj2, ...)Merge objects
min(arr)Minimum value
min_by(arr, &expr)Element with min expression value
not_null(val1, val2, ...)First non-null value
reverse(arr)Reverse array or string
sort(arr)Sort array
sort_by(arr, &expr)Sort by expression
starts_with(str, prefix)Check string prefix
sum(arr)Sum of numbers
to_array(val)Convert to array
to_number(val)Convert to number
to_string(val)Convert to string
type(val)Get value type
values(obj)Get object values

Sample Data

We’ll use this sample dataset throughout the guide:

cat > books.json << 'EOF'
{
  "library": {
    "name": "City Library",
    "books": [
      {"id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "year": 1925, "genres": ["fiction", "classic"], "rating": 4.2, "available": true},
      {"id": 2, "title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960, "genres": ["fiction", "classic"], "rating": 4.5, "available": false},
      {"id": 3, "title": "1984", "author": "George Orwell", "year": 1949, "genres": ["fiction", "dystopian"], "rating": 4.3, "available": true},
      {"id": 4, "title": "Pride and Prejudice", "author": "Jane Austen", "year": 1813, "genres": ["fiction", "romance"], "rating": 4.4, "available": true},
      {"id": 5, "title": "The Catcher in the Rye", "author": "J.D. Salinger", "year": 1951, "genres": ["fiction", "classic"], "rating": 3.9, "available": false},
      {"id": 6, "title": "Brave New World", "author": "Aldous Huxley", "year": 1932, "genres": ["fiction", "dystopian"], "rating": 4.1, "available": true},
      {"id": 7, "title": "The Hobbit", "author": "J.R.R. Tolkien", "year": 1937, "genres": ["fiction", "fantasy"], "rating": 4.6, "available": true},
      {"id": 8, "title": "Animal Farm", "author": "George Orwell", "year": 1945, "genres": ["fiction", "satire"], "rating": 4.0, "available": false}
    ]
  }
}
EOF

Basic Selection

Access Nested Data

jpx --strict 'library.name' books.json

Output: "City Library"

Get All Books

jpx --strict 'library.books' books.json

Select Specific Fields

jpx --strict 'library.books[*].title' books.json

Output:

["The Great Gatsby", "To Kill a Mockingbird", "1984", "Pride and Prejudice", "The Catcher in the Rye", "Brave New World", "The Hobbit", "Animal Farm"]

Project Multiple Fields

jpx --strict 'library.books[*].{title: title, author: author}' books.json

Output:

[
  {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
  {"title": "To Kill a Mockingbird", "author": "Harper Lee"},
  ...
]

Filtering

Filter by Condition

jpx --strict 'library.books[?available == `true`].title' books.json

Output:

["The Great Gatsby", "1984", "Pride and Prejudice", "Brave New World", "The Hobbit"]

Filter by Numeric Comparison

jpx --strict 'library.books[?rating >= `4.3`].{title: title, rating: rating}' books.json

Output:

[
  {"title": "To Kill a Mockingbird", "rating": 4.5},
  {"title": "1984", "rating": 4.3},
  {"title": "Pride and Prejudice", "rating": 4.4},
  {"title": "The Hobbit", "rating": 4.6}
]

Filter by Year Range

jpx --strict 'library.books[?year >= `1940` && year < `1960`].title' books.json

Output:

["1984", "The Catcher in the Rye", "Animal Farm"]

Filter with String Functions

# Books by authors starting with "J"
jpx --strict 'library.books[?starts_with(author, `J`)].{title: title, author: author}' books.json

Output:

[
  {"title": "Pride and Prejudice", "author": "Jane Austen"},
  {"title": "The Catcher in the Rye", "author": "J.D. Salinger"},
  {"title": "The Hobbit", "author": "J.R.R. Tolkien"}
]

Filter with Contains

# Books with "The" in the title
jpx --strict 'library.books[?contains(title, `The`)].title' books.json

Output:

["The Great Gatsby", "The Catcher in the Rye", "The Hobbit"]

Check Array Membership

# Books in the "dystopian" genre
jpx --strict 'library.books[?contains(genres, `dystopian`)].title' books.json

Output:

["1984", "Brave New World"]

Sorting

Sort by Field

jpx --strict 'sort_by(library.books, &year)[*].{title: title, year: year}' books.json

Output (oldest first):

[
  {"title": "Pride and Prejudice", "year": 1813},
  {"title": "The Great Gatsby", "year": 1925},
  {"title": "Brave New World", "year": 1932},
  ...
]

Sort Descending (Reverse)

jpx --strict 'reverse(sort_by(library.books, &rating))[*].{title: title, rating: rating}' books.json

Output (highest rated first):

[
  {"title": "The Hobbit", "rating": 4.6},
  {"title": "To Kill a Mockingbird", "rating": 4.5},
  {"title": "Pride and Prejudice", "rating": 4.4},
  ...
]

Sort Simple Arrays

jpx --strict 'sort(library.books[*].year)' books.json

Output:

[1813, 1925, 1932, 1937, 1945, 1949, 1951, 1960]

Aggregation

Count Items

jpx --strict 'length(library.books)' books.json

Output: 8

Count Filtered Items

jpx --strict 'length(library.books[?available == `true`])' books.json

Output: 5

Sum Values

jpx --strict 'sum(library.books[*].year)' books.json

Output: 15512

Average

jpx --strict 'avg(library.books[*].rating)' books.json

Output: 4.25

Min and Max

jpx --strict '{
  oldest_year: min(library.books[*].year),
  newest_year: max(library.books[*].year),
  lowest_rating: min(library.books[*].rating),
  highest_rating: max(library.books[*].rating)
}' books.json

Output:

{
  "oldest_year": 1813,
  "newest_year": 1960,
  "lowest_rating": 3.9,
  "highest_rating": 4.6
}

Find Element with Min/Max

# Highest rated book
jpx --strict 'max_by(library.books, &rating).{title: title, rating: rating}' books.json

Output:

{"title": "The Hobbit", "rating": 4.6}
# Oldest book
jpx --strict 'min_by(library.books, &year).{title: title, year: year}' books.json

Output:

{"title": "Pride and Prejudice", "year": 1813}

Object Operations

Get Keys

jpx --strict 'keys(library.books[0])' books.json

Output:

["id", "title", "author", "year", "genres", "rating", "available"]

Get Values

jpx --strict 'values(library.books[0])' books.json

Output:

[1, "The Great Gatsby", "F. Scott Fitzgerald", 1925, ["fiction", "classic"], 4.2, true]

Merge Objects

jpx --strict 'merge(library.books[0], {checked_out: `true`, due_date: `2025-02-01`})' books.json

Output:

{
  "id": 1,
  "title": "The Great Gatsby",
  "author": "F. Scott Fitzgerald",
  "year": 1925,
  "genres": ["fiction", "classic"],
  "rating": 4.2,
  "available": true,
  "checked_out": true,
  "due_date": "2025-02-01"
}

Type Conversions

Check Type

jpx --strict 'type(library.books)' books.json

Output: "array"

jpx --strict 'type(library.books[0].rating)' books.json

Output: "number"

Convert to String

jpx --strict 'library.books[*].{title: title, year_str: to_string(year)}' books.json

Convert to Number

echo '{"value": "42"}' | jpx --strict 'to_number(value)'

Output: 42

Convert to Array

jpx --strict 'to_array(library.name)' books.json

Output: ["City Library"]


String Operations

Join Array Elements

jpx --strict 'join(`, `, library.books[*].title)' books.json

Output: "The Great Gatsby, To Kill a Mockingbird, 1984, Pride and Prejudice, The Catcher in the Rye, Brave New World, The Hobbit, Animal Farm"

Check Prefix/Suffix

# Titles ending with specific words
jpx --strict 'library.books[?ends_with(title, `Rye`)].title' books.json

Output: ["The Catcher in the Rye"]


The Map Function

The map function applies an expression to each element:

# Extract just the year from each book
jpx --strict 'map(&year, library.books)' books.json

Output:

[1925, 1960, 1949, 1813, 1951, 1932, 1937, 1945]
# Create formatted strings
jpx --strict 'map(&{book: title, published: year}, library.books)' books.json

Handling Nulls

First Non-Null Value

echo '{"a": null, "b": null, "c": "found"}' | jpx --strict 'not_null(a, b, c)'

Output: "found"

Null-Safe Field Access

echo '{"items": [{"name": "a", "value": 1}, {"name": "b"}]}' | \
  jpx --strict 'items[*].{name: name, value: not_null(value, `0`)}'

Output:

[
  {"name": "a", "value": 1},
  {"name": "b", "value": 0}
]

Pipelines

Chain operations with the pipe operator |:

# Filter, sort, and take top 3
jpx --strict 'library.books 
  | [?available == `true`] 
  | sort_by(@, &rating) 
  | reverse(@) 
  | [:3] 
  | [*].{title: title, rating: rating}' books.json

Output:

[
  {"title": "The Hobbit", "rating": 4.6},
  {"title": "Pride and Prejudice", "rating": 4.4},
  {"title": "1984", "rating": 4.3}
]

Complex Example: Library Report

Combining multiple standard functions:

jpx --strict '{
  library_name: library.name,
  total_books: length(library.books),
  available_count: length(library.books[?available == `true`]),
  checked_out_count: length(library.books[?available == `false`]),
  average_rating: avg(library.books[*].rating),
  rating_range: {
    min: min(library.books[*].rating),
    max: max(library.books[*].rating)
  },
  year_range: {
    oldest: min(library.books[*].year),
    newest: max(library.books[*].year)
  },
  top_rated: max_by(library.books, &rating).title,
  authors: sort(library.books[*].author),
  available_titles: sort(library.books[?available == `true`][*].title)
}' books.json

Output:

{
  "library_name": "City Library",
  "total_books": 8,
  "available_count": 5,
  "checked_out_count": 3,
  "average_rating": 4.25,
  "rating_range": {"min": 3.9, "max": 4.6},
  "year_range": {"oldest": 1813, "newest": 1960},
  "top_rated": "The Hobbit",
  "authors": ["Aldous Huxley", "F. Scott Fitzgerald", "George Orwell", "George Orwell", "Harper Lee", "J.D. Salinger", "J.R.R. Tolkien", "Jane Austen"],
  "available_titles": ["1984", "Brave New World", "Pride and Prejudice", "The Great Gatsby", "The Hobbit"]
}

When to Use Extensions

Standard JMESPath is powerful for basic transformations, but you’ll want extensions for:

  • String manipulation: split, trim, lower, upper, replace
  • Date/time: from_epoch, format_date, date_diff
  • Advanced math: median, stddev, round, percentile
  • Grouping: group_by, frequencies, unique
  • Text processing: tokens, stems, word_count
  • Hashing: sha256, md5, base64_encode
  • And 350+ more functions

To see what’s available:

jpx --list-functions
jpx --search "group"
jpx --describe group_by

NLP Text Processing

jpx includes a comprehensive NLP toolkit for text analysis and processing. This guide demonstrates how to build text processing pipelines combining tokenization, stemming, stopword removal, and more.

NLP Pipeline Overview

A typical NLP preprocessing pipeline:

Raw Text → Tokenize → Remove Stopwords → Stem → Analyze

jpx makes this composable with pipes:

echo '"The quick brown foxes are running quickly"' | \
  jpx 'tokens(@) | remove_stopwords(@) | stems(@)'
# ["quick", "brown", "fox", "run", "quick"]

Tokenization

Basic Tokenization

The tokens function provides simple, normalized tokenization:

echo '"Hello, World! This is a TEST."' | jpx 'tokens(@)'
# ["hello", "world", "this", "is", "a", "test"]

Configurable Tokenization

Use tokenize for more control:

# Preserve case
echo '"Hello World"' | jpx 'tokenize(@, `{"case": "preserve"}`)'
# ["Hello", "World"]

# Keep punctuation
echo '"Hello, World!"' | jpx 'tokenize(@, `{"punctuation": "keep"}`)'
# ["hello,", "world!"]

# Both options
echo '"Hello, World!"' | jpx 'tokenize(@, `{"case": "preserve", "punctuation": "keep"}`)'
# ["Hello,", "World!"]

Stop Word Removal

Stop words are common words (the, is, at, which) that often don’t add meaning for analysis.

Check Stopwords

# Get English stopwords
echo '{}' | jpx 'stopwords() | length(@)'
# 179

# Check specific words
echo '{}' | jpx 'is_stopword(`"the"`)'
# true

echo '{}' | jpx 'is_stopword(`"algorithm"`)'
# false

Filter Stopwords from Tokens

echo '["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]' | \
  jpx 'remove_stopwords(@)'
# ["quick", "brown", "fox", "jumps", "lazy", "dog"]

Multilingual Support

Stopwords are available in 30+ languages:

# Spanish
echo '{}' | jpx 'stopwords(`"es"`) | slice(@, `0`, `5`)'
# ["de", "la", "que", "el", "en"]

# German
echo '{}' | jpx 'stopwords(`"de"`) | slice(@, `0`, `5`)'
# ["aber", "alle", "allem", "allen", "aller"]

# French
echo '["le", "chat", "noir", "est", "rapide"]' | jpx 'remove_stopwords(@, `"fr"`)'
# ["chat", "noir", "rapide"]

Stemming

Stemming reduces words to their root form, helping match variations like “running”, “runs”, “ran” to “run”.

Single Word Stemming

echo '{}' | jpx 'stem(`"running"`)'
# "run"

echo '{}' | jpx 'stem(`"connections"`)'
# "connect"

echo '{}' | jpx 'stem(`"happiness"`)'
# "happi"

Stem Token Arrays

echo '["running", "jumps", "walked", "swimming"]' | jpx 'stems(@)'
# ["run", "jump", "walk", "swim"]

Multilingual Stemming

Supports 17 languages via Snowball stemmers:

# Spanish
echo '{}' | jpx 'stem(`"corriendo"`, `"es"`)'
# "corr"

# German
echo '{}' | jpx 'stem(`"laufend"`, `"de"`)'
# "lauf"

# French
echo '["courant", "marchant", "parlant"]' | jpx 'stems(@, `"fr"`)'
# ["cour", "march", "parl"]

Supported languages: ar (Arabic), da (Danish), de (German), el (Greek), en (English), es (Spanish), fi (Finnish), fr (French), hu (Hungarian), it (Italian), nl (Dutch), no (Norwegian), pt (Portuguese), ro (Romanian), ru (Russian), sv (Swedish), tr (Turkish).


Text Normalization

Unicode Normalization

Normalize text to consistent Unicode forms:

# NFC (composed) - default
echo '"café"' | jpx 'normalize_unicode(@)'
# "café"

# NFKC - compatibility composed (expands ligatures)
echo '"file"' | jpx 'normalize_unicode(@, `"nfkc"`)'
# "file"

Remove Accents

Strip diacritical marks for accent-insensitive matching:

echo '"café résumé naïve"' | jpx 'remove_accents(@)'
# "cafe resume naive"

echo '"señor über"' | jpx 'remove_accents(@)'
# "senor uber"

Collapse Whitespace

Normalize irregular whitespace:

echo '"hello    world\t\ntest"' | jpx 'collapse_whitespace(@)'
# "hello world test"

Complete NLP Pipelines

Basic Text Preprocessing

TEXT='"The quick brown foxes are running and jumping quickly over the lazy dogs."'

echo "$TEXT" | jpx '
  tokens(@) 
  | remove_stopwords(@) 
  | stems(@)
'
# ["quick", "brown", "fox", "run", "jump", "quick", "lazi", "dog"]

Word Frequency Analysis

TEXT='"The cat sat on the mat. The cat was happy. The mat was soft."'

# Get word frequencies after preprocessing
echo "$TEXT" | jpx '
  tokens(@) 
  | remove_stopwords(@) 
  | frequencies(@)
'
# {"cat": 2, "sat": 1, "mat": 2, "happi": 1, "soft": 1}

# With stemming first
echo "$TEXT" | jpx '
  tokens(@) 
  | remove_stopwords(@) 
  | stems(@)
  | frequencies(@)
'

N-gram Analysis After Preprocessing

TEXT='"Machine learning models require training data"'

# Word bigrams
echo "$TEXT" | jpx '
  tokens(@) 
  | remove_stopwords(@) 
  | join(` `, @) 
  | bigrams(@)
'
# [["machine", "learning"], ["learning", "models"], ["models", "require"], ["require", "training"], ["training", "data"]]

Multilingual Pipeline

# French text processing
FRENCH='"Les chats noirs sont très rapides et intelligents"'

echo "$FRENCH" | jpx '
  tokens(@) 
  | remove_stopwords(@, `"fr"`) 
  | stems(@, `"fr"`)
'
# ["chat", "noir", "rapid", "intelligent"]

Working with JSON Data

Process Text Fields in Objects

cat <<'EOF' | jpx '
  [*].{
    id: id,
    original: text,
    tokens: tokens(text) | remove_stopwords(@) | stems(@),
    word_count: word_count(text)
  }
'
[
  {"id": 1, "text": "The quick brown fox jumps over the lazy dog"},
  {"id": 2, "text": "Machine learning algorithms are transforming industries"},
  {"id": 3, "text": "Natural language processing enables text analysis"}
]
EOF

Extract and Analyze Descriptions

# Analyze product descriptions
cat <<'EOF' | jpx '
  products[*].{
    name: name,
    keywords: tokens(description) | remove_stopwords(@) | stems(@) | unique(@)
  }
'
{
  "products": [
    {"name": "Widget A", "description": "A fantastic widget for organizing your daily tasks"},
    {"name": "Widget B", "description": "An amazing widget for managing your weekly schedule"}
  ]
}
EOF

Text Similarity Preparation

Prepare text for similarity comparison:

cat <<'EOF' | jpx '
  [*].{
    id: id,
    normalized: text 
      | lower(@) 
      | remove_accents(collapse_whitespace(@))
      | tokens(@) 
      | remove_stopwords(@) 
      | stems(@) 
      | sort(@) 
      | join(` `, @)
  }
'
[
  {"id": 1, "text": "The café serves excellent coffee"},
  {"id": 2, "text": "Cafés serving good coffees"}
]
EOF
# Both normalize to similar representations for comparison

Combining with Other jpx Functions

With Language Detection

cat <<'EOF' | jpx '
  [*].{
    text: text,
    language: detect_language_iso(text),
    tokens: tokens(text)
  }
'
[
  {"text": "Hello world, this is English"},
  {"text": "Bonjour le monde, ceci est français"},
  {"text": "Hola mundo, esto es español"}
]
EOF

With Fuzzy Matching

Find similar terms after stemming:

echo '{}' | jpx '{
  terms: [`"running"`, `"runner"`, `"ran"`, `"run"`] | stems(@) | unique(@),
  similar: jaro_winkler(`"run"`, `"ran"`)
}'
# {"terms": ["run"], "similar": 0.933...}

With Phonetic Matching

Combine text normalization with phonetic codes:

echo '["café", "cafe", "caffè"]' | jpx '[*].{
  original: @,
  normalized: remove_accents(@),
  soundex: soundex(remove_accents(@))
}'

Performance Tips

  1. Chain operations efficiently: The pipe operator streams data without intermediate allocations

  2. Filter early: Remove stopwords before expensive operations like stemming

  3. Use tokens for simple cases: It’s optimized for the common case (lowercase, no punctuation)

  4. Batch process: Process arrays of text rather than calling functions repeatedly

# Good: Process array at once
echo '["text1", "text2", "text3"]' | jpx '[*] | stems(tokens(@[0]))'

# Better: Use map expressions
echo '{"texts": ["running fast", "jumping high"]}' | jpx 'texts[*] | [*].tokens(@)'

Using Query Libraries

Save your NLP pipelines in a .jpx query library for reuse. See examples/nlp.jpx for ready-to-use text processing queries:

# List available NLP queries
jpx -Q examples/nlp.jpx --list-queries

# Clean HTML from text
echo '"<p>Hello <b>World</b>!</p>"' | jpx -Q examples/nlp.jpx:clean-html

# Extract keywords
echo '"The quick brown foxes are running quickly"' | jpx -Q examples/nlp.jpx:extract-keywords

# Get reading statistics
cat article.txt | jpx -Q examples/nlp.jpx:reading-stats

Create your own domain-specific library:

-- :name preprocess
-- :desc Standard preprocessing pipeline
tokens(@) | remove_stopwords(@) | stems(@)

-- :name keyword-extract
-- :desc Top 10 keywords from text
tokens(@) | remove_stopwords(@) | stems(@) | frequencies(@) | items(@) | sort_by(@, &[1]) | reverse(@) | [:10][*][0]

See Query Files for more on creating and using query libraries.


Hacker News

The Hacker News Algolia API provides searchable access to HN stories, comments, and discussions. This dataset is excellent for NLP analysis because it has:

  • Rich technical text (story titles, Ask HN posts, comments)
  • Metadata for filtering (points, comments, timestamps)
  • Real developer discussions with domain-specific vocabulary
  • Multilingual content (occasional non-English posts)

Getting the Data

# Front page stories
curl -s "https://hn.algolia.com/api/v1/search?tags=front_page&hitsPerPage=50" > hn_front.json

# Ask HN posts (rich text content)
curl -s "https://hn.algolia.com/api/v1/search?tags=ask_hn&hitsPerPage=50" > hn_ask.json

# Show HN posts
curl -s "https://hn.algolia.com/api/v1/search?tags=show_hn&hitsPerPage=50" > hn_show.json

# Search for specific topics
curl -s "https://hn.algolia.com/api/v1/search?query=rust&tags=story&hitsPerPage=50" > hn_rust.json

# Comments on a story
curl -s "https://hn.algolia.com/api/v1/search?tags=comment,story_12345&hitsPerPage=100" > comments.json

Data Structure

The search API returns:

{
  "hits": [
    {
      "objectID": "37392676",
      "title": "Ask HN: I'm an FCC Commissioner proposing regulation of IoT security",
      "url": null,
      "author": "SimingtonFCC",
      "points": 2847,
      "story_text": "Hi everyone, I'm FCC Commissioner Nathan Simington...",
      "num_comments": 475,
      "created_at": "2023-09-05T15:00:00.000Z",
      "created_at_i": 1693926000,
      "_tags": ["story", "author_SimingtonFCC", "story_37392676", "ask_hn"]
    }
  ],
  "nbHits": 12345,
  "page": 0,
  "nbPages": 50,
  "hitsPerPage": 50
}

Key fields:

  • title - Story headline (always present)
  • story_text - Full text for Ask HN / Show HN posts (HTML)
  • url - External link (null for Ask HN)
  • points - Upvotes
  • num_comments - Discussion size
  • _tags - Includes ask_hn, show_hn, front_page, etc.

Basic Queries

List Story Titles

jpx 'hits[*].title' hn_front.json

Get Top Stories with Metadata

jpx 'hits[*].{
  title: title,
  points: points,
  comments: num_comments,
  author: author
}' hn_front.json

Filter by Points

jpx 'hits[?points > `500`].title' hn_front.json

Sort by Discussion Size

jpx 'sort_by(hits, &num_comments) | reverse(@) | [:10].{
  title: title,
  comments: num_comments
}' hn_front.json

NLP Analysis on Titles

Tokenize and Analyze Titles

# Get word frequencies across all titles
jpx 'hits[*].title | join(` `, @) | tokens(@) | remove_stopwords(@) | frequencies(@)' hn_front.json

Extract Keywords from Top Stories

jpx 'hits[?points > `200`].title 
  | join(` `, @) 
  | tokens(@) 
  | remove_stopwords(@) 
  | stems(@) 
  | frequencies(@)' hn_front.json

Find Common Technical Terms

# Stem and count to normalize variations (Rust/rust, APIs/API)
jpx 'hits[*].title 
  | join(` `, @) 
  | tokens(@) 
  | stems(@) 
  | frequencies(@) 
  | items(@) 
  | sort_by(@, &[1]) 
  | reverse(@) 
  | [:20]' hn_front.json

Bigram Analysis on Headlines

jpx 'hits[*].title | join(` `, @) | bigrams(@) | frequencies(@)' hn_front.json

Analyzing Ask HN Posts

Ask HN posts have rich story_text content - perfect for NLP.

Extract and Clean Story Text

# Remove HTML tags and normalize
jpx 'hits[0].story_text 
  | regex_replace(@, `<[^>]+>`, ` `) 
  | collapse_whitespace(@)' hn_ask.json

Full Text Analysis Pipeline

jpx 'hits[0].story_text 
  | regex_replace(@, `<[^>]+>`, ` `)
  | tokens(@) 
  | remove_stopwords(@) 
  | stems(@) 
  | frequencies(@)
  | items(@)
  | sort_by(@, &[1])
  | reverse(@)
  | [:15]' hn_ask.json

Compare Vocabulary Across Posts

# Extract top keywords per Ask HN post
jpx 'hits[:5] | [*].{
  title: title,
  keywords: story_text 
    | regex_replace(@, `<[^>]+>`, ` `) 
    | tokens(@) 
    | remove_stopwords(@) 
    | stems(@) 
    | frequencies(@) 
    | items(@) 
    | sort_by(@, &[1]) 
    | reverse(@) 
    | [:5][*][0]
}' hn_ask.json

Reading Time Estimates

jpx 'hits[:10] | [*].{
  title: title,
  reading_time: story_text | regex_replace(@, `<[^>]+>`, ` `) | reading_time(@),
  word_count: story_text | regex_replace(@, `<[^>]+>`, ` `) | word_count(@)
}' hn_ask.json

Topic Detection

Categorize by Keywords

# Find AI/ML related posts
jpx 'hits[?contains(lower(title), `ai`) || 
        contains(lower(title), `llm`) || 
        contains(lower(title), `gpt`) ||
        contains(lower(title), `machine learning`)].{
  title: title,
  points: points
}' hn_front.json

Programming Language Mentions

jpx 'hits[*].{
  title: title,
  mentions_rust: contains(lower(title), `rust`),
  mentions_go: regex_match(lower(title), `\\bgo\\b`),
  mentions_python: contains(lower(title), `python`)
} | [?mentions_rust || mentions_go || mentions_python]' hn_front.json

N-gram Topic Extraction

# Find common 2-word phrases (potential topics)
jpx 'hits[*].title 
  | [*] | join(` `, @) 
  | lower(@)
  | ngrams(@, `2`, `word`)
  | frequencies(@)
  | items(@)
  | sort_by(@, &[1])
  | reverse(@)
  | [:10]' hn_front.json

Sentiment and Engagement Analysis

High Engagement Posts

# Posts with high comment-to-point ratio (controversial?)
jpx 'hits[?points > `100`] | [*].{
  title: title,
  points: points,
  comments: num_comments,
  ratio: divide(num_comments, points)
} | sort_by(@, &ratio) | reverse(@) | [:10]' hn_front.json

Question Posts (Seeking Help)

jpx 'hits[?ends_with(title, `?`)].{
  title: title,
  comments: num_comments
}' hn_front.json

Time-Based Analysis

Posts by Hour

jpx 'hits[*].{
  title: title,
  hour: split(created_at, `T`)[1] | split(@, `:`)[0]
} | group_by(@, &hour)' hn_front.json

Recent vs Older Content

jpx 'hits | {
  recent: [?created_at_i > `1700000000`] | length(@),
  older: [?created_at_i <= `1700000000`] | length(@)
}' hn_front.json

Author Analysis

Most Active Authors

jpx 'hits[*].author | frequencies(@) | items(@) | sort_by(@, &[1]) | reverse(@) | [:10]' hn_front.json

Author Vocabulary Fingerprint

# What words does a specific author use most?
jpx 'hits[?author == `dang`].title 
  | join(` `, @) 
  | tokens(@) 
  | remove_stopwords(@) 
  | frequencies(@)' hn_front.json

Cross-Dataset Comparisons

Compare Ask HN vs Show HN Vocabulary

# Run separately and compare results
jpx 'hits[*].title | join(` `, @) | tokens(@) | remove_stopwords(@) | stems(@) | frequencies(@)' hn_ask.json > ask_vocab.json
jpx 'hits[*].title | join(` `, @) | tokens(@) | remove_stopwords(@) | stems(@) | frequencies(@)' hn_show.json > show_vocab.json

Search Query Analysis

# Fetch multiple topics and compare
curl -s "https://hn.algolia.com/api/v1/search?query=kubernetes&hitsPerPage=30" > hn_k8s.json
curl -s "https://hn.algolia.com/api/v1/search?query=docker&hitsPerPage=30" > hn_docker.json

# Compare title vocabulary
jpx 'hits[*].title | join(` `, @) | tokens(@) | remove_stopwords(@) | frequencies(@)' hn_k8s.json
jpx 'hits[*].title | join(` `, @) | tokens(@) | remove_stopwords(@) | frequencies(@)' hn_docker.json

Building a Search Index

Use the NLP functions to prepare content for search:

# Create searchable document representations
jpx 'hits[:20] | [*].{
  id: objectID,
  title: title,
  normalized_title: title | lower(@) | remove_accents(@),
  title_tokens: title | tokens(@) | remove_stopwords(@) | stems(@),
  has_text: story_text != null,
  text_keywords: story_text 
    | default(@, `""`)
    | regex_replace(@, `<[^>]+>`, ` `)
    | tokens(@)
    | remove_stopwords(@)
    | stems(@)
    | unique(@)
    | slice(@, `0`, `20`)
}' hn_ask.json

Complete Analysis Pipeline

Here’s a comprehensive analysis combining multiple NLP techniques:

jpx '{
  meta: {
    total_stories: length(hits),
    total_points: sum(hits[*].points),
    avg_comments: avg(hits[*].num_comments)
  },
  top_keywords: hits[*].title 
    | join(` `, @) 
    | tokens(@) 
    | remove_stopwords(@) 
    | stems(@) 
    | frequencies(@)
    | items(@)
    | sort_by(@, &[1])
    | reverse(@)
    | [:10][*][0],
  top_bigrams: hits[*].title
    | join(` `, @)
    | lower(@)
    | bigrams(@)
    | [*] | join(` `, @)
    | frequencies(@)
    | items(@)
    | sort_by(@, &[1])
    | reverse(@)
    | [:5][*][0],
  question_posts: length(hits[?ends_with(title, `?`)]),
  avg_title_words: avg(hits[*].title | [*] | word_count(@))
}' hn_front.json

Using Query Libraries

Instead of typing these queries repeatedly, save them in a .jpx query library. See examples/hacker-news.jpx for a ready-to-use library:

# List available queries
jpx -Q examples/hacker-news.jpx --list-queries

# Run common analyses
jpx -Q examples/hacker-news.jpx:title-keywords hn_front.json
jpx -Q examples/hacker-news.jpx:top-stories hn_front.json
jpx -Q examples/hacker-news.jpx:summary hn_front.json

# Output as table
jpx -Q examples/hacker-news.jpx:most-discussed -t hn_front.json

See Query Files for more on creating and using query libraries.


Tips for HN Data

  1. HTML in story_text: Always strip HTML tags before NLP processing

    regex_replace(story_text, `<[^>]+>`, ` `)
    
  2. Rate limiting: The Algolia API is generous but cache results locally for iteration

  3. Pagination: Use page parameter for more results

    curl "https://hn.algolia.com/api/v1/search?tags=ask_hn&page=2&hitsPerPage=50"
    
  4. Date filtering: Use numericFilters for time ranges

    curl "https://hn.algolia.com/api/v1/search?numericFilters=created_at_i>1700000000"
    
  5. Combining with fuzzy search: Use jpx’s fuzzy_search on processed results

    jpx 'fuzzy_search(hits, `title`, `database`)' hn_front.json
    

USGS Earthquake Data

The USGS Earthquake Hazards Program provides real-time earthquake data via a public API. This dataset is excellent for learning jpx because it has:

  • Nested GeoJSON structure
  • Numeric data for statistical analysis (magnitude, depth)
  • Geographic coordinates for geo functions
  • Timestamps for date/time operations

Getting the Data

# Fetch recent magnitude 5+ earthquakes (10 events)
curl -s "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&limit=10&minmagnitude=5" > earthquakes.json

# Fetch more data to play with (100 events)
curl -s "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&limit=100&minmagnitude=4" > earthquakes_100.json

Data Structure

The API returns GeoJSON with this structure:

{
  "type": "FeatureCollection",
  "metadata": {
    "generated": 1768410727000,
    "title": "USGS Earthquakes",
    "count": 10
  },
  "features": [
    {
      "type": "Feature",
      "properties": {
        "mag": 6.2,
        "place": "133 km SE of Kuril'sk, Russia",
        "time": 1768289648725,
        "sig": 591,
        "tsunami": 0,
        "type": "earthquake",
        "title": "M 6.2 - 133 km SE of Kuril'sk, Russia"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [149.2768, 44.5495, 35.788]
      },
      "id": "us7000rpcg"
    }
  ]
}

Key fields:

  • properties.mag - Magnitude
  • properties.place - Human-readable location
  • properties.time - Unix timestamp (milliseconds)
  • properties.sig - Significance (0-1000+)
  • geometry.coordinates - [longitude, latitude, depth_km]

Basic Queries

List All Earthquake Titles

jpx 'features[*].properties.title' earthquakes.json

Output:

[
  "M 5.1 - 119 km N of Lae, Papua New Guinea",
  "M 5.1 - 68 km E of Severo-Kuril'sk, Russia",
  "M 6.2 - 133 km SE of Kuril'sk, Russia",
  ...
]

Get Magnitudes Only

jpx 'features[*].properties.mag' earthquakes.json

Output:

[5.1, 5.1, 6.2, 5.3, 5.6, 5.2, 5.2, 5.1, 5.0, 5.6]

Count Total Events

jpx 'length(features)' earthquakes.json

Output: 10


Filtering

Find Major Earthquakes (Magnitude 6+)

jpx 'features[?properties.mag >= `6`].properties.title' earthquakes.json

Output:

["M 6.2 - 133 km SE of Kuril'sk, Russia"]

Find Deep Earthquakes (> 100km depth)

The depth is the third element in the coordinates array:

jpx 'features[?geometry.coordinates[2] > `100`].{
  title: properties.title,
  depth_km: geometry.coordinates[2]
}' earthquakes.json

Output:

[
  {"title": "M 5.1 - 119 km N of Lae, Papua New Guinea", "depth_km": 172.746},
  {"title": "M 5.3 - 72 km W of Ollagüe, Chile", "depth_km": 118.039},
  {"title": "M 5.1 - 233 km ENE of Lospalos, Timor Leste", "depth_km": 101.686}
]

Find Earthquakes with Tsunami Alerts

jpx 'features[?properties.tsunami == `1`].properties.title' earthquakes.json

Filter by Location (Text Match)

jpx 'features[?contains(properties.place, `Russia`)].properties.title' earthquakes.json

Output:

[
  "M 5.1 - 68 km E of Severo-Kuril'sk, Russia",
  "M 6.2 - 133 km SE of Kuril'sk, Russia",
  "M 5.2 - 177 km S of Severo-Kuril'sk, Russia",
  "M 5.6 - 265 km N of Kuril'sk, Russia"
]

Statistics

Average Magnitude

jpx 'avg(features[*].properties.mag)' earthquakes.json

Output: 5.34

Magnitude Statistics

jpx '{
  count: length(features),
  min: min(features[*].properties.mag),
  max: max(features[*].properties.mag),
  avg: avg(features[*].properties.mag),
  median: median(features[*].properties.mag),
  stddev: round(stddev(features[*].properties.mag), `3`)
}' earthquakes.json

Output:

{
  "count": 10,
  "min": 5.0,
  "max": 6.2,
  "avg": 5.34,
  "median": 5.2,
  "stddev": 0.351
}

Depth Statistics

jpx '{
  shallowest: min(features[*].geometry.coordinates[2]),
  deepest: max(features[*].geometry.coordinates[2]),
  avg_depth: round(avg(features[*].geometry.coordinates[2]), `1`)
}' earthquakes.json

Sorting

Sort by Magnitude (Descending)

jpx 'sort_by(features, &properties.mag) | reverse(@) | [*].{
  mag: properties.mag,
  place: properties.place
}' earthquakes.json

Output:

[
  {"mag": 6.2, "place": "133 km SE of Kuril'sk, Russia"},
  {"mag": 5.6, "place": "Easter Island region"},
  {"mag": 5.6, "place": "265 km N of Kuril'sk, Russia"},
  ...
]

Top 3 by Significance

jpx 'sort_by(features, &properties.sig) | reverse(@) | [:3] | [*].{
  significance: properties.sig,
  title: properties.title
}' earthquakes.json

Geographic Calculations

Extract Coordinates

jpx 'features[*].{
  place: properties.place,
  lat: geometry.coordinates[1],
  lon: geometry.coordinates[0]
}' earthquakes.json

Distance from a Reference Point

Calculate distance from San Francisco (37.7749, -122.4194) using the geo_distance_km function:

jpx 'features[*].{
  place: properties.place,
  distance_km: round(geo_distance_km(
    `37.7749`, `-122.4194`,
    geometry.coordinates[1], geometry.coordinates[0]
  ), `0`)
} | sort_by(@, &distance_km)' earthquakes.json

Output:

[
  {"place": "Easter Island region", "distance_km": 7580},
  {"place": "72 km W of Ollagüe, Chile", "distance_km": 9513},
  {"place": "South Sandwich Islands region", "distance_km": 12748},
  ...
]

Find Nearest Earthquake

jpx 'min_by(features, &geo_distance_km(
  `37.7749`, `-122.4194`,
  geometry.coordinates[1], geometry.coordinates[0]
)).properties.title' earthquakes.json

Date/Time Operations

Convert Timestamps to Readable Dates

The time field is Unix milliseconds. Convert to ISO format:

jpx 'features[*].{
  title: properties.title,
  date: from_unixtime(floor(divide(properties.time, `1000`)))
}' earthquakes.json

Find Earthquakes in Last 24 Hours

jpx --let now='now()' 'features[?properties.time > multiply(subtract($now, `86400`), `1000`)].properties.title' earthquakes.json

Group by Day

jpx 'features[*].{
  day: format_datetime(from_unixtime(floor(divide(properties.time, `1000`))), `%Y-%m-%d`),
  mag: properties.mag,
  place: properties.place
}' earthquakes.json

Data Transformation

Reshape to Flat Structure

jpx 'features[*].{
  id: id,
  magnitude: properties.mag,
  location: properties.place,
  longitude: geometry.coordinates[0],
  latitude: geometry.coordinates[1],
  depth_km: geometry.coordinates[2],
  timestamp: properties.time,
  url: properties.url
}' earthquakes.json

Export to CSV

jpx --csv 'features[*].{
  magnitude: properties.mag,
  location: properties.place,
  latitude: geometry.coordinates[1],
  longitude: geometry.coordinates[0],
  depth_km: geometry.coordinates[2]
}' earthquakes.json

Output:

magnitude,location,latitude,longitude,depth_km
5.1,"119 km N of Lae, Papua New Guinea",-5.6511,147.12,172.746
5.1,"68 km E of Severo-Kuril'sk, Russia",50.7928,157.0843,64.364
6.2,"133 km SE of Kuril'sk, Russia",44.5495,149.2768,35.788
...

Advanced Pipelines

Summary Report

Create a comprehensive earthquake summary:

jpx '{
  report_title: `USGS Earthquake Summary`,
  generated: metadata.title,
  total_events: length(features),
  magnitude_stats: {
    min: min(features[*].properties.mag),
    max: max(features[*].properties.mag),
    average: round(avg(features[*].properties.mag), `2`),
    median: median(features[*].properties.mag)
  },
  depth_stats: {
    shallowest_km: min(features[*].geometry.coordinates[2]),
    deepest_km: max(features[*].geometry.coordinates[2])
  },
  major_events: features[?properties.mag >= `5.5`] | [*].{
    magnitude: properties.mag,
    location: properties.place
  },
  by_region: {
    russia: length(features[?contains(properties.place, `Russia`)]),
    indonesia: length(features[?contains(properties.place, `Indonesia`)]),
    chile: length(features[?contains(properties.place, `Chile`)])
  }
}' earthquakes.json

Pipeline with Multiple Steps

# Get major earthquakes, sort by magnitude, format for display
jpx 'features 
  | [?properties.mag >= `5.5`] 
  | sort_by(@, &properties.mag) 
  | reverse(@) 
  | [*].{
      mag: properties.mag,
      where: properties.place,
      depth: join(``, [to_string(geometry.coordinates[2]), ` km`])
    }' earthquakes.json

Try It Yourself

  1. Fetch live data: The USGS API updates in real-time

    curl -s "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&limit=50&minmagnitude=4" > quakes.json
    
  2. Explore the API parameters:

    • minmagnitude / maxmagnitude - Filter by magnitude
    • starttime / endtime - Date range (ISO 8601)
    • latitude / longitude / maxradiuskm - Geographic radius
    • limit - Number of results (max 20000)
  3. API Documentation: USGS Earthquake API

Nobel Prize Data

The Nobel Prize API provides comprehensive data about Nobel laureates and prizes. This dataset is excellent for learning jpx because it has:

  • Deeply nested multilingual data
  • Rich biographical information
  • Dates and geographic locations
  • Complex relationships (laureates, prizes, affiliations)

Getting the Data

# Fetch laureates (paginated, 25 per page by default)
curl -s "https://api.nobelprize.org/2.1/laureates?limit=50" > laureates.json

# Fetch all prizes
curl -s "https://api.nobelprize.org/2.1/nobelPrizes?limit=100" > prizes.json

# Fetch laureates by category
curl -s "https://api.nobelprize.org/2.1/laureates?nobelPrizeCategory=phy&limit=50" > physics_laureates.json

Data Structure

The laureates endpoint returns:

{
  "laureates": [
    {
      "id": "102",
      "knownName": {"en": "Aage N. Bohr", "se": "Aage N. Bohr"},
      "givenName": {"en": "Aage N."},
      "familyName": {"en": "Bohr"},
      "gender": "male",
      "birth": {
        "date": "1922-06-19",
        "place": {
          "city": {"en": "Copenhagen"},
          "country": {"en": "Denmark"},
          "continent": {"en": "Europe"}
        }
      },
      "death": {
        "date": "2009-09-08",
        "place": {...}
      },
      "nobelPrizes": [
        {
          "awardYear": "1975",
          "category": {"en": "Physics"},
          "portion": "1/3",
          "motivation": {"en": "for the discovery of..."},
          "prizeAmount": 630000,
          "affiliations": [...]
        }
      ]
    }
  ],
  "meta": {"count": 50, "limit": 50, "offset": 0}
}

Basic Queries

List All Laureate Names

jpx 'laureates[*].knownName.en' laureates.json

Get Name and Prize Year

jpx 'laureates[*].{
  name: knownName.en,
  year: nobelPrizes[0].awardYear,
  category: nobelPrizes[0].category.en
}' laureates.json

Output:

[
  {"name": "A. Michael Spence", "year": "2001", "category": "Economic Sciences"},
  {"name": "Aage N. Bohr", "year": "1975", "category": "Physics"},
  ...
]

Count Laureates

jpx 'length(laureates)' laureates.json

Filtering

Find Physics Laureates

jpx 'laureates[?nobelPrizes[0].category.en == `Physics`].knownName.en' laureates.json

Find Female Laureates

jpx 'laureates[?gender == `female`].{
  name: knownName.en,
  category: nobelPrizes[0].category.en,
  year: nobelPrizes[0].awardYear
}' laureates.json

Find Living Laureates

jpx 'laureates[?death == null].{
  name: knownName.en,
  born: birth.date
}' laureates.json

Find Laureates Born in a Specific Country

jpx 'laureates[?birth.place.country.en == `USA`].{
  name: knownName.en,
  city: birth.place.city.en
}' laureates.json

Find Multiple Prize Winners

Some laureates have won multiple Nobel Prizes:

jpx 'laureates[?length(nobelPrizes) > `1`].{
  name: knownName.en,
  prizes: nobelPrizes[*].{
    year: awardYear,
    category: category.en
  }
}' laureates.json

Working with Multilingual Data

The API provides data in multiple languages (en, se, no):

Extract English Names

jpx 'laureates[*].knownName.en' laureates.json

Compare Languages

jpx 'laureates[:5] | [*].{
  english: knownName.en,
  swedish: knownName.se
}' laureates.json

Get Motivation in English

jpx 'laureates[*].{
  name: knownName.en,
  motivation: nobelPrizes[0].motivation.en
}' laureates.json

Date Operations

Extract Birth Years

jpx 'laureates[*].{
  name: knownName.en,
  birth_year: split(birth.date, `-`)[0]
}' laureates.json

Calculate Age at Award

jpx 'laureates[?birth.date != null].{
  name: knownName.en,
  born: split(birth.date, `-`)[0],
  awarded: nobelPrizes[0].awardYear,
  age_at_award: subtract(
    to_number(nobelPrizes[0].awardYear),
    to_number(split(birth.date, `-`)[0])
  )
}' laureates.json

Find Laureates Born Before 1900

jpx 'laureates[?to_number(split(birth.date, `-`)[0]) < `1900`].{
  name: knownName.en,
  born: birth.date
}' laureates.json

Geographic Analysis

Laureates by Birth Country

jpx 'laureates[*].birth.place.country.en' laureates.json | jpx -s 'group_by(@, &@) | @.{
  country: [0],
  count: length(@)
}'

Or using frequencies:

jpx 'frequencies(laureates[*].birth.place.country.en)' laureates.json

Laureates by Continent

jpx 'laureates[*].{
  name: knownName.en,
  continent: birth.place.continent.en
}' laureates.json

Extract Coordinates

jpx 'laureates[?birth.place.cityNow.latitude != null].{
  name: knownName.en,
  city: birth.place.city.en,
  lat: birth.place.cityNow.latitude,
  lon: birth.place.cityNow.longitude
}' laureates.json

Prize Analysis

Prize Amounts Over Time

jpx 'laureates[*].nobelPrizes[0].{
  year: awardYear,
  amount: prizeAmount,
  adjusted: prizeAmountAdjusted
} | sort_by(@, &year)' laureates.json

Shared Prizes

The portion field shows how the prize was split:

jpx 'laureates[?nobelPrizes[0].portion != `1`].{
  name: knownName.en,
  portion: nobelPrizes[0].portion,
  year: nobelPrizes[0].awardYear
}' laureates.json

Find Solo Winners

jpx 'laureates[?nobelPrizes[0].portion == `1`].{
  name: knownName.en,
  category: nobelPrizes[0].category.en,
  year: nobelPrizes[0].awardYear
}' laureates.json

Affiliations

Extract University Affiliations

jpx 'laureates[*].{
  name: knownName.en,
  university: nobelPrizes[0].affiliations[0].name.en,
  country: nobelPrizes[0].affiliations[0].country.en
}' laureates.json

Find Stanford Affiliates

jpx 'laureates[?contains(to_string(nobelPrizes[*].affiliations[*].name.en), `Stanford`)].knownName.en' laureates.json

Text Processing

Search Motivations

Find laureates whose work involved “quantum”:

jpx 'laureates[?contains(lower(to_string(nobelPrizes[*].motivation.en)), `quantum`)].{
  name: knownName.en,
  motivation: nobelPrizes[0].motivation.en
}' laureates.json

Extract Key Terms from Motivations

jpx 'laureates[*].nobelPrizes[0].motivation.en' laureates.json

Data Transformation

Flatten for Export

jpx 'laureates[*].{
  id: id,
  name: knownName.en,
  gender: gender,
  birth_date: birth.date,
  birth_country: birth.place.country.en,
  death_date: death.date,
  prize_year: nobelPrizes[0].awardYear,
  prize_category: nobelPrizes[0].category.en,
  prize_portion: nobelPrizes[0].portion,
  motivation: nobelPrizes[0].motivation.en
}' laureates.json

Export to CSV

jpx --csv 'laureates[*].{
  name: knownName.en,
  year: nobelPrizes[0].awardYear,
  category: nobelPrizes[0].category.en,
  country: birth.place.country.en
}' laureates.json

Advanced Pipelines

Summary by Category

jpx '{
  physics: length(laureates[?nobelPrizes[0].category.en == `Physics`]),
  chemistry: length(laureates[?nobelPrizes[0].category.en == `Chemistry`]),
  medicine: length(laureates[?contains(nobelPrizes[0].category.en, `Medicine`) || contains(nobelPrizes[0].category.en, `Physiology`)]),
  literature: length(laureates[?nobelPrizes[0].category.en == `Literature`]),
  peace: length(laureates[?nobelPrizes[0].category.en == `Peace`]),
  economics: length(laureates[?contains(nobelPrizes[0].category.en, `Economic`)])
}' laureates.json

Gender Distribution by Decade

jpx 'laureates[*].{
  decade: concat(slice(nobelPrizes[0].awardYear, `0`, `3`), `0s`),
  gender: gender
}' laureates.json

Youngest and Oldest Winners

jpx '{
  dataset: laureates[?birth.date != null] | [*].{
    name: knownName.en,
    age: subtract(to_number(nobelPrizes[0].awardYear), to_number(split(birth.date, `-`)[0]))
  },
  youngest: min_by(@.dataset, &age),
  oldest: max_by(@.dataset, &age)
}' laureates.json

Try It Yourself

  1. Explore the API:

    # All laureates (paginated)
    curl -s "https://api.nobelprize.org/2.1/laureates?limit=100&offset=0"
    
    # Filter by category
    curl -s "https://api.nobelprize.org/2.1/laureates?nobelPrizeCategory=lit"
    
    # Filter by year
    curl -s "https://api.nobelprize.org/2.1/nobelPrizes?nobelPrizeYear=2023"
    
  2. Category codes: phy (Physics), che (Chemistry), med (Medicine), lit (Literature), pea (Peace), eco (Economics)

  3. API Documentation: Nobel Prize API

NASA Near Earth Objects

NASA’s Near Earth Object Web Service provides data about asteroids and comets that pass close to Earth. This dataset is excellent for learning jpx because it has:

  • Deeply nested data structures
  • Multiple unit systems (km, miles, AU, lunar distances)
  • Boolean flags for hazard classification
  • Time series data (close approach dates)
  • Complex orbital mechanics data

Getting the Data

# Browse all NEOs (paginated)
curl -s "https://api.nasa.gov/neo/rest/v1/neo/browse?api_key=DEMO_KEY" > neo_browse.json

# Get NEOs by close approach date (today)
curl -s "https://api.nasa.gov/neo/rest/v1/feed?api_key=DEMO_KEY" > neo_feed.json

# Get specific date range
curl -s "https://api.nasa.gov/neo/rest/v1/feed?start_date=2024-01-01&end_date=2024-01-07&api_key=DEMO_KEY" > neo_week.json

# Look up specific asteroid
curl -s "https://api.nasa.gov/neo/rest/v1/neo/2000433?api_key=DEMO_KEY" > eros.json

Note: Use DEMO_KEY for testing (limited rate). Get a free API key at api.nasa.gov.

Data Structure

The browse endpoint returns:

{
  "links": {"self": "...", "next": "..."},
  "page": {
    "size": 20,
    "total_elements": 41839,
    "total_pages": 2092,
    "number": 0
  },
  "near_earth_objects": [
    {
      "id": "2000433",
      "name": "433 Eros (A898 PA)",
      "name_limited": "Eros",
      "nasa_jpl_url": "https://ssd.jpl.nasa.gov/...",
      "absolute_magnitude_h": 10.38,
      "estimated_diameter": {
        "kilometers": {"estimated_diameter_min": 22.31, "estimated_diameter_max": 49.89},
        "meters": {...},
        "miles": {...},
        "feet": {...}
      },
      "is_potentially_hazardous_asteroid": false,
      "close_approach_data": [
        {
          "close_approach_date": "2024-01-15",
          "relative_velocity": {
            "kilometers_per_second": "5.57",
            "kilometers_per_hour": "20083.02",
            "miles_per_hour": "12478.81"
          },
          "miss_distance": {
            "astronomical": "0.314",
            "lunar": "122.5",
            "kilometers": "47112732",
            "miles": "29274494"
          },
          "orbiting_body": "Earth"
        }
      ]
    }
  ]
}

Basic Queries

List Asteroid Names

jpx 'near_earth_objects[*].name' neo_browse.json

Get Short Names

jpx 'near_earth_objects[*].name_limited' neo_browse.json

Output:

["Eros", "Albert", "Alinda", "Ganymed", ...]

Count NEOs in Response

jpx 'page.total_elements' neo_browse.json

Output: 41839 (total known NEOs!)


Filtering

Find Potentially Hazardous Asteroids (PHAs)

jpx 'near_earth_objects[?is_potentially_hazardous_asteroid == `true`].{
  name: name_limited,
  diameter_km: estimated_diameter.kilometers.estimated_diameter_max
}' neo_browse.json

Find Large Asteroids (> 1km)

jpx 'near_earth_objects[?estimated_diameter.kilometers.estimated_diameter_max > `1`].{
  name: name,
  max_diameter_km: estimated_diameter.kilometers.estimated_diameter_max,
  hazardous: is_potentially_hazardous_asteroid
}' neo_browse.json

Find Non-Hazardous Objects

jpx 'near_earth_objects[?is_potentially_hazardous_asteroid == `false`].name_limited' neo_browse.json

Working with Nested Data

Extract Diameter Estimates

The API provides diameters in multiple units:

jpx 'near_earth_objects[*].{
  name: name_limited,
  min_km: estimated_diameter.kilometers.estimated_diameter_min,
  max_km: estimated_diameter.kilometers.estimated_diameter_max
}' neo_browse.json

Calculate Average Diameter

jpx 'near_earth_objects[*].{
  name: name_limited,
  avg_diameter_km: divide(
    add(
      estimated_diameter.kilometers.estimated_diameter_min,
      estimated_diameter.kilometers.estimated_diameter_max
    ),
    `2`
  )
}' neo_browse.json

Get Diameter in Different Units

jpx 'near_earth_objects[0].{
  name: name,
  in_km: estimated_diameter.kilometers,
  in_meters: estimated_diameter.meters,
  in_miles: estimated_diameter.miles
}' neo_browse.json

Close Approach Data

Get Next Close Approach

jpx 'near_earth_objects[*].{
  name: name_limited,
  next_approach: close_approach_data[0].close_approach_date,
  miss_distance_km: close_approach_data[0].miss_distance.kilometers
}' neo_browse.json

Find Closest Approaches (in Lunar Distances)

One lunar distance = 384,400 km (distance to the Moon):

jpx 'near_earth_objects[*].close_approach_data[*].{
  asteroid: @.@.@.name_limited,
  date: close_approach_date,
  lunar_distances: miss_distance.lunar
} | flatten(@)' neo_browse.json

Sort by Miss Distance

jpx 'near_earth_objects[*].{
  name: name_limited,
  miss_km: to_number(close_approach_data[0].miss_distance.kilometers)
} | sort_by(@, &miss_km) | [:10]' neo_browse.json

Find High-Speed Approaches

jpx 'near_earth_objects[*].{
  name: name_limited,
  speed_kph: to_number(close_approach_data[0].relative_velocity.kilometers_per_hour),
  date: close_approach_data[0].close_approach_date
} | [?speed_kph > `50000`]' neo_browse.json

Statistics

Size Statistics

jpx '{
  count: length(near_earth_objects),
  smallest_km: min(near_earth_objects[*].estimated_diameter.kilometers.estimated_diameter_min),
  largest_km: max(near_earth_objects[*].estimated_diameter.kilometers.estimated_diameter_max),
  avg_max_km: round(avg(near_earth_objects[*].estimated_diameter.kilometers.estimated_diameter_max), `2`)
}' neo_browse.json

Hazard Statistics

jpx '{
  total: length(near_earth_objects),
  hazardous: length(near_earth_objects[?is_potentially_hazardous_asteroid == `true`]),
  safe: length(near_earth_objects[?is_potentially_hazardous_asteroid == `false`]),
  hazard_percentage: multiply(
    divide(
      length(near_earth_objects[?is_potentially_hazardous_asteroid == `true`]),
      length(near_earth_objects)
    ),
    `100`
  )
}' neo_browse.json

Magnitude Distribution

Absolute magnitude (H) indicates brightness/size:

jpx '{
  magnitudes: near_earth_objects[*].absolute_magnitude_h,
  min_mag: min(near_earth_objects[*].absolute_magnitude_h),
  max_mag: max(near_earth_objects[*].absolute_magnitude_h),
  avg_mag: round(avg(near_earth_objects[*].absolute_magnitude_h), `2`)
}' neo_browse.json

Sorting and Ranking

Top 5 Largest Asteroids

jpx 'sort_by(near_earth_objects, &estimated_diameter.kilometers.estimated_diameter_max) 
  | reverse(@) 
  | [:5] 
  | [*].{
      name: name,
      max_diameter_km: round(estimated_diameter.kilometers.estimated_diameter_max, `2`),
      hazardous: is_potentially_hazardous_asteroid
    }' neo_browse.json

Brightest Objects (Lowest Magnitude)

jpx 'sort_by(near_earth_objects, &absolute_magnitude_h) | [:5] | [*].{
  name: name_limited,
  magnitude: absolute_magnitude_h,
  diameter_km: estimated_diameter.kilometers.estimated_diameter_max
}' neo_browse.json

Unit Conversions

Convert AU to Kilometers

1 AU (Astronomical Unit) = ~149,597,870.7 km:

jpx 'near_earth_objects[*].{
  name: name_limited,
  miss_au: to_number(close_approach_data[0].miss_distance.astronomical),
  miss_km: to_number(close_approach_data[0].miss_distance.kilometers)
}' neo_browse.json

Velocity Comparisons

jpx 'near_earth_objects[*].{
  name: name_limited,
  km_per_sec: to_number(close_approach_data[0].relative_velocity.kilometers_per_second),
  km_per_hour: to_number(close_approach_data[0].relative_velocity.kilometers_per_hour),
  mph: to_number(close_approach_data[0].relative_velocity.miles_per_hour)
}' neo_browse.json

Data Transformation

Flatten for Analysis

jpx 'near_earth_objects[*].{
  id: id,
  name: name_limited,
  magnitude: absolute_magnitude_h,
  diameter_min_km: estimated_diameter.kilometers.estimated_diameter_min,
  diameter_max_km: estimated_diameter.kilometers.estimated_diameter_max,
  is_hazardous: is_potentially_hazardous_asteroid,
  next_approach_date: close_approach_data[0].close_approach_date,
  miss_distance_km: close_approach_data[0].miss_distance.kilometers,
  velocity_kph: close_approach_data[0].relative_velocity.kilometers_per_hour,
  jpl_url: nasa_jpl_url
}' neo_browse.json

Export to CSV

jpx --csv 'near_earth_objects[*].{
  name: name_limited,
  magnitude: absolute_magnitude_h,
  max_diameter_km: estimated_diameter.kilometers.estimated_diameter_max,
  is_hazardous: is_potentially_hazardous_asteroid,
  approach_date: close_approach_data[0].close_approach_date
}' neo_browse.json

Advanced Pipelines

Risk Assessment Report

jpx '{
  report_title: `Near Earth Object Risk Assessment`,
  total_objects: page.total_elements,
  sample_size: length(near_earth_objects),
  potentially_hazardous: {
    count: length(near_earth_objects[?is_potentially_hazardous_asteroid == `true`]),
    objects: near_earth_objects[?is_potentially_hazardous_asteroid == `true`] | [*].{
      name: name,
      diameter_km: round(estimated_diameter.kilometers.estimated_diameter_max, `2`),
      next_approach: close_approach_data[0].close_approach_date,
      miss_lunar: close_approach_data[0].miss_distance.lunar
    }
  },
  largest_objects: sort_by(near_earth_objects, &estimated_diameter.kilometers.estimated_diameter_max) 
    | reverse(@) 
    | [:3] 
    | [*].{name: name_limited, km: round(estimated_diameter.kilometers.estimated_diameter_max, `1`)}
}' neo_browse.json

Pipeline: Filter, Sort, Transform

jpx 'near_earth_objects
  | [?estimated_diameter.kilometers.estimated_diameter_max > `0.5`]
  | [?is_potentially_hazardous_asteroid == `true`]
  | sort_by(@, &estimated_diameter.kilometers.estimated_diameter_max)
  | reverse(@)
  | [*].{
      name: name,
      size_category: `LARGE PHA`,
      diameter_range: concat(
        to_string(round(estimated_diameter.kilometers.estimated_diameter_min, `1`)),
        ` - `,
        to_string(round(estimated_diameter.kilometers.estimated_diameter_max, `1`)),
        ` km`
      )
    }' neo_browse.json

Working with the Feed Endpoint

The feed endpoint returns NEOs by date:

curl -s "https://api.nasa.gov/neo/rest/v1/feed?start_date=2024-01-15&end_date=2024-01-16&api_key=DEMO_KEY" > neo_day.json

Count by Date

jpx 'element_count' neo_day.json

List All Dates with Objects

jpx 'keys(near_earth_objects)' neo_day.json

Get Objects for Specific Date

jpx 'near_earth_objects."2024-01-15"[*].name' neo_day.json

Try It Yourself

  1. Get your free API key: api.nasa.gov

  2. Interesting queries to try:

    • Find the closest approach ever recorded
    • List all PHAs larger than 500 meters
    • Calculate average velocity of approaching objects
  3. API endpoints:

    • /neo/browse - Paginated list of all NEOs
    • /neo/rest/v1/feed - NEOs by approach date
    • /neo/{id} - Specific asteroid details
  4. API Documentation: NASA NEO API

Project Management Dataset

A comprehensive example using synthetic project management data that demonstrates most jpx function categories.

Dataset

All examples use this project management dataset:

[
  {
    "projectId": "P-8801",
    "status": "active",
    "timestamps": { "created_at": "2025-01-15T08:30:00Z", "last_modified": "2025-12-19T09:05:00Z" },
    "details": {
      "name": "Quantum Migration",
      "description": "High-priority initiative to transition legacy on-premise infrastructure to a distributed cloud-native architecture.",
      "tags": ["cloud", "security", "infrastructure"]
    },
    "milestones": [{ "phase": "Discovery", "due_date": "2025-03-01", "completed": true }]
  },
  {
    "projectId": "P-9902",
    "status": "pending",
    "timestamps": { "created_at": "2025-06-10T14:20:00Z", "last_modified": "2025-11-30T11:00:00Z" },
    "details": {
      "name": "Mobile App Redesign",
      "description": "Revamping the user interface for the primary customer-facing mobile application to improve accessibility and engagement.",
      "tags": ["UX", "frontend", "accessibility"]
    },
    "milestones": []
  },
  {
    "projectId": "P-4405",
    "status": "archived",
    "timestamps": { "created_at": "2024-11-01T09:00:00Z", "last_modified": "2025-01-20T16:30:00Z" },
    "details": {
      "name": "Legacy Data Cleanup",
      "description": "Automated purging of obsolete database records older than seven years to maintain compliance with regional data privacy laws.",
      "tags": ["database", "compliance", "automation"]
    },
    "milestones": [{ "phase": "Archive Audit", "due_date": "2025-01-15", "completed": true }]
  },
  {
    "projectId": "P-5521",
    "status": "active",
    "timestamps": { "created_at": "2025-02-14T10:15:00Z", "last_modified": "2025-12-10T14:45:00Z" },
    "details": {
      "name": "Cyber Sentinel Firewall",
      "description": "Implementation of AI-driven threat detection systems to safeguard corporate networks against zero-day vulnerabilities.",
      "tags": ["AI", "security", "network"]
    },
    "milestones": [{ "phase": "Training", "due_date": "2025-05-20", "completed": true }]
  },
  {
    "projectId": "P-1033",
    "status": "on-hold",
    "timestamps": { "created_at": "2025-03-22T13:00:00Z", "last_modified": "2025-09-05T08:20:00Z" },
    "details": {
      "name": "Blockchain Supply Chain",
      "description": "Integrating distributed ledger technology to provide end-to-end transparency for global logistics and manufacturing pipelines.",
      "tags": ["blockchain", "logistics", "transparency"]
    },
    "milestones": []
  },
  {
    "projectId": "P-2294",
    "status": "active",
    "timestamps": { "created_at": "2025-04-12T16:45:00Z", "last_modified": "2025-12-18T10:00:00Z" },
    "details": {
      "name": "Green Energy Analytics",
      "description": "Analyzing power consumption patterns to optimize the efficiency of solar and wind-based energy grids.",
      "tags": ["sustainability", "analytics", "energy"]
    },
    "milestones": [{ "phase": "Sensor Install", "due_date": "2025-08-30", "completed": true }]
  },
  {
    "projectId": "P-7761",
    "status": "pending",
    "timestamps": { "created_at": "2025-05-19T11:20:00Z", "last_modified": "2025-05-19T11:20:00Z" },
    "details": {
      "name": "Internal Wiki Overhaul",
      "description": "Restructuring the company knowledge base to facilitate better cross-departmental collaboration and documentation sharing.",
      "tags": ["knowledge", "internal", "documentation"]
    },
    "milestones": []
  },
  {
    "projectId": "P-6610",
    "status": "active",
    "timestamps": { "created_at": "2025-07-04T09:00:00Z", "last_modified": "2025-12-15T15:30:00Z" },
    "details": {
      "name": "E-Commerce Personalization",
      "description": "Deploying machine learning models to provide customized product recommendations based on real-time user behavior.",
      "tags": ["retail", "ML", "personalization"]
    },
    "milestones": [{ "phase": "Model Alpha", "due_date": "2025-11-01", "completed": true }]
  },
  {
    "projectId": "P-3349",
    "status": "archived",
    "timestamps": { "created_at": "2024-08-15T14:10:00Z", "last_modified": "2025-03-12T12:00:00Z" },
    "details": {
      "name": "Legacy Email Migration",
      "description": "Relocating historical email archives from physical tape drives to a searchable secure cloud vault.",
      "tags": ["email", "storage", "archival"]
    },
    "milestones": [{ "phase": "Final Sync", "due_date": "2025-02-28", "completed": true }]
  },
  {
    "projectId": "P-1122",
    "status": "active",
    "timestamps": { "created_at": "2025-09-30T08:45:00Z", "last_modified": "2025-12-19T11:15:00Z" },
    "details": {
      "name": "HR Portal Integration",
      "description": "Syncing payroll and performance management software to create a unified dashboard for human resources staff.",
      "tags": ["HR", "integration", "software"]
    },
    "milestones": [{ "phase": "API Bridge", "due_date": "2026-01-10", "completed": false }]
  }
]

Basic Extraction

Get all values for a field

# All project IDs
[*].projectId
["P-8801", "P-9902", "P-4405", "P-5521", "P-1033", "P-2294", "P-7761", "P-6610", "P-3349", "P-1122"]
# All project names
[*].details.name
["Quantum Migration", "Mobile App Redesign", "Legacy Data Cleanup", "Cyber Sentinel Firewall", "Blockchain Supply Chain", "Green Energy Analytics", "Internal Wiki Overhaul", "E-Commerce Personalization", "Legacy Email Migration", "HR Portal Integration"]

Access by index

# First project's name
[0].details.name
"Quantum Migration"

Filtering

Simple equality filter

# Active projects only
[?status == 'active'].details.name
["Quantum Migration", "Cyber Sentinel Firewall", "Green Energy Analytics", "E-Commerce Personalization", "HR Portal Integration"]

Numeric comparison

# Projects with milestones
[?length(milestones) > `0`].projectId
["P-8801", "P-4405", "P-5521", "P-2294", "P-6610", "P-3349", "P-1122"]

Empty array check

# Projects with no milestones
[?milestones == `[]`].details.name
["Mobile App Redesign", "Blockchain Supply Chain", "Internal Wiki Overhaul"]

Nested filtering

# All incomplete milestones across all projects
[*].milestones[?completed == `false`][]
[{"phase": "API Bridge", "due_date": "2026-01-10", "completed": false}]

Flattening & Nested Access

Flatten nested arrays

# All tags across all projects (flattened)
[*].details.tags[]
["cloud", "security", "infrastructure", "UX", "frontend", "accessibility", "database", "compliance", "automation", "AI", "security", "network", "blockchain", "logistics", "transparency", "sustainability", "analytics", "energy", "knowledge", "internal", "documentation", "retail", "ML", "personalization", "email", "storage", "archival", "HR", "integration", "software"]

Get unique values

# Unique tags (deduplicated and sorted)
unique([*].details.tags[]) | sort(@)
["AI", "HR", "ML", "UX", "accessibility", "analytics", "archival", "automation", "blockchain", "cloud", "compliance", "database", "documentation", "email", "energy", "frontend", "infrastructure", "integration", "internal", "knowledge", "logistics", "network", "personalization", "retail", "security", "software", "storage", "sustainability", "transparency"]

Status & Grouping

Count by category

# Count projects by status
frequencies([*].status)
{"active": 5, "pending": 2, "archived": 2, "on-hold": 1}

Group into buckets

# Group projects by status
group_by(@, 'status') | keys(@)
["active", "archived", "on-hold", "pending"]

Tag Operations

Tag frequency analysis

# How often each tag appears
frequencies([*].details.tags[])
{"security": 2, "cloud": 1, "infrastructure": 1, "UX": 1, "frontend": 1, ...}

Find projects by tag

# Projects with "security" tag
[?includes(details.tags, 'security')].{id: projectId, name: details.name}
[
  {"id": "P-8801", "name": "Quantum Migration"},
  {"id": "P-5521", "name": "Cyber Sentinel Firewall"}
]

Multiple tag filter (AND)

# Projects with BOTH security AND cloud tags
[?includes(details.tags, 'security') && includes(details.tags, 'cloud')].projectId
["P-8801"]

Tags as comma-separated string

# All unique tags as one string
join(', ', unique([*].details.tags[]) | sort(@))
"AI, HR, ML, UX, accessibility, analytics, archival, automation, blockchain, cloud, compliance, database, documentation, email, energy, frontend, infrastructure, integration, internal, knowledge, logistics, network, personalization, retail, security, software, storage, sustainability, transparency"

Date & Time Queries

Filter by date prefix

# Projects created in 2025
[?starts_with(timestamps.created_at, '2025')].projectId
["P-8801", "P-9902", "P-5521", "P-1033", "P-2294", "P-7761", "P-6610", "P-1122"]

Find most recent

# Most recently modified project
max_by(@, &timestamps.last_modified).details.name
"HR Portal Integration"

Find oldest

# Oldest project by creation date
min_by(@, &timestamps.created_at).details.name
"Legacy Email Migration"

Human-readable time ago

# Time since last modification (human readable)
[*].{name: details.name, last_touch: time_ago(timestamps.last_modified)}
[
  {"name": "Quantum Migration", "last_touch": "1 hour ago"},
  {"name": "Mobile App Redesign", "last_touch": "19 days ago"},
  ...
]

Stale project detection

# Projects not modified in 30+ days
[?date_diff(now(), to_epoch(timestamps.last_modified), 'days') > `30`].details.name
["Mobile App Redesign", "Blockchain Supply Chain", "Internal Wiki Overhaul", "Legacy Email Migration"]

Same-day check

# Projects modified today
[?is_same_day(timestamps.last_modified, from_epoch(now()))].details.name
["Quantum Migration", "HR Portal Integration"]

Milestone Analysis

Projects without milestones

# Projects with no milestones defined
[?milestones == `[]`].{id: projectId, name: details.name, status: status}
[
  {"id": "P-9902", "name": "Mobile App Redesign", "status": "pending"},
  {"id": "P-1033", "name": "Blockchain Supply Chain", "status": "on-hold"},
  {"id": "P-7761", "name": "Internal Wiki Overhaul", "status": "pending"}
]

Find incomplete work

# Projects with pending milestones
[?milestones[?completed == `false`]].{
  project: details.name,
  pending: milestones[?completed == `false`][].phase
}
[{"project": "HR Portal Integration", "pending": ["API Bridge"]}]

Upcoming milestones

# All incomplete milestones with due dates
[*].milestones[?completed == `false`][].{phase: phase, due: due_date}
[{"phase": "API Bridge", "due": "2026-01-10"}]

# Fuzzy match project names
[?fuzzy_match(details.name, 'energy')].details.name
["Green Energy Analytics"]

Keyword search in descriptions

# Projects mentioning AI or machine learning
[?contains(details.description, 'AI') || contains(details.description, 'machine learning')].details.name
["Cyber Sentinel Firewall", "E-Commerce Personalization"]
# Projects mentioning compliance (case-insensitive)
[?contains(lower(details.description), 'compliance')].details.name
["Legacy Data Cleanup"]

Word analysis

# Word count in descriptions
[*].{name: details.name, words: length(words(details.description))}
[
  {"name": "Quantum Migration", "words": 14},
  {"name": "Mobile App Redesign", "words": 16},
  ...
]

Truncated summaries

# Shortened descriptions
[*].{name: details.name, summary: truncate(details.description, `50`)}
[
  {"name": "Quantum Migration", "summary": "High-priority initiative to transition legacy..."},
  ...
]

String Transformations

URL-friendly slugs

# Kebab-case project names for URLs
[*].{id: projectId, slug: kebab_case(details.name)}
[
  {"id": "P-8801", "slug": "quantum-migration"},
  {"id": "P-9902", "slug": "mobile-app-redesign"},
  ...
]

Database column names

# Snake-case for database columns
[*].details.name | map(&snake_case(@), @)
["quantum_migration", "mobile_app_redesign", "legacy_data_cleanup", ...]

Uppercase status

# Uppercase status field
[*].{name: details.name, status: upper(status)}
[
  {"name": "Quantum Migration", "status": "ACTIVE"},
  ...
]

Projections & Reshaping

Create summary objects

# Clean summary for each project
[*].{id: projectId, name: details.name, status: status}
[
  {"id": "P-8801", "name": "Quantum Migration", "status": "active"},
  {"id": "P-9902", "name": "Mobile App Redesign", "status": "pending"},
  ...
]

Computed fields

# Add computed milestone count
[*].{
  name: details.name,
  status: status,
  milestone_count: length(milestones),
  has_incomplete: length(milestones[?completed == `false`]) > `0`
}
[
  {"name": "Quantum Migration", "status": "active", "milestone_count": 1, "has_incomplete": false},
  {"name": "HR Portal Integration", "status": "active", "milestone_count": 1, "has_incomplete": true},
  ...
]

Aggregation & Analytics

Dashboard summary

# Executive dashboard
{
  total_projects: length(@),
  active: length([?status == 'active']),
  pending: length([?status == 'pending']),
  on_hold: length([?status == 'on-hold']),
  archived: length([?status == 'archived']),
  with_milestones: length([?milestones != `[]`]),
  without_milestones: length([?milestones == `[]`]),
  incomplete_milestones: length([*].milestones[?completed == `false`][]),
  newest_project: max_by(@, &timestamps.created_at).details.name,
  oldest_project: min_by(@, &timestamps.created_at).details.name,
  unique_tags: length(unique([*].details.tags[]))
}
{
  "total_projects": 10,
  "active": 5,
  "pending": 2,
  "on_hold": 1,
  "archived": 2,
  "with_milestones": 7,
  "without_milestones": 3,
  "incomplete_milestones": 1,
  "newest_project": "HR Portal Integration",
  "oldest_project": "Legacy Email Migration",
  "unique_tags": 29
}

Complex Filters

Multiple conditions

# Active projects with completed milestones, modified this month
[?status == 'active'
  && milestones[?completed == `true`]
  && starts_with(timestamps.last_modified, '2025-12')
].details.name
["Quantum Migration", "Cyber Sentinel Firewall", "Green Energy Analytics", "E-Commerce Personalization", "HR Portal Integration"]

Stale pending projects

# Pending projects with no recent activity (60+ days)
[?status == 'pending'
  && date_diff(now(), to_epoch(timestamps.last_modified), 'days') > `60`
].details.name
["Internal Wiki Overhaul"]

At-risk detection

# Active projects without any milestones (potential risk)
[?status == 'active' && milestones == `[]`].details.name
[]

Export-Ready Formats

CSV row generation

# Generate CSV rows
[*] | map(&to_csv([projectId, status, details.name]), @)
[
  "P-8801,active,Quantum Migration",
  "P-9902,pending,Mobile App Redesign",
  ...
]

Bullet list for reports

# Active project names as bullet list
[?status == 'active'].details.name | join('\n- ', @)
"Quantum Migration\n- Cyber Sentinel Firewall\n- Green Energy Analytics\n- E-Commerce Personalization\n- HR Portal Integration"

Full export with metadata

# Rich export format
[*].{
  id: projectId,
  title: details.name,
  status: upper(status),
  tags: join(', ', details.tags),
  age_days: date_diff(now(), to_epoch(timestamps.created_at), 'days'),
  last_touch: time_ago(timestamps.last_modified),
  slug: kebab_case(details.name)
}
[
  {
    "id": "P-8801",
    "title": "Quantum Migration",
    "status": "ACTIVE",
    "tags": "cloud, security, infrastructure",
    "age_days": 338,
    "last_touch": "1 hour ago",
    "slug": "quantum-migration"
  },
  ...
]

Function Categories Used

This guide demonstrates functions from these categories:

CategoryFunctions Used
Arrayfirst, last, unique, length, frequencies, group_by
Stringupper, lower, join, split, starts_with, contains, truncate, kebab_case, snake_case, words
DateTimenow, to_epoch, from_epoch, time_ago, date_diff, is_same_day
Expressionmap, filter_expr, max_by, min_by
Utilityincludes, default
Fuzzyfuzzy_match
Formatto_csv

Try It Yourself

Save the sample dataset to a file and experiment:

# Save the JSON to a file
cat > projects.json << 'EOF'
[ ... paste the sample data ... ]
EOF

# Run queries with jpx
cat projects.json | jpx '[?status == '\''active'\''].details.name'
cat projects.json | jpx 'frequencies([*].status)'
cat projects.json | jpx '[*].{name: details.name, tags: join('\'', '\'', details.tags)}'

Or use the interactive REPL:

jpx --repl < projects.json