Introduction
Filters are the most powerful feature of JSONPath. They allow you to select JSON elements based on conditions — comparing values, matching strings, checking existence, and combining logic with AND/OR. Filters transform JSONPath from simple navigation into a full query language.
Key Concepts
- Filter syntax
? (condition): Applies a boolean condition to filter elements. Only elements where the condition is true are returned. @(current item): Inside a filter,@refers to the element currently being evaluated.- Comparison operators:
==,!=,<,<=,>,>=for value comparisons. - Logical operators:
&&(and),||(or),!(not) for combining conditions. like_regex: A string matching operator that supports regular expressions within filters.
Real World Context
Filter expressions are essential for queries like "find all orders with a total above $100" or "find all users whose email matches a domain pattern." Without filters, you would need to expand arrays to rows, filter with WHERE, and re-aggregate — a verbose process. JSONPath filters do this in a single expression, often within a GIN-indexed @? check.
Deep Dive
Filter Syntax
Filters use the ? (condition) syntax after a path element:
sql-- Filter syntax: ? (condition) SELECT jsonb_path_query_array( '[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]'::jsonb, '$[*] ? (@.age > 27)' ); -- Result: [{"age": 30, "name": "Alice"}]
The @ symbol refers to the current item being filtered. In $[*] ? (@.age > 27), each array element is tested, and only those with age > 27 are returned.
Common Filter Patterns
Here are the patterns you will use most often:
sql-- Equality filter SELECT jsonb_path_query_array( '[{"status": "active"}, {"status": "inactive"}]'::jsonb, '$[*] ? (@.status == "active")' ); -- Multiple conditions with && (and) SELECT jsonb_path_query_array( '[{"name": "Alice", "age": 30, "active": true}, {"name": "Bob", "age": 25, "active": true}]'::jsonb, '$[*] ? (@.age >= 25 && @.active == true)' ); -- OR conditions with || SELECT jsonb_path_query_array( '[{"role": "admin"}, {"role": "user"}, {"role": "moderator"}]'::jsonb, '$[*] ? (@.role == "admin" || @.role == "moderator")' ); -- Negation with ! SELECT jsonb_path_query_array( '[{"deleted": true}, {"deleted": false}]'::jsonb, '$[*] ? (!(@.deleted == true))' );
These patterns combine to express complex filter logic concisely.
String Operations in Filters
JSONPath supports string-specific operations:
sql-- starts with SELECT jsonb_path_query_array( '[{"email": "alice@company.com"}, {"email": "bob@external.org"}]'::jsonb, '$[*] ? (@.email starts with "alice")' ); -- like_regex for pattern matching SELECT jsonb_path_query_array( '[{"email": "alice@company.com"}, {"email": "bob@company.com"}]'::jsonb, '$[*] ? (@.email like_regex "@company\\.com$")' ); -- Case-insensitive regex SELECT jsonb_path_query_array( '[{"name": "ALICE"}, {"name": "bob"}]'::jsonb, '$[*] ? (@.name like_regex "alice" flag "i")' );
The like_regex operator uses POSIX regular expressions and supports flags like "i" for case-insensitive matching.
exists() Filter Function
The exists() function checks whether a nested path exists:
sql-- Check if nested path exists SELECT jsonb_path_query_array( '[{"user": {"email": "a@b.com"}}, {"user": {}}]'::jsonb, '$[*] ? (exists (@.user.email))' ); -- Only returns objects where user.email exists
This is useful for filtering documents that have optional nested fields.
Common Pitfalls
- Forgetting to double-escape backslashes in regex — In SQL string literals, you need
\\\\for a literal backslash in like_regex. Use dollar-quoted strings to reduce escaping. - Using = instead of == — JSONPath uses
==for equality comparison, not=. Using=causes a syntax error. - Comparing different types without casting —
@.age > "25"compares a number to a string and may not behave as expected.
Best Practices
- Combine filters with @? for indexed lookups —
WHERE column @? '$.items[*] ? (@.price > 100)'leverages GIN indexes. - Use exists() for optional field checks — It is more reliable than comparing to null.
- Keep filters simple — Complex multi-condition filters can be hard to debug. Break them into separate checks when troubleshooting.
Summary
- Filters use
? (condition)syntax with@referring to the current element. - Comparison operators (
==,!=,<,>, etc.) and logical operators (&&,||,!) combine for complex conditions. starts withandlike_regexprovide string-specific filtering.exists()checks for the presence of nested paths.- Filters work with
@?and@@operators, which are GIN-indexable.
Code Examples
-- Filter array elements by condition
SELECT jsonb_path_query_array(
'[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]'::jsonb,
'$[*] ? (@.age > 27)'
);
-- Result: [{"age": 30, "name": "Alice"}]
-- Multiple conditions with &&
SELECT jsonb_path_query_array(
'[{"role": "admin", "active": true}, {"role": "user", "active": false}]'::jsonb,
'$[*] ? (@.role == "admin" && @.active == true)'
);
-- Regex matching with like_regex
SELECT jsonb_path_query_array(
'[{"email": "alice@company.com"}, {"email": "bob@external.org"}]'::jsonb,
'$[*] ? (@.email like_regex "@company\\.com$")'
);