A jq clone implemented in MoonBit
| Category | Features |
|---|---|
| Basic | . (identity), .foo, .[n], .[], .. (recursive descent) |
| Operators | \|, ,, + - * / %, == != < > <= >=, and or not |
| Construction | [expr], {k: v}, {foo} (shorthand), (expr) |
| Control | if-then-elif-else-end, try-catch, .foo?, try expr |
| Binding | as $x, reduce, def f: body;, def f(a;b): body; |
| Builtins | length, keys, values, type, select, map, map_values, empty, add, sort, sort_by, group_by, unique, unique_by, reverse, flatten, min, max, min_by, max_by, first, last, limit, range, has, contains, inside, any, all, tostring, tonumber, tojson, fromjson, ascii_downcase, ascii_upcase, split, join, startswith, endswith, ltrimstr, rtrimstr, trim, ltrim, rtrim, trimstr, abs, fabs, floor, ceil, round, explode, implode, to_entries, from_entries, with_entries, walk, transpose, bsearch, indices, index, rindex, not, recurse, debug, getpath, IN |
| Formats | @base64, @base64d, @json, @text, @html, @uri |
| Types | numbers, strings, booleans, nulls, arrays, objects, iterables, scalars |
| Benchmark | Ops/sec | vs Naive | Description |
|---|---|---|---|
| identity | 4,910,610 | 10.8x | . |
| field_access | 3,805,345 | 10.5x | .a |
| nested_field | 539,358 | 2.9x | .a.b.c.d |
| sort | 325,085 | 2.3x | sort (10 elements) |
| keys_length | 468,123 | 4.3x | keys \| length |
| object_construct | 397,703 | 4.0x | {x: .a, y: .b} |
| parse_complex | 321,200 | 25.1x | if .a > 0 then ... end |
| reduce | 297,406 | 26.6x | reduce .[] as $x (0; . + $x) |
| group_by_map | 2,516,187 | 95.9x | group_by(.k) \| map(...) |
| split_join | 250,297 | 6.1x | split(",") \| join("-") |
| map | 111,305 | 3.1x | map(. + 1) (10 elements) |
| recursive_descent | 99,485 | 4.0x | [.. \| numbers] |
| array_iterate | 83,673 | 2.8x | [.[] \| . * 2] (10 elements) |
| select_filter | 48,006 | 6.2x | [.[] \| select(. > 5)] (10 elements) |
| pipe_chain | 36,160 | 4.7x | .[] \| . * 2 \| . + 1 \| tostring \| length |
just # check + test
just fmt # format code
just check # type check
just test # run tests
just run # run main///|
test {
inspect(
@jq.run_string(".foo", "{\"foo\":42}"),
content=(
#|["42"]
),
)
}///|
test {
inspect(
@jq.run_string("[.[] | select(. > 3)]", "[1,2,3,4,5]"),
content=(
#|["[4,5]"]
),
)
}///|
test {
let input = @json.parse("{\"a\":1,\"b\":2}")
let results = @jq.run("{sum: (.a + .b)}", input)
inspect(
results[0].stringify(),
content=(
#|{"sum":3}
),
)
}///|
test {
let filter = @jq.compile("map(. * 2)")
let r1 = @jq.run_compiled(filter, @json.parse("[1,2,3]"))
let r2 = @jq.run_compiled(filter, @json.parse("[10,20]"))
inspect(r1[0].stringify(), content="[2,4,6]")
inspect(r2[0].stringify(), content="[20,40]")
}///|
test {
let result = @jq.run_string(".foo", "not json") catch {
@jq.JqError::JqError(msg) => ["Error: " + msg]
}
inspect(result[0].has_prefix("Error:"), content="true")
}///|
test {
let input = "{\"user\":{\"name\":\"alice\",\"age\":30}}"
inspect(
@jq.run_string(".user.name", input),
content=(
#|["\"alice\""]
),
)
inspect(
@jq.run_string(".user | keys", input),
content=(
#|["[\"age\",\"name\"]"]
),
)
}///|
test {
let arr = "[3,1,4,1,5,9,2,6]"
inspect(
@jq.run_string("sort | unique", arr),
content=(
#|["[1,2,3,4,5,6,9]"]
),
)
inspect(
@jq.run_string("map(. * 2) | add", arr),
content=(
#|["62"]
),
)
}///|
test {
inspect(
@jq.run_string("reduce .[] as $x (0; . + $x)", "[1,2,3,4,5]"),
content=(
#|["15"]
),
)
}///|
test {
let input = "[{\"name\":\"a\",\"val\":1},{\"name\":\"b\",\"val\":2}]"
inspect(
@jq.run_string("map({(.name): .val}) | add", input),
content=(
#|["{\"a\":1,\"b\":2}"]
),
)
}///|
test {
inspect(
@jq.run_string(
"map(if . > 0 then \"pos\" elif . == 0 then \"zero\" else \"neg\" end)", "[-1,0,1]",
),
content=(
#|["[\"neg\",\"zero\",\"pos\"]"]
),
)
}///|
test {
inspect(
@jq.run_string(
"def double: . * 2; def inc: . + 1; map(double | inc)", "[1,2,3]",
),
content=(
#|["[3,5,7]"]
),
)
}///|
test {
inspect(
@jq.run_string("@base64", "\"hello\""),
content=(
#|["\"aGVsbG8=\""]
),
)
inspect(
@jq.run_string("@base64d", "\"aGVsbG8=\""),
content=(
#|["\"hello\""]
),
)
}pub(all) suberror JqError {
JqError(String)
}pub enum ArithOp {
Add
Sub
Mul
Div
Mod
}pub enum CmpOp {
Eq
Ne
Lt
Gt
Le
Ge
}pub enum Filter {
Identity
RecurseOp
Field(String)
Index(Int)
Slice(Int?, Int?)
Iterate
Pipe(Filter, Filter)
Comma(Filter, Filter)
Literal(Json)
ArrayConstruct(Filter?)
ObjectConstruct(Array[(ObjKey, Filter)])
Paren(Filter)
Try(Filter)
TryCatch(Filter, Filter)
IfThenElse(Filter, Filter, Filter)
Binding(String, Filter, Filter)
Reduce(Filter, String, Filter, Filter)
FuncDef(String, Array[String], Filter, Filter)
FuncCall(String, Array[Filter])
DynIndex(Filter)
PostfixDynIndex(Filter, Filter)
DynSlice(Filter, Filter?, Filter?)
Neg(Filter)
Arith(ArithOp, Filter, Filter)
Compare(CmpOp, Filter, Filter)
LogicAnd(Filter, Filter)
LogicOr(Filter, Filter)
}