MoonJQ is a high-performance, jq-compatible JSON query interpreter written in MoonBit. It implements a complete pipeline: lexer → parser → streaming interpreter with lazy evaluation using Iterator[Json].
- Streaming semantics - Process large JSON with constant memory via iterators
- jq-compatible - Familiar syntax and behavior for jq users
- Type-safe - Built with MoonBit's strong type system
- Well-tested - 415+ passing tests covering core jq functionality
- Documented - All code examples in this README are type-checked and tested
- Identity & Access:
.(identity),.foo(field),.[0](index),.[-1](negative index) - Iteration:
.[](array iteration),.[2:4](slicing),..(recursive descent) - Composition:
|(pipe),,(comma/multiple outputs) - Safety:
?(optional access),//(alternative/default)
- Arithmetic:
+(add/concat),-(subtract),*(multiply/repeat),/(divide),%(modulo) - Comparison:
==,!=,<,<=,>,>= - Logical:
and,or,not - Type coercion: Automatic for arithmetic operations
- Conditionals:
if ... then ... else ... end - Error handling:
try ... catch ... - Variables:
$var(read-only bindings)
- Transformation:
map(expr),select(expr),sort,reverse,flatten,flatten(n),unique - Aggregation:
add,min,max,length - Inspection:
type,keys,values - Math:
floor,sqrt - Utility:
empty,not
- Arrays:
[expr],[](empty) - Objects:
{key: value},{}(empty)
# Clone the repository
git clone https://github.com/moonbit-community/moobit-jq.git
cd moobit-jq
# Run tests to verify installation
moon testUse the jq helper function to evaluate queries:
///|
/// Helper function: Evaluate a jq query and return newline-separated results.
/// This mimics the command-line jq tool's behavior.
fn jq(query : String, input : String) -> String raise {
let expr = @parser.parse(query)
let json = @json.parse(input)
@ast.eval(expr, json).collect().map(fn(v) { @debug.to_string(v) }).join("\n")
}The native command follows jq's argument order: the filter comes first, followed by zero or more input files. When no file is provided, input is read from stdin.
moon run cmd/jq --target native -- -c '.foo' data.json
cat data.json | moon run cmd/jq --target native -- -r '.name'
moon run cmd/jq --target native -- -n -c '{ok: true}'
moon run cmd/jq --target native -- -f filter.jq data.jsonBuild the release binary when you want to run it directly:
moon build --target native --release cmd/jq
_build/native/release/build/cmd/jq/jq.exe -c '.items[]' data.jsonSupported CLI options:
-c,--compact-output: print compact JSON.-r,--raw-output: print strings without JSON quotes.-f,--from-file FILE: read the filter fromFILE.-n,--null-input: run the filter once withnullinput.-l,--logs: treat input as JSONL/NDJSON and skip non-JSON lines.
See TUTORIAL.md for a CLI walkthrough adapted from the jq tutorial.
All examples below are executable and type-checked by moon check README.mbt.md.
Extract specific fields from objects that meet criteria:
inspect( jq(query, input), content=( #|Object({"name": String("Alice"), "email": String("alice@example.com")}) ), ) }
**Explanation**: The `select(.age >= 18)` filters users 18 or older, then `{name: .name, email: .email}` constructs new objects with only those fields.
### 2. Optional Access with Defaults
Handle missing fields gracefully using `?` and `//`:
```mbt check
inspect(
jq(query, input),
content=(
#|String("(unknown)")
),
)
}
Explanation: The ? operator prevents errors when .user.name doesn't exist, and // provides a default value.
inspect(jq(query, input), content="Number(12)") }
**Explanation**: `map(. * 2)` doubles each number, then `add` sums them all: `(1*2 + 2*2 + 3*2) = 12`.
### 4. Filter Logs by Level
Extract specific log messages based on severity:
```mbt check
///|
test "readme: extract error messages" {
///|
inspect(
jq(query, input),
content=(
#|String("disk full")
#|String("timeout")
),
)
}
Explanation: Streaming semantics produce multiple outputs. Each error-level event produces one result.
Work with array subsets using slicing:
///|
test "readme: array slicing" {
let query = ".items[1:3] | reverse"
let input =
#|{ "items": [10, 20, 30, 40, 50] }
inspect(
jq(query, input),
content=(
#|Array([Number(30), Number(20)])
),
)
}Explanation: [1:3] extracts elements at indices 1-2 (20, 30), then reverse flips the order.
Find all values at any depth using ..:
///|
test "readme: recursive descent" {
let query = ".. | select(type == \"number\")"
let input =
#|{
#| "a": 1,
#| "b": { "c": 2, "d": { "e": 3 } }
#|}
inspect(
jq(query, input),
content=(
#|Number(1)
#|Number(2)
#|Number(3)
),
)
}Explanation: .. recursively visits all values in the structure, then select filters only numbers.
moobit-jq/
├── moon.mod # Module metadata
├── README.mbt.md # This file (executable documentation)
├── TUTORIAL.md # CLI tutorial
├── ast/ # AST + streaming evaluator + integration tests
├── cmd/jq/ # Native jq-compatible CLI
├── parser/ # Parser (includes lexer)
├── tests/cram/ # Moon Cram CLI tests
# Run all tests (415+ tests)
moon test
# Run specific package tests
moon test -p parser
moon test -p ast
# Type-check without running tests
moon check
# Type-check this README
moon check README.mbt.md
# Update test snapshots
moon test --updateThe CLI tests use the moon cram command. moon cram builds the workspace
first and puts the built CLI binaries in PATH, so the cram examples call
jq.exe directly.
moon cram test tests/cram TUTORIAL.md# Format code
moon fmt
# Generate package interfaces
moon info
# Check for warnings
moon check --target all- Streaming: Uses MoonBit's
Iteratorfor lazy evaluation and constant memory - Parser: Hand-written recursive-descent parser with precedence climbing
- Error handling: Leverages MoonBit's checked error system with
raise - Testing: 415+ tests using MoonBit's snapshot testing (
inspect)
See FEATURES.md for detailed feature status.
Not yet implemented:
- Variable binding with
aspatterns reduceexpressionssort_by,group_by- Assignment operators (
|=,=) - String interpolation (
\(expr)) - Many string/array utility functions
Contributions welcome!
See LICENSE.
The @moonjq package exposes a simple, high-level API:
| Type | Description |
|---|---|
Query |
A compiled jq query that can be evaluated multiple times |
| Function | Signature | Description |
|---|---|---|
parse |
(String) -> Query raise |
Compile a jq query string into a reusable Query |
eval |
(Query, Json) -> Iter[Json] raise |
Evaluate a query, streaming results lazily |
eval_all |
(Query, Json) -> Array[Json] raise |
Evaluate a query, collecting all results |
run |
(String, String) -> Array[Json] raise |
Parse and evaluate in one step |
run_json |
(Query, Json) -> Array[Json] raise |
Evaluate against already-parsed JSON |
| Method | Signature | Description |
|---|---|---|
Query::eval |
(Query, Json) -> Iter[Json] raise |
Stream evaluation results |
Query::eval_all |
(Query, Json) -> Array[Json] raise |
Collect all results |
Query::eval_logs |
(Query, String) -> Iter[Json] raise |
Process NDJSON logs |
See LICENSE.