Skip to content
jsonbeautifiers
English

Filter JSON

Pull out only the parts you need with a JSONPath filter expression.

Document
Result

Nothing you paste leaves your browser. The connect-src allowlist makes that a browser guarantee rather than a promise. Check it yourself

Cut a large document down to the part you need, using a JSONPath filter expression. The result comes back as a JSON array of the matching values, ready to copy.

To be clear about what this is: it is JSONPath, not jq. jq is a full language and a genuinely better tool for complex transformation. This is for selection, which is what most filtering actually is.

Filter expressions

The whole syntax in one place, since this is the part people look up every time.

$.items[?@.active]
Every item where the key exists and is truthy. Existence alone is a valid test.
$.items[?@.price < 10]
Numeric comparison. Ordering only applies between two numbers or two strings.
$.items[?@.type == 'book']
String comparison. The literal must be quoted, which is the commonest mistake.
$.items[?@.price < 10 && @.stock > 0]
Conjunction with && and disjunction with ||, and grouping with parentheses.
$.items[?!@.archived]
Negation, which here means the key is absent or false.
$.items[?length(@.tags) > 2]
A function extension. length() works on strings, arrays and objects.
$.items[?match(@.sku, '[A-Z]{3}-[0-9]+')]
A regular expression anchored to the whole value. Use search() for a substring match.
$..[?@.id == 42]
A filter applied at every depth, which is how you find a record without knowing where it lives.

What happens to a missing key

This is where pre-RFC implementations disagreed most, so it is worth stating. In RFC 9535 a query that selects nothing is "missing", and a missing value equals only another missing value. So @.price < 10 is false when price is absent, rather than throwing or matching. Both branches of an == comparison must be missing for it to be true.

The practical consequence: to test for absence use !@.price rather than @.price == null, because null is a value and absence is not.

How to do this in code

Filtering in code, where jq usually is the right answer.

sh jq

The third example is the line that decides it: if you need grouping or aggregation, use jq.

# Select, then reshape
jq '[.items[] | select(.price < 10) | {sku, price}]' data.json

# Filter at any depth
jq '[.. | objects | select(.id? == 42)]' data.json

# Group and aggregate, which JSONPath cannot do at all
jq 'group_by(.category) | map({category: .[0].category, n: length})' data.json
py Python
# A comprehension beats a query language for anything you
# can express directly.
cheap = [i for i in data['items'] if i['price'] < 10]

# JMESPath when the filter is configuration rather than code
import jmespath
cheap = jmespath.search("items[?price < `10`]", data)
js JavaScript
const cheap = data.items.filter((i) => i.price < 10);

// Deep search without a library
function findAll(node, test, out = []) {
  if (node && typeof node === 'object') {
    if (test(node)) out.push(node);
    for (const v of Object.values(node)) findAll(v, test, out);
  }
  return out;
}
const matches = findAll(data, (n) => n.id === 42);

Questions

Why is this not a jq playground?
Running real jq in a browser means shipping it compiled to WebAssembly, which is around a megabyte. On a site whose pitch is that it loads fast, that is a bad trade for a tool most people use to select rather than to transform. Calling a JSONPath filter a jq playground would also be a lie, and this site does not do that.
Can I filter and reshape at the same time?
Not with JSONPath: it selects nodes, it does not build new ones. Use JMESPath, which has multiselect hashes, or jq.
How do I find every object with a given key anywhere in the document?
$..[?@.theKey] applies the filter at every depth. To find a specific record, $..[?@.id == 42].