moonjsonpath

    JSON Pointer and JSONPath query utilities for MoonBit

    jsonpath
    json-pointer
    json
    query
    moonbit
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    4 months ago
    Downloads
    60

    #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