Skip to main content

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

StepSyntaxResult
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 fieldprefix#localnamespace-qualified element
Namespace URI.#the node's namespace URI
Presence…?boolean — was it present
Assert…!value, or raises if missing