A jq clone implemented in MoonBit
Dependencies
moon install mizchi/jq/cmd/moonjqecho '{"name":"alice","age":30}' | moonjq '.name'
# "alice"
echo '[1,2,3,4,5]' | moonjq 'map(. * 2) | add'
# 30
echo '[{"a":1},{"a":2},{"a":3}]' | moonjq '[.[] | select(.a > 1)]'
# [{"a":2},{"a":3}]moon add mizchi/jqlet results = @jq.run(".foo", @json.parse("{\"foo\":42}"))
// results = [Number(42)]
// Pre-compile for repeated use
let filter = @jq.compile("map(. + 1)")
let r1 = @jq.run_compiled(filter, @json.parse("[1,2,3]"))
// r1 = [Array([Number(2), Number(3), Number(4)])]| Category | Features |
|---|---|
| Basic | ., .foo, .[n], .[], .., .[m:n] |
| Operators | \|, ,, + - * / %, == != < > <= >=, and or not, // |
| Assignment | =, \|=, +=, -=, *=, /=, %=, //= |
| Construction | [expr], {k: v}, {foo} (shorthand), (expr), "\(.expr)" |
| Control | if-then-elif-else-end, try-catch, .foo?, foreach, while, until, label/break |
| Binding | as $x, as [$a,$b], as {a:$x}, ?//, reduce, def |
| Path | path(), setpath(), getpath(), delpaths(), del(), pick() |
| 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, toboolean, 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, IN, INDEX, error, paths, isempty, nth, skip, builtins, utf8bytelength, $__loc__ |
| Math | sqrt, sin, cos, atan, atan2, log, log2, exp, exp2, pow, infinite, nan, isnan, isinfinite, isfinite, isnormal |
| Formats | @base64, @base64d, @json, @text, @html, @uri |
| Types | numbers, strings, booleans, nulls, arrays, objects, iterables, scalars |
| Feature | Tests | Notes |
|---|---|---|
| import/include | 10 | Module system (out of scope) |
| input | 1 | Multi-input I/O |
| Issue | Tests | Detail |
|---|---|---|
| Large integers > 2^53 | 4 | Overflow to Infinity instead of exact representation |
| -0 handling | 1 | abs on -0.0 outputs 0 instead of -0 |
| NUL byte (\u0000) in strings | 2 | contains and toboolean misbehave with embedded NUL |
| path() error message | 1 | Missing result value in error message |
| pick(last) negative index | 1 | Different error from jq |
| fromjson error message | 1 | MoonBit JSON parser produces different error text |
just # check + test
just fmt # format code
just check # type check
just test # run tests
just moonjq FILTER # run CLI///|
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 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(Pattern, Filter, Filter)
Reduce(Filter, Pattern, 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)
Alternative(Filter, Filter)
Foreach(Filter, Pattern, Filter, Filter, Filter?)
StringInterp(Array[(String, Filter?)])
Assign(Filter, Filter)
Update(Filter, Filter)
UpdateAlt(Filter, Filter)
Label(String, Filter)
Break(String)
BindingAlt(Array[Pattern], Filter, Filter)
} derive(Show)A jq clone implemented in MoonBit
Dependencies