jq

A jq clone implemented in MoonBit

jq
json
moonbit
moon add mizchi/jq@0.2.2
Download zip
Author
Version
0.2.2
License
Apache-2.0
Last updated
3 months ago
Downloads
1K

Dependencies

README

#mizchi/jq

A jq clone implemented in MoonBit.

#Install CLI

moon install mizchi/jq/cmd/moonjq

This installs moonjq to ~/.moon/bin/moonjq.

echo '{"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}]

#Use as Library

moon add mizchi/jq

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

See README.mbt.md for full doc-tested API examples.

#Compatibility

96.2% compatible with jq 1.8.1 (252/262 verified tests).

429 of 514 jq.test cases ported. 496 MoonBit tests total, all passing.

#Supported Features

CategoryFeatures
Basic., .foo, .[n], .[], .., .[m:n]
Operators\|, ,, + - * / %, == != < > <= >=, and or not, //
Assignment=, \|=, +=, -=, *=, /=, %=, //=
Construction[expr], {k: v}, {foo} (shorthand), (expr), "\(.expr)"
Controlif-then-elif-else-end, try-catch, .foo?, foreach, while, until, label/break
Bindingas $x, as [$a,$b], as {a:$x}, ?//, reduce, def
Pathpath(), setpath(), getpath(), delpaths(), del(), pick()
Builtinslength, 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__
Mathsqrt, sin, cos, atan, atan2, log, log2, exp, exp2, pow, infinite, nan, isnan, isinfinite, isfinite, isnormal
Formats@base64, @base64d, @json, @text, @html, @uri
Typesnumbers, strings, booleans, nulls, arrays, objects, iterables, scalars

#Excluded Tests (85 of 514)

FeatureTestsNotes
import/include10Module system (out of scope)
input1Multi-input I/O

#Remaining Incompatibilities (10 of 262 tested)

All caused by platform constraints (IEEE 754 double / JS string handling):

IssueTestsDetail
Large integers > 2^534Overflow to Infinity instead of exact representation
-0 handling1abs on -0.0 outputs 0 instead of -0
NUL byte (\u0000) in strings2contains and toboolean misbehave with embedded NUL
path() error message1Missing result value in error message
pick(last) negative index1Different error from jq
fromjson error message1MoonBit JSON parser produces different error text

#Quick Commands

just # check + test just fmt # format code just check # type check just test # run tests just moonjq FILTER # run CLI

#License

Apache-2.0

#mizchi/jq

A jq clone implemented in MoonBit. Provides compile, run, and run_string for JSON query/transform.

#Quick Start

///|
test {
inspect(
@jq.run_string(".foo", "{\"foo\":42}"),
content=(
#|["42"]
),
)
}

#API

#run_string — One-shot string-based execution

///|
test {
inspect(
@jq.run_string("[.[] | select(. > 3)]", "[1,2,3,4,5]"),
content=(
#|["[4,5]"]
),
)
}

#run — Execute with Json value

///|
test {
let input = @json.parse("{\"a\":1,\"b\":2}")
let results = @jq.run("{sum: (.a + .b)}", input)
inspect(
results[0].stringify(),
content=(
#|{"sum":3}
),
)
}

#compile + run_compiled — Pre-compile for repeated use

///|
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]")
}

#Error Handling

///|
test {
let result = @jq.run_string(".foo", "not json") catch {
@jq.JqError::JqError(msg) => ["Error: " + msg]
}
inspect(result[0].has_prefix("Error:"), content="true")
}

#Examples

#Field Access and Pipes

///|
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\"]"]
),
)
}

#Array Operations

///|
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"]
),
)
}

#Reduce

///|
test {
inspect(
@jq.run_string("reduce .[] as $x (0; . + $x)", "[1,2,3,4,5]"),
content=(
#|["15"]
),
)
}

#Object Construction

///|
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}"]
),
)
}

#Conditionals

///|
test {
inspect(
@jq.run_string(
"map(if . > 0 then \"pos\" elif . == 0 then \"zero\" else \"neg\" end)", "[-1,0,1]",
),
content=(
#|["[\"neg\",\"zero\",\"pos\"]"]
),
)
}

#User-Defined Functions

///|
test {
inspect(
@jq.run_string(
"def double: . * 2; def inc: . + 1; map(double | inc)", "[1,2,3]",
),
content=(
#|["[3,5,7]"]
),
)
}

#Format Strings

///|
test {
inspect(
@jq.run_string("@base64", "\"hello\""),
content=(
#|["\"aGVsbG8=\""]
),
)
inspect(
@jq.run_string("@base64d", "\"aGVsbG8=\""),
content=(
#|["\"hello\""]
),
)
}

#
JqError

pub(all) suberror JqError {
JqError(String)
} derive(Show)

Error type for jq compilation and evaluation failures.

#
ArithOp

pub enum ArithOp {
Add
Sub
Mul
Div
Mod
} derive(Show)

Arithmetic operation type used in filter expressions.

#
CmpOp

pub enum CmpOp {
Eq
Ne
Lt
Gt
Le
Ge
} derive(Show)

Comparison operation type used in filter expressions.

#
Filter

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 compiled jq filter AST node.

#
ObjKey

pub enum ObjKey {
StrKey(String)
ExprKey(Filter)
} derive(Show)

Object key type: either a static string or a dynamic expression.

#
Pattern

pub enum Pattern {
PatVar(String)
PatArray(Array[Pattern])
PatObject(Array[(String, Pattern)])
} derive(Show)

Pattern for destructuring bind in 'as' expressions.

#
compile

fn compile(filter_str : String) -> Filter raise JqError

Compile a jq filter string into a reusable Filter AST.

#
run

fn run(filter_str : String, input : Json) -> Array[Json] raise JqError

Compile and execute a jq filter string against JSON input.

#
run_compiled

fn run_compiled(filter : Filter, input : Json) -> Array[Json] raise JqError

Execute a pre-compiled filter against JSON input.

#
run_string

fn run_string(filter_str : String, input_str : String) -> Array[String] raise JqError

Compile and execute a jq filter, with JSON string input/output.