CatalogPath
A location shape inside a document: the same list of steps as a app.oreshkov.rabosh.variant.VariantPath, except that array indices are collapsed into a single CatalogStep.AnyElement.
{"items":[{"sku":"a"},{"sku":"b"}]}
$ object
$.items array
$.items[*] object, twice
$.items[*].sku string, twiceWhy this is not VariantPath. That type's contract is that it names exactly one location, which is what makes it usable as an index key and what lets Variant.select return one value. Adding a wildcard to it would break that guarantee for every existing caller. So the catalog gets its own type, and the relationship is one-way: every VariantPath has a CatalogPath shape, and a CatalogPath describes a set of VariantPaths.
That set is not abstract. forEachNodeIn enumerates it against a document — which is the only way it can be enumerated, since [*] stands for as many locations as that document has elements. The direction stays one-way: there is no CatalogPath.toVariantPath, and there cannot be.
And one step the data cannot produce. CatalogStep.AnyDescendant, spelled .., is a pattern rather than a collapse: $..["@type"] names that field wherever it sits, at any depth, which is the only sound way to ask about a corpus whose nesting is the content designer's rather than the schema's. Enumerating the shapes instead was measured and refused — on one 46 MB corpus, 72% of tagged elements belong to a type occupying more than one shape, one type occupies 49, and a shape missing from the list is a document missing from a result with nothing to report it. A sketch never emits this step, an index and a predicate leaf may carry it, and a projection still may not.
Why the indices are collapsed at all. A ten-thousand-element array would otherwise produce ten thousand paths, exhaust the path budget on its own, and push everything genuinely worth modelling into the overflow bucket. Collapsing also produces the path an inverted index over array elements actually wants — $.tags[*] is the thing somebody queries, $.tags[7] is not. ClickHouse's JSON type and the Variant shredding specification both make the same choice.
Paths are ordered so that a report is stable between runs: field before element at the same position, then by field name, then shorter first.
PATHS.md compares this grammar with the other three, which is where to look before spelling one expression for a filter and an extraction at once: [*] means array elements here and every child in RFC 9535, and a backslash is literal here and an escape there. Neither divergence has a diagnostic, and both are what toJsonPath and Companion.parseJsonPath exist to cross.
Properties
Functions
Every node this path stands for in document, in document order.
The same, materialised.
This path extended by step.
Whether this path is other or lies underneath it.
This shape as an RFC 9535 query: $['items'][:]['sku'].