Selecting and Navigating Data
Selectors pull data out of the payload and any other value. You write a selector as a starting value followed by one or more steps, and the chain returns the value at the end of the path. The key behavior to understand is that a path pointing at something that isn't there yields null instead of raising an error. This is what lets you point selectors at messy, real-world documents without writing defensive checks at every step.
Field Selection
payload.customer.name // nested field access
payload."order id" // quoted key (spaces, punctuation)
payload.(keyExpr) // dynamic key — keyExpr is evaluated to a string
A missing key returns null. Keys are matched by string first and then by symbol, so it does not matter whether the payload was deserialized with string or symbol keys.
Multi-Value Field Selection — .*
.*name collects every matching field rather than the first, and returns an array:
payload.*book // all `book` children, as an array
payload.orders.*total // every `total` under orders
Applied to an array of objects, ordinary .field already maps across the elements and flattens the results. Use .*field when you want the array form even for a single match.
Descendant Selection — ..
..name searches recursively at any depth, and .. alone collects all descendants:
payload..price // every `price` anywhere in the tree
payload.. // every descendant node
Index and Range Selection
payload.items[0] // first element
payload.items[-1] // last element (negative counts from the end)
payload.items[1 to 3] // inclusive slice, elements 1..3
payload.items[-2 to -1] // slice with negative bounds
Indexing works on arrays only and is bounds-safe: an out-of-range index returns null rather than raising. Negative indices count from the end. Ranges use the to keyword (there is no 1..3 or [1:3] form), are inclusive on both ends, and clamp or return empty for degenerate bounds. A range against a value that is not an array raises Range selectors require array targets.
Filtering — [?( … )]
A filter keeps only the entries whose predicate is truthy. Inside the predicate, @ refers to the current element:
payload.items[?(@ > 100)] // array → elements over 100
payload.orders[?(@.status == "open")] // array of objects → matching objects
payload.config[?(@ != null)] // object → entries whose value is truthy
Filtering an array yields the surviving elements. Filtering an object yields a sub-object of the surviving key/value pairs. Truthiness here follows the conditional rules, so a string counts as truthy only if it is "true".
Key/Value and Key Extraction — .&
Where .field gives you the value, .&field gives you the { key: value } pair, which is useful when you need to keep the key name:
payload.&total // { "total": 99 }
Across an array this collects each matching pair and skips empties.
Presence and Assertion Suffixes
Two suffixes act on whether the preceding selection was present:
payload.nickname? // → true if the field was present, false if missing
payload.nickname! // → raises "Selector assertion failed" if missing, else the value
? turns a selection into a boolean presence test, which is distinct from the value being null. ! asserts presence and raises when the path is absent, so you can use it to fail fast on a required field.
XML and Namespace Navigation
For XML and other namespaced inputs, selectors reach attributes, namespaces, and prefixed elements:
payload.book.@isbn // attribute access
payload.book.@(attrExpr) // dynamic attribute
payload.order.ns0#id // namespace-qualified element (prefix declared with `ns`)
payload.book.# // the namespace URI of the node
Attributes are addressed with @name. Dynamic attributes use .@(expr) or [@(expr)]. Namespace-qualified names use prefix#local once the prefix is declared with ns prefix "uri" (see Syntax). String-like XML nodes are unwrapped to their text content automatically during selection.
Selector Reference
| Step | Syntax | Result |
|---|---|---|
| Field | .name, ."a b" | the field's value, or null |
| Dynamic field | .(expr), [(expr)] | field named by expr |
| Multi-field | .*name, [*(expr)] | array of all matches |
| Descendant | ..name, .. | recursive matches at any depth |
| Index | [i] | element i (negatives from end), null if out of bounds |
| Range | [a to b] | inclusive slice |
| Filter | [?(pred)] | matching elements / entries (@ = current) |
| Key/value | .&name, [&(expr)] | { key: value } pair |
| Attribute | .@name, .@(expr), [@(expr)] | XML attribute value |
| Namespace field | prefix#local | namespace-qualified element |
| Namespace URI | .# | the node's namespace URI |
| Presence | …? | boolean — was it present |
| Assert | …! | value, or raises if missing |
Related
- Syntax — where
/is regex versus division, and the bracket forms. - Types and Coercion — the truthiness used by filters.
- Functions and Lambdas —
map,filter,pluck, and related helpers for richer traversal. - Pattern Matching and Updates — the
updateoperator for writing into a structure.