toJsonPath

This shape as an RFC 9535 query: $['items'][:]['sku'].

The interchange spelling, and deliberately not the engine's. toString writes $.items[*], which parses under RFC 9535 and means something else — * selects every child, of an object as well as an array, while CatalogStep.AnyElement selects array elements. Over {"items":{"sku":"a"}} the two nodelists differ, with nothing to say so. So AnyElement is written here as the slice [:], which §2.3.4.2.2 defines over arrays alone — "It selects no nodes from a node that is not an array" — and the rendering means what the walk means over every document shape. NodeWalkDifferentialTest is where that equality is checked rather than claimed.

Member names are single-quoted with §2.3.1.1's escapes, and a control character is written \uXXXX even where a named escape exists.

This is a query, and never a Normalized Path. RFC 9535 §2.7 admits name selectors and non-negative index selectors only, so a shape carrying AnyElement has no normalized form at all — there is no single location to name. Two of these must therefore not be compared as text the way two VariantPath.toNormalizedPath strings can be; compare the paths.

This is not what the engine writes to disk. A path is persisted as toString and read back by parse. Reach for this to hand a shape to something outside the engine, and for toString everywhere else.

A trailing .. has no rendering at all, and that is a fact about RFC 9535 rather than a gap here: $.. means the root and every node below it, $..* means every node below it and not the root, and the grammar has no way to say the union. Rendering it as $..* would be the [*]-for-AnyElement mistake a second time — an expression that parses, looks right, and quietly drops a node. So it is refused, like the surrogate below.

Throws

if a field name holds an unpaired surrogate, which RFC 9535 has no production for — a name decoded from a stored document cannot hold one — or if the path ends in CatalogStep.AnyDescendant.