class val JSONPath
"""
Compiled JSONPath query for extracting values from JSON documents.
JSONPath queries navigate JSON structures using string path expressions
(RFC 9535). Compile a path with `JSONPathParser.parse()`, then apply
it to any number of documents with `query()`:
```pony
match JSONPathParser.parse("$.store.book[*].author")
| let path: JSONPath =>
let authors = path.query(doc)
| let err: JSONPathParseError =>
env.err.print(err.string())
end
```
Evaluation follows RFC 9535 semantics: missing keys, out-of-bounds
indices, and type mismatches produce empty results, never errors.
Only malformed path strings produce errors (at parse time). Filter
expressions support function extensions (`length`, `count`, `match`,
`search`, `value`) per RFC 9535 Section 2.4.
For simple single-value extraction, `query_one()` returns the first
match or JSONNotFound.
"""
let _segments: Array[_Segment] val
new val _create(segments': Array[_Segment] val) =>
_segments = segments'
fun query(root: JSONValue): Array[JSONValue] val =>
"""
Execute this query against a JSON document.
Returns all matching values. Returns an empty array if no values
match. Evaluation never errors.
"""
_JSONPathEval(root, root, _segments)
fun query_one(root: JSONValue): (JSONValue | JSONNotFound) =>
"""
Execute this query and return the first matching value, or
JSONNotFound if no values match.
Convenience for paths known to select at most one value.
"""
let results = query(root)
if results.size() > 0 then
try results(0)? else JSONNotFound end
else
JSONNotFound
end
primitive JSONPathParser
"""
Parser for JSONPath expressions.
Provides two entry points:
- `parse()` returns errors as data (consistent with `JSONParser.parse()`)
- `compile()` raises on invalid input (convenience for known-valid paths)
"""
fun parse(path: String): (JSONPath | JSONPathParseError) =>
"""
Parse a JSONPath expression. Returns a compiled query on success
or a structured error on failure.
"""
let parser = _JSONPathParser(path)
try
let segments = parser.parse()?
JSONPath._create(segments)
else
parser.error_result()
end
fun compile(path: String): JSONPath ? =>
"""
Parse a JSONPath expression, raising on invalid input.
Use this when the path string is known to be valid (e.g., a string
literal). For user-provided paths, prefer `parse()` which returns
errors as data.
"""
match \exhaustive\ JSONPathParser.parse(path)
| let jp: JSONPath => jp
| let _: JSONPathParseError => error
end
class val JSONPathParseError is Stringable
"""
Structured parse error for JSONPath expressions.
"""
let message: String
let offset: USize
new val create(message': String, offset': USize) =>
message = message'
offset = offset'
fun string(): String iso^ =>
let s = recover iso String(64) end
s.append("JSONPath parse error at offset ")
s.append(offset.string())
s.append(": ")
s.append(message)
consume s