Skip to content

Latest commit

 

History

235 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

csonpath

That's not my path, that's not your path, but csonpath.

Project Sandbox

csonpath is a partial JSONPath implementation in C. It is backend-agnostic: the same core engine can query, update, and remove data from any value representation that supports array, object, and scalar semantics—not just JSON.

Out of the box it ships with C backends for json-c and yyjson, plus language bindings for Python and Rust.


🚀 Features

JSONPath Syntax

Feature Example Description
Dot notation $.a.b Access nested object fields
Bracket notation $['a']['b'] Alternative object/array access
Array index $.array[0] Access by zero-based index
Wildcard [*] $.array[*].field Iterate all array elements
Recursive descent .. $..name Search recursively for a key
Recursive descent wildcard ..* $..* Return all descendants
Union , (inside brackets) $['a','b'], $.array[0,1], $.items[?n==1, ?n==2] Match all listed selectors at once
OR fallback | $.a | $.b Try the left path first; fall back to the right one if it does not match. Only the first successful path is used.
Filters $.items[?price > 10] Filter array elements
Regex filters $.items[?name =~ "foo"] POSIX regex-based filtering
Multiple filters (&) $.items[?a=1 & b=2] Combine conditions
Subpath expressions $.obj[$.key] Use JSON values as dynamic path keys
@ current object $.items[?@.price > 10] Reference the current element in filters
Type selectors $..["@odata.id"]@string(), $.*@integer() Filter matches by JSON type (string, integer, null)

Operations

  • Find First — retrieve the first match.
  • Find All — retrieve all matches (returns an array).
  • Update or Create — modify existing values or create missing ones.
  • Remove — delete matching elements.
  • Callback — execute a custom callback on each match.
  • Update or Create Callback — traverse the path, creating missing intermediate objects and arrays, then invoke the callback on each leaf node.

🌐 Links


📄 Table of Contents


📦 Installation

csonpath is a C library at its core. Pick the instructions for the language you want to use it from.

C (json-c)

Prerequisites: C compiler (gcc, clang, or tcc), json-c.

Just include the header in your project. There is no separate install step required:

#include "csonpath_json-c.h"

Make sure to link against json-c when compiling:

gcc myapp.c -o myapp $(pkg-config --cflags --libs json-c)

C (yyjson)

Prerequisites: C compiler (gcc, clang, or tcc), yyjson.

#include "csonpath_yyjson.h"

Link against yyjson when compiling:

gcc myapp.c -o myapp $(pkg-config --cflags --libs yyjson)

Python

Prerequisites: Python 3.x.

Install from PyPI:

pip install csonpath

To install from source (development):

pip install .
# or
make pip-dev

Rust

Prerequisites: Rust toolchain.

The Rust crate is located in rust/. Build and test it with:

cd rust
cargo build
cargo test

To depend on it from another Rust project:

[dependencies]
csonpath = { path = "rust" }

🛠️ Usage

C (json-c)

#include "csonpath_json-c.h"

static void my_cb(json_object *parent, struct csonpath_child_info *info,
                  json_object *current, void *ud)
{
    json_object_set_string(current, "modified");
}

int main(void)
{
    struct json_object *jobj = json_tokener_parse(json_str);
    struct csonpath *p = csonpath_new("$.a");

    /* Find First: return the first match, or NULL */
    struct json_object *ret = csonpath_find_first(p, jobj);

    /* Find All: return a NEW json_object array. Caller must free it. */
    ret = csonpath_find_all(p, jobj);
    json_object_put(ret);

    /* Remove: delete matching keys (or set array slots to null). Returns count. */
    int removed = csonpath_remove(p, jobj);

    /* Update or Create: replace matches, or create the full path if missing. */
    csonpath_update_or_create(p, jobj, json_object_new_string("new_value"));

    /* Callback: call a user function for every match. */
    csonpath_callback(p, jobj, my_cb, NULL);

    /* Update or Create Callback: like callback, but creates missing parents first,
       then invokes the callback on every leaf (existing or newly created). */
    csonpath_update_or_create_callback(p, jobj, my_cb, NULL);

    csonpath_destroy(p);
    json_object_put(jobj);
    return 0;
}

Python

import csonpath

data = {"a": "value", "array": [1, 2, 3]}
p = csonpath.CsonPath("$.a")

# Find First / Find All
p.find_first(data)   # -> "value"
p.find_all(data)     # -> ["value"]

# Remove: returns number of removed items
p.set_path("$.array[*]")
p.remove(data)

# Update or Create: builds missing objects/arrays automatically
p.set_path("$.x.y.z")
p.update_or_create(data, [])
# data is now {"a": "value", "array": [1, 2, 3], "x": {"y": {"z": []}}}

# Callback
p.set_path("$.a")
p.callback(data, lambda parent, idx, cur, _: parent.__setitem__(idx, cur.upper()))

# Update or Create Callback: creates parents, then calls cb on each leaf
p.set_path("$[*].a")
p.update_or_create_callback(dst, my_sync_fn, userdata)

📘 C API Reference

The reference below uses the json-c backend as an example (struct json_object *). All backends expose the same functions with their own value type.

struct csonpath *csonpath_new(const char *path);

Create and initialize a new csonpath object.

int csonpath_set_path(struct csonpath *p, const char *path);

Change the path of an existing object.

int csonpath_compile(struct csonpath *p);

Compile the path expression. This is optional—paths are compiled automatically on first use—but explicit compilation can help catch syntax errors earlier.

void csonpath_print_instruction(struct csonpath *p);

Print the compiled bytecode instructions (useful for debugging).

struct json_object *csonpath_find_first(struct csonpath *p, struct json_object *json);

Return the first matching value, or NULL if none is found.

struct json_object *csonpath_find_all(struct csonpath *p, struct json_object *json);

Return a new json_object array containing all matches. Must be freed with json_object_put().

int csonpath_remove(struct csonpath *p, struct json_object *json);

Remove all matching elements. Returns the number of elements removed.

int csonpath_update_or_create(struct csonpath *p, struct json_object *json, struct json_object *new_val);

Replace matching values with new_val, or create the path if it does not exist.

int csonpath_callback(struct csonpath *p, struct json_object *json,
                      json_c_callback callback, void *userdata);

Invoke callback for every match.

int csonpath_update_or_create_callback(struct csonpath *p, struct json_object *json,
                                       json_c_callback callback, void *userdata);

Like callback, but traverses the path while updating/creating missing intermediate objects.

void csonpath_destroy(struct csonpath *p);

Free the csonpath object.


📗 Python API Reference

  • CsonPath(path, return_empty_array=False, jq_like=False) — Create a new csonpath object. Optional flags: return_empty_array returns [] instead of None when find_all() finds nothing; jq_like allows jq-style paths without a leading $.
  • set_path(path) — Change the path expression.
  • find_first(json) — Return the first match, or None.
  • find_all(json) — Return a list of all matches, or None (or [] if configured).
  • remove(json) — Remove all matches. Returns the number of removed items.
  • update_or_create(json, value) — Replace matches with value, or create the path.
  • callback(json, callback, callback_data=None) — Call callback(parent, idx, current, callback_data) for every match.
  • update_or_create_callback(json, callback, callback_data=None) — Same as callback, but creates missing parent objects along the path.

🖥️ CLI

A standalone C CLI is available in cli/csonpath_cli.c. It links directly against json-c and the csonpath C core, so it works without Python.

make csonpath          # build ./csonpath
make tests-cli         # run shell tests

It reads JSON from stdin, a file (-f), or a string (-s) and exposes the library operations through action flags.

# Get the first match (default)
echo '{"a": "value", "array": [1, 2, 3]}' | ./csonpath '$.a'
# => "value"

# Find all matches
echo '{"items": [{"price": 5}, {"price": 15}]}' | ./csonpath -a '$.items[?price > 10]'
# => [{"price":15}]

# One match per line
echo '{"array": [1, 2, 3]}' | ./csonpath -a -o lines '$.array[*]'
# => 1
# => 2
# => 3

# Set or create a value
echo '{"a": 1}' | ./csonpath --set '42' '$.x.y.z'
# => {"a":1,"x":{"y":{"z":42}}}

# Or with a positional value
echo '{"a": 1}' | ./csonpath '$.x.y.z' '42'

# Remove matches
echo '{"a": 1, "b": 2}' | ./csonpath -d '$.b'
# => {"a":1}

# Edit a file in place
./csonpath -f data.json -p -i --set '"2.0"' '$.version'

Options

Option Description
-a, --all Return all matches instead of the first one.
-d, --delete Remove matches and print the modified JSON.
--set VALUE Set PATH to VALUE (JSON).
VALUE (positional) Alternative to --set.
-r, --raw Treat the value as a raw string.
-i, --in-place Edit FILE in place (requires --file).
--strict Exit with an error if --delete removes nothing.
-f FILE, --file FILE Read JSON from FILE instead of stdin.
-s JSON, --string JSON Read JSON from a string.
-j, --jq-like Allow jq-style paths without a leading $.
-p, --pretty Pretty-print JSON output.
-o {json,pretty,raw,lines} Output format (default: json).
-e, --empty-array Return [] instead of nothing when -a matches nothing.

Exit codes

Code Meaning
0 Success.
1 No match found, or --strict delete found nothing.
EINVAL Usage error, JSON parse error, JSONPath compilation error, or invalid JSON value.
errno I/O error (e.g. ENOENT, EACCES).

🧩 Using Multiple Backends

You can include more than one csonpath backend in the same translation unit, provided you prefix all but one of them to avoid symbol collisions. Define CSONPATH_USE_PREFIX before a backend header to prefix its API with the backend name.

For example, to use both json-c and the immutable yyjson backend in the same file:

#define CSONPATH_USE_PREFIX
#include "csonpath_json-c.h"

#undef CSONPATH_USE_PREFIX
#include "csonpath_yyjson_const.h"

To use both yyjson backends (immutable and mutable) together, include the aggregator header:

#include "csonpath_yyjson.h"

When using the mutable yyjson backend, assign the document pointer to backend_ctx before mutable operations:

struct csonpath *mp = yyjson_mut_csonpath_new("$.a");
mp->backend_ctx = mdoc;
yyjson_mut_csonpath_update_or_create(mp, mroot, new_val);

🔌 Custom Backends

csonpath is designed to be backend-agnostic. The C core manipulates opaque value pointers through a small set of macros, so it can work with any data structure that supports array, object, and scalar semantics.

A backend is an adapter that implements those macros for a concrete value representation. To create a custom backend, define the required macros and types in a header file (similar to csonpath_json-c.h or csonpath_yyjson.h), then include your backend header before csonpath.h.

Existing backend adapters:

  • csonpath_json-c.h — json-c values
  • csonpath_yyjson.h — yyjson values
  • csonpath_python.c — Python C API values (used by the Python bindings)
  • rust/csonpath_rust_backend.h — Rust serde_json::Value (used by the Rust crate)

🧪 Running Tests

C Tests

make tests-c

Rust Tests

cd rust
cargo test

Python Tests

make tests-py

All Tests

make tests

📁 Directory Structure

File / Directory Description
csonpath.h, csonpath_do.h Core implementation (header-only style)
csonpath_json-c.h json-c backend
csonpath_yyjson.h yyjson backend
csonpath_python.c Python C extension backend
rust/ Rust crate and its C backend adapter
csonpath_my_fuzzing.h Fuzzer helpers (C)
tests/ C and Python test suites
bench/ Performance benchmarks

🤝 Contributing

We welcome contributions!

Please read our Contributing Guidelines and Code of Conduct before submitting a pull request.

Feel free to open issues or pull requests!


📜 License

BSD 3-Clause. See LICENSE.

About

Recode of jsonpath-ng

Topics

Resources

Code of conduct

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages