Skip to content

sjqtentacles/sml-jsonpath

Repository files navigation

sml-jsonpath

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.

Status

  • 34 assertions, green on MLton and Poly/ML.
  • Anchored on the canonical JSONPath "bookstore" document and consensus queries.
  • Vendors sml-json (which bundles sml-parsec) — Layout B, builds standalone.

Supported grammar

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.

Install

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.

Quick start

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 doc

API (signature JSON_PATH)

type 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.

Example

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)

How it works

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.

Build & test

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)]).

License

MIT — see LICENSE.

About

Pure Standard ML JSONPath query engine over sml-json (MLton + Poly/ML)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors