A JSONPath query engine in pure Standard ML, evaluating
Goessner-style / JSONPath-consensus
expressions over the Json.json AST from
sml-json. Compile a path once,
then run it against any document to get a node list back. No FFI, no external
dependencies, and deterministic — byte-identical under both
MLton and Poly/ML.
- 34 assertions, green on MLton and Poly/ML.
- Anchored on the canonical JSONPath "bookstore" document and consensus queries.
- Vendors
sml-json(which bundlessml-parsec) — Layout B, builds standalone.
| Syntax | Meaning |
|---|---|
$ |
the root node |
.name |
child by member name |
['name'] |
child by member name (bracket form; "name" also accepted) |
.. |
recursive descent (descendants-or-self of the current node) |
* / [*] |
wildcard — all member values / all array elements |
[n] |
array index (negative n counts from the end) |
[a:b:c] |
array slice, Python semantics (any of a/b/c may be omitted) |
[?(<filter>)] |
filter — keep children for which the predicate holds |
Filter predicates compare a relative path (@, @.field, @['field'],
@[n], and dotted/bracketed chains) or a literal (number, 'string'/"string",
true, false, null) with ==, !=, <, <=, >, >=. A bare relative
path — e.g. ?(@.isbn) — is an existence test. Numeric comparisons coerce
JInt/JReal; string comparisons are lexicographic.
With smlpkg:
smlpkg add github.com/sjqtentacles/sml-jsonpath
smlpkg sync
Include the MLB from your own (it pulls in the vendored sml-json + sml-parsec):
local
$(SML_LIB)/basis/basis.mlb
lib/github.com/sjqtentacles/sml-jsonpath/... (via smlpkg)
in
...
end
This brings structure JsonPath (and the vendored Json) into scope.
val doc = case Json.parseJson jsonText of
CharParsec.Ok v => v
| CharParsec.Err _ => raise Fail "bad json"
(* one-shot *)
val authors = JsonPath.query "$.store.book[*].author" doc (* Json.json list *)
(* or compile once, reuse *)
val p = JsonPath.compile "$..book[?(@.price<10)]"
val cheap = JsonPath.queryPath p doctype path
exception SyntaxError of string
val compile : string -> path
val queryPath : path -> Json.json -> Json.json list
val query : string -> Json.json -> Json.json list (* = queryPath o compile *)query/compile raise SyntaxError on a malformed path. Results are returned
in document order with duplicates preserved.
make example parses the canonical bookstore document and runs a few queries:
JSONPath over the canonical bookstore document
================================================
$.store.book[*].author (all authors)
-> Nigel Rees, Evelyn Waugh, Herman Melville, J. R. R. Tolkien
$..author (recursive authors)
-> Nigel Rees, Evelyn Waugh, Herman Melville, J. R. R. Tolkien
$.store.bicycle.color (child path)
-> red
$..book[-1:] (last book, slice)
-> The Lord of the Rings
$..book[?(@.price<10)] (cheap books, filter)
-> Sayings of the Century, Moby Dick
$..book[?(@.isbn)] (books with an isbn)
-> Moby Dick, The Lord of the Rings
$..* matches 27 nodes (every node except the root)
A path compiles into a list of (axis, selector) steps. Evaluation folds the
steps over a node list that starts as the singleton root:
- a child step maps every current node to its matching children;
- a descendant step (
..) first expands every current node to its descendants-or-self in document order, then applies the selector.
This "descendants-or-self then selector" model yields the consensus results for
... For instance $..* enumerates every node except the root exactly once,
because every non-root node is the direct child of exactly one node.
make test # MLton
make test-poly # Poly/ML
make all-tests # both
make example # build + run examples/demo.sml
make clean
Both compilers run the same strict-TDD suite (test/test.sml), whose oracle is
the JSONPath consensus behaviour on the bookstore document. Highlights:
- Child / dotted & bracket:
$.store.bicycle.color,$['store']['bicycle']['color']. - Wildcard:
$.store.book[*].author,$.store.*. - Index:
$..book[2], negative$.store.book[-1], out-of-range. - Slice:
$.store.book[0:2],$..book[-1:], stepped[0:4:2]. - Recursive descent:
$..author,$.store..price,$..*(27 nodes). - Filters: comparisons (
<,<=,>=,>,==,!=) and existence ($..book[?(@.isbn)]).
MIT — see LICENSE.