JSON Query Language Specification
A lightweight, JavaScript-native syntax for deep traversal, mutation, filtering, and
aggregation of JSON
data. Queries are evaluated strictly left-to-right.
Core Philosophy: Path extraction happens first, followed by filtering
{condition}
, projection/shaping => {shape}, and finally modifiers
:modifier
. Weak typing applies implicitly (e.g., "3" <= 3 evaluates to
true).
1. Data Context Reference
All examples in this documentation are executed against the following JSON dataset:
{
"total_count": 3,
"users": [
{
"id": 101, "name": "Alice Vance", "age": 28, "str_id": "3", "is_admin": true,
"tags": ["frontend", "ux", "mentor"], "address": {"city": "Berlin", "country": "Germany"}, "deleted_at": null
},
{
"id": 102, "name": "Bob Smith", "age": 34, "is_admin": false,
"tags": ["backend", "devops"], "secret_identity": "Bernd Schmitt",
"address": {"city": "San Francisco", "country": "USA"}, "deleted_at": "2025-12-01"
},
{
"id": 103, "name": "Charlie Day", "age": 40, "is_admin": false,
"tags": ["manager"], "address": {"city": "Berlin", "country": "Germany"}, "deleted_at": null
}
],
"active_status": true,
"metadata": { "version": 1.2 }
}
2. Basic Extraction & Traversal
Use dot notation (or implicit paths) to extract nodes. If applied to an array, the
language automatically
maps the path extraction across all items in the array.
| Query String |
Result / Explanation |
active_status
|
true
Fetches root-level primitives.
|
metadata.version
|
1.2
Traverses objects.
|
users.name
|
["Alice Vance", "Bob Smith", "Charlie Day"]
Auto-maps extraction
across arrays.
|
users.secret_identity
|
[undefined, "Bernd Schmitt", undefined]
Missing properties safely
return native undefined.
|
users[0].name
|
Alice Vance
You can access array elements by index.
|
users[-1].name[0]
|
C
Use -1 for getting the last element. Strings can also be indexed.
|
users[0:2].name
|
["Alice Vance", "Bob Smith"]
Python-style slicing. Returns elements from start (inclusive) to end
(exclusive).
|
users[1:]
users[:-1]
users[-2:]
|
Open slices. Omit start or end to slice from the beginning or to the end.
Negative indices count from the end.
|
users[-1].name[0:3]
|
Cha
Slicing works on strings too.
|
3. Filtering Data
Encapsulate conditions inside braces {}. The syntax maps closely to JS
operators but
introduces cleaner query aliases.
-
Supported logic:
and
, or
, not.
-
Supported comparisons:
==
, !=
, <,
>
, <=
, >=.
-
Special operators:
has field
, in
, contains,
=~
(Regex match), !~ (Negated regex).
| Query String |
Result / Explanation |
users.{address.city == 'Berlin'}.name
users{address.city == 'Berlin'}name
|
["Alice Vance", "Charlie Day"]
Filters array based on deep
boolean evaluations. Note that the . before and after braces is
optional.
|
users.{age < 40 and not is_admin}.name
|
["Bob Smith"]
Compound logic operators.
|
users.{deleted_at == null}.id
|
[101, 103]
Null comparisons.
|
users.{has secret_identity}.name
users.{secret_identity != undefined}.name
|
["Bob Smith"]
has verifies property existence.
Standard undefined checks also work.
|
users.{'ux' in tags}.id
users.{tags contains "ux"}.id
|
[101]
in
and contains act as safe
array-inclusion lookups. Both syntaxes act as identical synonyms.
|
users.{str_id <= 3}.name
|
["Alice Vance"]
Demonstrates implicit JS weak-typing. String "3"
is evaluated successfully against Integer 3.
|
users.{name =~ /^B/}.id
|
[102]
The =~ operator invokes native JavaScript
RegExp pattern matching.
|
users.{name !~ /^A/}.name
|
["Bob Smith", "Charlie Day"]
The !~ operator negates the
regex match. Supports flags like /pattern/i.
|
users{tags:len < 3}.id
|
[102, 103]
You can also use modifiers in conditions.
|
4. Projection & Shape Mutation
Use the fat arrow => {} to remap existing objects into entirely new
structures. You can
map variables directly, inject hardcoded strings, or use spread syntax to control field
inheritance.
| Query String |
Result / Explanation |
=> {last_updated, meta: metadata}
|
{
"last_updated": "2026-02-16T18:00:00Z",
"meta": {
"version": 1.2,
"environment": "production"
}
}
Project whole input to a new structure with only last_updated and metadata
(renaming metadata to
meta).
|
users => {id: id}
users => {id}
|
[{"id": 101}, {"id": 102}, {"id": 103}]
Standard key/value
mapping.
|
users => {name: "test"}
|
[{"name": "test"}, {"name": "test"}, {"name": "test"}]
Supports
injection of hardcoded literals.
|
users.{age < 40} => {first_name: name, location:
address.city}
|
[
{"first_name": "Alice Vance", "location": "Berlin"},
{"first_name": "Bob Smith", "location": "San Francisco"}
]
Shape mapping with nested resolution.
|
users.{id == 101} => {first_name: name, ... but name, is_admin,
address}
|
[
{
"first_name": "Alice Vance",
"id": 101,
"age": 28,
"str_id": "3",
"tags": ["frontend", "ux", "mentor"],
"deleted_at": null
}
]
Use ... to spread the original object properties. Use
... but field, field to blacklist specific fields from the spread
operation.
|
5. Array Manipulation
Modifiers (prefixed by :) alter structural boundaries, limits, and array
definitions.
| Query String |
Result / Explanation |
users.tags
|
[["frontend", "ux", "mentor"], ["backend", "devops"], ["manager"]]
Standard
traversal maintains 2D structural boundaries.
|
users.tags:flat
|
["frontend", "ux", "mentor", "backend", "devops", "manager"]
:flat destroys 1 level of inner-array boundaries.
|
users.address.city:unique
|
["Berlin", "San Francisco"]
Deduplicates elements.
|
users.tags:flat:join(', ')
|
"frontend, ux, backend, devops, manager"
Chains modifiers to
collapse an array into a single concatenated string.
|
users:limit(1).name
|
["Alice Vance"]
Truncates arrays.
|
users:len
users.name:len
|
3
Returns counts of arrays
|
users[0]:len
|
8
... or count of object keys.
|
users:reverse.name
|
["Charlie Day", "Bob Smith", "Alice Vance"]
:reverse reverses array order without mutating.
|
6. Aggregations, Grouping & Sorting
| Query String |
Result / Explanation |
users.age:avg
|
34
|
users.age:max
|
40
|
users.age:sum
|
102
Math modifiers apply calculations against evaluated arrays.
|
users:sort(age, desc).name
|
["Charlie Day", "Bob Smith", "Alice Vance"]
Accepts a sort
target
and an optional direction (asc
/ desc).
|
users:group_by(address.country)
|
{
"Germany": [
{"id": 101, "name": "Alice Vance", ...},
{"id": 103, "name": "Charlie Day", ...}
],
"USA": [
{"id": 102, "name": "Bob Smith", ...}
]
}
Pivots flat arrays into key-value objects based on deep-object values.
|
users => {name, city: address.city}:group_by(city)
|
{
"Berlin": [
{
"name": "Alice Vance",
"city": "Berlin"
},
{
"name": "Charlie Day",
"city": "Berlin"
}
],
"San Francisco": [
{
"name": "Bob Smith",
"city": "San Francisco"
}
]
}
First project the users to a new structure with only name and city, and then
group by
city.
|
users:count_by(address.country)
|
{"Germany": 2, "USA": 1}
Like :group_by, but returns counts per group instead of arrays.
|
users:group_by(address.country):keys
|
["Germany", "USA"]
:keys
extracts object keys as an array. Useful after :group_by.
|
users:group_by(address.country):values
|
Returns the grouped arrays. :values extracts object values as an
array.
|
users:group_by(address.country):entries
|
[{"key": "Germany", "value": [...]}, {"key": "USA", "value": [...]}]
:entries
converts an object into an array of {key, value} pairs,
bridging object results back into array processing.
|
7. String Modifiers
String modifiers transform text values. When applied to an array, they auto-map over each
element.
| Query String |
Result / Explanation |
users.name:upper
|
["ALICE VANCE", "BOB SMITH", "CHARLIE DAY"]
Converts strings to uppercase.
|
users.name:lower
|
["alice vance", "bob smith", "charlie day"]
Converts strings to lowercase.
|
name:lower:trim
|
Modifiers can be chained. :trim removes leading and trailing
whitespace.
|
users.{name:upper == 'BOB SMITH'}.id
|
[102]
String modifiers also work inline within filters.
|
8. Composition (Multi-Queries)
| Query String |
Result / Explanation |
{total: users:len, hired: users.{tags contains 'ux'}.name}
|
{
"total": 3,
"hired": [
"Alice Vance"
]
}
Each value of the result object is computed as a separate query.
|
{total: `users`:len, "hired users": `users`.{tags contains
'ux'}.name}
|
{
"total": 3,
"hired users": [
"Alice Vance"
]
}
You can use string syntax (double or single quotes) for aliases to include
spaces and special
characters. Arbitrary JSON field names that include special characters or spaces
can be wrapped
in backticks.
|
{`all tags`: users.tags:flat, users: `users`.{`tags`:len > 1} =>
{`user:id` : id}}
|
{
"all tags": [
"frontend",
"ux",
"mentor",
"backend",
"devops",
"manager"
],
"users": [
{
"user:id": 101
},
{
"user:id": 102
}
]
}
|