moonjsonpath

JSON Pointer and JSONPath query utilities for MoonBit

jsonpath
json-pointer
json
query
moonbit
moon add Freesia666/moonjsonpath@0.1.1
Download zip
Version
0.1.1
License
Apache-2.0
Last updated
last month
Downloads
24

Dependencies

README

#MoonJSONPath

MoonJSONPath is a MoonBit-native JSON Pointer, JSONPath, and lightweight JSON patch toolkit. It helps MoonBit programs locate, inspect, and update structured JSON data without hand-written recursive traversal code.

This project is designed for MoonBit tooling, configuration validation, API clients, documentation processing, test fixtures, and LLM tool-calling workflows.

#Highlights

  • JSON Pointer parser and formatter compatible with RFC 6901 escaping.
  • JSON Pointer get, set, remove, and URI fragment helpers.
  • JSONPath query engine with members, quoted members, arrays, wildcards, recursive descent, slices, unions, filters, and match locations.
  • Extended filter expressions: &&, ||, !, grouped expressions, contains, starts_with, ends_with, and .length.
  • Pointer Patch operations: add, replace, remove, test, copy, and move.
  • CLI subcommands: query, get, set, remove, and patch.
  • Output modes for values, JSON Pointer paths, and { path, value } match objects.
  • Runnable cookbook, conformance-style tests, workflow catalog, operator reference, readiness matrix, and contest submission evidence generators.

#Quick Start

Install MoonBit, then run the test suite from the project root:

moon check moon test

Run the CLI:

moon run cmd/main -- query '$.users[*].name' data.json moon run cmd/main -- query --pointers '$..name' data.json moon run cmd/main -- query --matches --pretty '$.users[?(@.age > 20)]' data.json moon run cmd/main -- get '/users/0/name' data.json moon run cmd/main -- set '/users/0/active' true data.json moon run cmd/main -- remove '/users/0/secret' data.json moon run cmd/main -- patch patch.json data.json

Running without arguments prints help:

moon run cmd/main

#Library Example

///|
test {
let doc : Json = {
"users": [
{ "name": "Ada", "age": 36, "active": true },
{ "name": "Grace", "age": 85, "active": false },
],
}

let path = @moonjsonpath.Path::compile("$.users[?(@.age > 40)].name").unwrap()
let matches = path.query(doc)

inspect(matches[0].pointer.to_string(), content="/users/1/name")
@json.json_inspect(matches.map(item => item.value), content=["Grace"])
}

#JSON Pointer

JSON Pointer identifies one exact location inside a JSON document.

///|
test {
let doc : Json = { "meta": { "count": 2 }, "tags": ["old"] }

let pointer = @moonjsonpath.Pointer::parse("/meta/count").unwrap()
@json.json_inspect(pointer.get(doc).unwrap(), content=2)

let updated = pointer.set(doc, 3).unwrap()
@json.json_inspect(updated, content={
"meta": { "count": 3 },
"tags": ["old"],
})
}

Supported Pointer features:

  • root pointer: ""
  • object fields: /user/name
  • array indexes: /users/0
  • RFC 6901 escaping: /a~1b for a/b, /m~0n for m~n
  • URI fragments: #/users/0/name
  • immutable set and remove

#JSONPath

JSONPath queries can return multiple matches. Each match includes both the value and its JSON Pointer location.

Supported JSONPath selectors:

FeatureExample
Root$
Dot member$.users
Quoted member$['display name']
Array index$.users[0]
Negative index$.users[-1]
Wildcard$.users[*], $.meta.*
Recursive member$..name
Slice$.items[1:4], $.items[::-1]
Union$.items[0,2], $.meta['owner','count']
Filter$.users[?(@.active == true)]

Filter examples:

$.users[?(@.age >= 18)] $.users[?(@.active == true && @.role == "admin")] $.users[?(@.name starts_with "A" || @.name contains "ace")] $.files[?(@.name ends_with ".mbt")] $.items[?(@.tags.length >= 2)] $.items[?(!(@.hidden == true))]

#Pointer Patch

MoonJSONPath includes a lightweight pointer-based transformation layer.

///|
test {
let doc : Json = {
"users": [{ "name": "Ada", "active": false }],
"secret": true,
}

let changed = @moonjsonpath.apply_patch(doc, [
@moonjsonpath.PatchOp::replace("/users/0/active", true),
@moonjsonpath.PatchOp::remove("/secret"),
]).unwrap()

@json.json_inspect(changed, content={
"users": [{ "name": "Ada", "active": true }],
})
}

Patch operation constructors:

  • PatchOp::add(path, value)
  • PatchOp::replace(path, value)
  • PatchOp::remove(path)
  • PatchOp::assert_value(path, value)
  • PatchOp::copy(from, path)
  • PatchOp::move_to(from, path)
  • PatchOp::parse_many(json)

The patch layer is intentionally documented as MoonJSONPath's own lightweight transform API. It is not advertised as full RFC 6902 compatibility.

#CLI Usage

Usage: moonjsonpath query [--values|--pointers|--matches] [--pretty] <jsonpath> <file> moonjsonpath get [--pretty] <pointer> <file> moonjsonpath set [--pretty] <pointer> <json-value> <file> moonjsonpath remove [--pretty] <pointer> <file> moonjsonpath patch [--pretty] <patch-file> <file> Legacy: moonjsonpath [--values|--pointers|--matches] [--pretty] <jsonpath> <file>

Example input:

{ "users": [ { "name": "Ada", "active": false }, { "name": "Grace", "active": true } ] }

Examples:

moon run cmd/main -- query '$.users[*].name' users.json # ["Ada","Grace"] moon run cmd/main -- query --pointers '$.users[*].name' users.json # ["/users/0/name","/users/1/name"] moon run cmd/main -- get '/users/0/name' users.json # "Ada" moon run cmd/main -- set '/users/0/active' true users.json # {"users":[{"name":"Ada","active":true},{"name":"Grace","active":true}]}

File input is supported through moonbitlang/x/fs. Direct stdin reading is not claimed yet because the MoonBit standard/x packages used here do not currently expose a stable stdin API.

#Public API Overview

Core query APIs:

  • Path::compile(input)
  • Path::query(doc)
  • Path::to_string()
  • Path::first(doc)
  • Path::exists(doc)
  • Path::values(doc)
  • Path::pointers(doc)
  • Path::explain()
  • query_json_text(path, json)
  • query_json_file(path, file)

Pointer APIs:

  • Pointer::parse(input)
  • Pointer::parse_uri_fragment(input)
  • Pointer::to_string()
  • Pointer::to_uri_fragment()
  • Pointer::tokens()
  • Pointer::get(doc)
  • Pointer::set(doc, value)
  • Pointer::remove(doc)

Text transform helpers:

  • pointer_get_json_text(pointer, json_text)
  • pointer_set_json_text(pointer, value_text, json_text)
  • pointer_remove_json_text(pointer, json_text)
  • patch_json_text(patch_text, json_text)

Documentation and demo data generators:

  • cookbook_markdown()
  • scenario_catalog_markdown()
  • operator_reference_markdown()
  • readiness_matrix_markdown()
  • submission_evidence_markdown()
  • longform_handbook()

#Project Structure

moonjsonpath/ ├── moonjsonpath.mbt # Core Pointer and JSONPath implementation ├── patch.mbt # Pointer Patch operations ├── transform.mbt # Text-based JSON transform helpers ├── cli_command.mbt # CLI parser and command execution ├── query_helpers.mbt # Convenience query APIs ├── explain.mbt # JSONPath explain/introspection helpers ├── pointer_uri.mbt # JSON Pointer URI fragment support ├── cookbook.mbt # Runnable cookbook examples ├── *_test.mbt # Unit, catalog, conformance, and CLI tests ├── cmd/main/ # CLI executable entry point ├── docs/ # Contest proposal and project notes ├── moon.mod # MoonBit module metadata └── moon.pkg # Package imports

#Testing

Run:

moon fmt moon info moon check moon test

The test suite covers:

  • JSON Pointer parsing, escaping, reading, setting, removing, and URI fragments
  • JSONPath parsing, selection, filters, formatting, diagnostics, and helpers
  • Pointer Patch operations and error reporting
  • CLI command parsing and text execution
  • RFC-style conformance catalog cases
  • Workflow, operator reference, readiness, and submission evidence catalogs

At the time this README was written, the project has 65 passing tests and just over 7k MoonBit source lines.

#Scope and Non-goals

MoonJSONPath is not a jq clone. jq is a full JSON programming language with pipes, functions, object construction, reductions, arithmetic, and many transformation operators. MoonJSONPath focuses on a compact, testable query and location layer for MoonBit projects.

Current non-goals:

  • full jq compatibility
  • arbitrary script expressions inside filters
  • full RFC 9535 regular-expression predicates
  • streaming parser
  • direct stdin support before a stable MoonBit API is available
  • claiming full RFC 6902 compatibility for the patch layer

#Contest Notes

MoonJSONPath is an original MoonBit implementation inspired by open standards: JSON Pointer RFC 6901 and the JSONPath RFC 9535 query model. It is not a port of one upstream repository.

The repository includes runnable examples, focused tests, conformance-style catalogs, structured documentation generators, and a CLI suitable for local demonstration during contest review.

#License

This project is licensed under the Apache License 2.0. See LICENSE.

#
CliCommand

pub(all) struct CliCommand {
kind : CliCommandKind
path_text : String
pointer_text : String
value_text : String
patch_file_path : String
file_path : String
output : OutputMode
indent : Int
} derive(Eq,
Debug
)

#
CliCommand::parse

fn CliCommand::parse(args : Array[String]) -> Result[CliCommand, String]

#
CliCommandKind

pub(all) enum CliCommandKind {
Query
PointerGet
PointerSet
PointerRemove
Patch
Help
} derive(Eq,
Debug
)

#
CliCommandKind::to_string

fn CliCommandKind::to_string(self : CliCommandKind) -> String

#
CliConfig

pub(all) struct CliConfig {
path_text : String
file_path : String
options : QueryOptions
show_help : Bool
} derive(Eq,
Debug
)

#
CliConfig::parse

fn CliConfig::parse(args : Array[String]) -> Result[CliConfig, String]

#
CompareOp

pub(all) enum CompareOp {
Eq
Ne
Gt
Ge
Lt
Le
} derive(Eq,
Debug
)

#
CookbookEntry

pub(all) struct CookbookEntry {
id : String
title : String
json_text : String
path_text : String
output_mode : OutputMode
expected : String
note : String
} derive(Eq,
Debug
)

#
CookbookEntry::run

fn CookbookEntry::run(self : CookbookEntry) -> Result[CookbookResult, String]

#
CookbookResult

pub(all) struct CookbookResult {
id : String
title : String
output : String
expected : String
passed : Bool
} derive(Eq,
Debug
)

#
EvidenceItem

pub(all) struct EvidenceItem {
area : String
claim : String
evidence : String
command : String
} derive(Eq,
Debug
)

#
FilterExpr

pub(all) enum FilterExpr {
Exists(Array[String])
Compare(Array[String], CompareOp, Literal)
StringMatch(Array[String], StringOp, String)
LengthCompare(Array[String], CompareOp, Literal)
And(FilterExpr, FilterExpr)
Or(FilterExpr, FilterExpr)
Not(FilterExpr)
} derive(Eq,
Debug
)

#
Literal

pub(all) enum Literal {
LString(String)
LNumber(Double)
LBool(Bool)
LNull
} derive(Eq,
Debug
)

#
OperatorReference

pub(all) struct OperatorReference {
group : String
syntax : String
example : String
description : String
rfc_note : String
} derive(Eq,
Debug
)

#
OutputMode

pub(all) enum OutputMode {
Values
Pointers
Matches
} derive(Eq,
Debug
)

#
OutputMode::to_string

fn OutputMode::to_string(self : OutputMode) -> String

#
PatchError

pub(all) struct PatchError {
index : Int
path : String
message : String
} derive(Eq,
Debug
)

#
PatchError::describe

fn PatchError::describe(self : PatchError) -> String

#
PatchKind

pub(all) enum PatchKind {
Add
Replace
Remove
AssertValue
Copy
MoveValue
} derive(Eq,
Debug
)

#
PatchOp

pub(all) struct PatchOp {
kind : PatchKind
path : String
value : Json?
from : String?
} derive(Eq,
Debug
)

#
PatchOp::add

fn PatchOp::add(path : String, value : Json) -> PatchOp

#
PatchOp::assert_value

fn PatchOp::assert_value(path : String, value : Json) -> PatchOp

#
PatchOp::copy

fn PatchOp::copy(from : String, path : String) -> PatchOp

#
PatchOp::move_to

fn PatchOp::move_to(from : String, path : String) -> PatchOp

#
PatchOp::parse

fn PatchOp::parse(doc : Json) -> Result[PatchOp, String]

#
PatchOp::parse_many

fn PatchOp::parse_many(doc : Json) -> Result[Array[PatchOp], String]

#
PatchOp::remove

fn PatchOp::remove(path : String) -> PatchOp

#
PatchOp::replace

fn PatchOp::replace(path : String, value : Json) -> PatchOp

#
Path

pub(all) struct Path {
segments : Array[PathSegment]
} derive(Eq,
Debug
)

#
Path::compile

fn Path::compile(input : String) -> Result[Path, PathError]

#
Path::exists

fn Path::exists(self : Path, doc : Json) -> Bool

#
Path::explain

fn Path::explain(self : Path) -> Array[String]

#
Path::first

fn Path::first(self : Path, doc : Json) -> Json?

#
Path::is_definite

fn Path::is_definite(self : Path) -> Bool

#
Path::pointers

fn Path::pointers(self : Path, doc : Json) -> Json

#
Path::query

fn Path::query(self : Path, doc : Json) -> Array[PathMatch]

#
Path::segment_count

fn Path::segment_count(self : Path) -> Int

#
Path::to_string

fn Path::to_string(self : Path) -> String

#
Path::values

fn Path::values(self : Path, doc : Json) -> Json

#
PathError

pub(all) struct PathError {
position : Int
message : String
} derive(Eq,
Debug
)

#
PathError::describe

fn PathError::describe(self : PathError, input : String) -> String

#
PathMatch

pub(all) struct PathMatch {
value : Json
pointer : Pointer
} derive(Eq,
Debug
)

#
PathSegment

pub(all) enum PathSegment {
Member(String)
Element(Int)
Wildcard
RecursiveMember(String)
Slice(Int?, Int?, Int?)
Filter(FilterExpr)
Union(Array[PathSegment])
} derive(Eq,
Debug
)

#
Pointer

pub(all) struct Pointer {
parts : Array[String]
} derive(Eq,
Debug
)

#
Pointer::get

fn Pointer::get(self : Pointer, doc : Json) -> Result[Json, PointerError]

#
Pointer::parse

fn Pointer::parse(input : String) -> Result[Pointer, PointerError]

#
Pointer::parse_uri_fragment

fn Pointer::parse_uri_fragment(input : String) -> Result[Pointer, PointerError]

#
Pointer::remove

fn Pointer::remove(self : Pointer, doc : Json) -> Result[Json, PointerError]

#
Pointer::set

fn Pointer::set(self : Pointer, doc : Json, value : Json) -> Result[Json, PointerError]

#
Pointer::to_string

fn Pointer::to_string(self : Pointer) -> String

#
Pointer::to_uri_fragment

fn Pointer::to_uri_fragment(self : Pointer) -> String

#
Pointer::tokens

fn Pointer::tokens(self : Pointer) -> Array[String]

#
PointerError

pub(all) struct PointerError {
kind : PointerErrorKind
path : String
message : String
} derive(Eq,
Debug
)

#
PointerError::describe

fn PointerError::describe(self : PointerError) -> String

#
PointerErrorKind

pub(all) enum PointerErrorKind {
InvalidSyntax
MissingKey
InvalidIndex
WrongContainer
} derive(Eq,
Debug
)

#
QueryOptions

pub(all) struct QueryOptions {
output : OutputMode
indent : Int
} derive(Eq,
Debug
)

#
QueryOptions::matches

fn QueryOptions::matches(indent? : Int) -> QueryOptions

#
QueryOptions::pointers

fn QueryOptions::pointers(indent? : Int) -> QueryOptions

#
QueryOptions::values

fn QueryOptions::values(indent? : Int) -> QueryOptions

#
ReadinessItem

pub(all) struct ReadinessItem {
category : String
item : String
status : String
evidence : String
next_step : String
} derive(Eq,
Debug
)

#
Scenario

pub(all) struct Scenario {
title : String
domain : String
query : String
pointer : String
cli : String
note : String
} derive(Eq,
Debug
)

#
StringOp

pub(all) enum StringOp {
Contains
StartsWith
EndsWith
} derive(Eq,
Debug
)

#
apply_patch

fn apply_patch(doc : Json, ops : Array[PatchOp]) -> Result[Json, PatchError]

#
cli_command_usage

fn cli_command_usage() -> String

#
cli_usage

fn cli_usage() -> String

#
cookbook_entries

fn cookbook_entries() -> Array[CookbookEntry]

#
cookbook_markdown

fn cookbook_markdown() -> String

#
execute_cli_text

fn execute_cli_text(command : CliCommand, json_text : String, patch_text : String) -> Result[String, String]

#
handbook_submission_supplement

fn handbook_submission_supplement() -> String

#
longform_handbook

fn longform_handbook() -> String

#
operator_reference

fn operator_reference() -> Array[OperatorReference]

#
operator_reference_markdown

fn operator_reference_markdown() -> String

#
patch_json_text

fn patch_json_text(patch_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
pointer_get_json_text

fn pointer_get_json_text(pointer_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
pointer_remove_json_text

fn pointer_remove_json_text(pointer_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
pointer_set_json_text

fn pointer_set_json_text(pointer_text : String, value_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
query_exists_json_text

fn query_exists_json_text(path_text : String, json_text : String) -> Result[Bool, String]

#
query_first_json_text

fn query_first_json_text(path_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
query_json_file

fn query_json_file(path_text : String, file_path : String) -> Result[String, String]

#
query_json_file_with_options

fn query_json_file_with_options(path_text : String, file_path : String, options : QueryOptions) -> Result[String, String]

#
query_json_text

fn query_json_text(path_text : String, json_text : String) -> Result[String, String]

#
query_json_text_with_options

fn query_json_text_with_options(path_text : String, json_text : String, options : QueryOptions) -> Result[String, String]

#
query_pointers_json_text

fn query_pointers_json_text(path_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
query_values_json_text

fn query_values_json_text(path_text : String, json_text : String, indent? : Int) -> Result[String, String]

#
readiness_count_by_status

fn readiness_count_by_status(status : String) -> Int

#
readiness_matrix

fn readiness_matrix() -> Array[ReadinessItem]

#
readiness_matrix_markdown

fn readiness_matrix_markdown() -> String

#
run_cli

fn run_cli(args : Array[String]) -> Int

#
run_cli_command

fn run_cli_command(args : Array[String]) -> Int

#
run_cookbook

fn run_cookbook() -> Array[CookbookResult]

#
scenario_catalog

fn scenario_catalog() -> Array[Scenario]

#
scenario_catalog_markdown

fn scenario_catalog_markdown() -> String

#
scenario_count_by_domain

fn scenario_count_by_domain(domain : String) -> Int

#
submission_evidence

fn submission_evidence() -> Array[EvidenceItem]

#
submission_evidence_count

fn submission_evidence_count(area : String) -> Int

#
submission_evidence_markdown

fn submission_evidence_markdown() -> String