GitHub - outscale/csonpath: Recode of jsonpath-ng

GitHub

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

csonpath is a partial

JSONPath

implementation in C, with Python bindings. It allows you to query, update, and remove data from JSON objects using path expressions.

Unlike many JSONPath libraries, csonpath is backend-agnostic: it can work with any C library or environment that manipulates array, object, and scalar types—not just JSON. Out of the box, backends for

json-c

,

yyjson

, Python and Rust (via serde_json) are provided.

———
🚀 Features

JSONPath Syntax

FeatureExampleDescriptionDot notation$.a.bAccess nested object fieldsBracket notation$['a']['b']Alternative object/array accessArray index$.array[0]Access by zero-based indexWildcard [*]$.array[*].fieldIterate all array elementsRecursive descent ..$..nameSearch recursively for a keyUnion , (inside brackets)$['a','b'], $.array[0,1], $.items[?n==1, ?n==2]Match all listed selectors at onceOR fallback |$.a | $.bTry 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 elementsRegex filters$.items[?name =~ "foo"]POSIX regex-based filteringMultiple filters (&)$.items[?a=1 & b=2]Combine conditionsSubpath expressions$.obj[$.key]Use JSON values as dynamic path keys@ current object$.items[[email protected] > 10]Reference the current element in filtersOperations

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

Project website:

https://github.com/outscale/csonpath

Join our community on

Discord

———
📄 Table of Contents

Features

Installation

Usage

C API Reference

Python API Reference

CLI

Custom Backends

Running Tests

Directory Structure

Contributing

License

———
📦 Installation

Prerequisites

C compiler (gcc or clang)

json-c

library (for C usage)

Python 3.x (for Python bindings)

C (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 (Rust backend)

The Rust backend is located in rust/. It builds both a safe Rust API and the C glue required by the csonpath core.

cd rust cargo build cargo testTo use it from C, include the backend header and link against the produced static library:

#include"rust/csonpath_rust_backend.h"Python

Install from PyPI:

pip install csonpathTo install from source (development):

pip install .# or make pip-dev
———
🛠️ Usage

C (json-c)

#include"csonpath_json-c.h"staticvoidmy_cb(json_object*parent, structcsonpath_child_info*info, json_object*current, void*ud) { json_object_set_string(current, "modified"); } intmain(void) { structjson_object*jobj=json_tokener_parse(json_str); structcsonpath*p=csonpath_new("$.a"); /* Find First: return the first match, or NULL */structjson_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. */intremoved=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); return0; }Python

importcsonpathdata= {"a": "value", "array": [1, 2, 3]} p=csonpath.CsonPath("$.a") # Find First / Find Allp.find_first(data) # -> "value"p.find_all(data) # -> ["value"]# Remove: returns number of removed itemsp.set_path("$.array[*]") p.remove(data) # Update or Create: builds missing objects/arrays automaticallyp.set_path("$.x.y.z") p.update_or_create(data, []) # data is now {"a": "value", "array": [1, 2, 3], "x": {"y": {"z": []}}}# Callbackp.set_path("$.a") p.callback(data, lambdaparent, idx, cur, _: parent.__setitem__(idx, cur.upper())) # Update or Create Callback: creates parents, then calls cb on each leafp.set_path("$[*].a") p.update_or_create_callback(dst, my_sync_fn, userdata)
———
📘 C API Reference

structcsonpath*csonpath_new(constchar*path);Create and initialize a new csonpath object.

intcsonpath_set_path(structcsonpath*p, constchar*path);Change the path of an existing object.

intcsonpath_compile(structcsonpath*p);Compile the path expression. This is optional—paths are compiled automatically on first use—but explicit compilation can help catch syntax errors earlier.

voidcsonpath_print_instruction(structcsonpath*p);Print the compiled bytecode instructions (useful for debugging).

structjson_object*csonpath_find_first(structcsonpath*p, structjson_object*json);Return the first matching value, or NULL if none is found.

structjson_object*csonpath_find_all(structcsonpath*p, structjson_object*json);Return a new json_object array containing all matches. Must be freed with json_object_put().

intcsonpath_remove(structcsonpath*p, structjson_object*json);Remove all matching elements. Returns the number of elements removed.

intcsonpath_update_or_create(structcsonpath*p, structjson_object*json, structjson_object*new_val);Replace matching values with new_val, or create the path if it does not exist.

intcsonpath_callback(structcsonpath*p, structjson_object*json, json_c_callbackcallback, void*userdata);Invoke callback for every match.

intcsonpath_update_or_create_callback(structcsonpath*p, structjson_object*json, json_c_callbackcallback, void*userdata);Like callback, but traverses the path while updating/creating missing intermediate objects.

voidcsonpath_destroy(structcsonpath*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 testsIt 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 matchesecho'{"items": [{"price": 5}, {"price": 15}]}'| ./csonpath -a '$.items[?price > 10]'# => [{"price":15}]# One match per lineecho'{"array": [1, 2, 3]}'| ./csonpath -a -o lines '$.array[*]'# => 1# => 2# => 3# Set or create a valueecho'{"a": 1}'| ./csonpath --set '42''$.x.y.z'# => {"a":1,"x":{"y":{"z":42}}}# Or with a positional valueecho'{"a": 1}'| ./csonpath '$.x.y.z''42'# Remove matchesecho'{"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

OptionDescription-a, --allReturn all matches instead of the first one.-d, --deleteRemove matches and print the modified JSON.--set VALUESet PATH to VALUE (JSON).VALUE (positional)Alternative to --set.-r, --rawTreat the value as a raw string.-i, --in-placeEdit FILE in place (requires --file).--strictExit with an error if --delete removes nothing.-f FILE, --file FILERead JSON from FILE instead of stdin.-s JSON, --string JSONRead JSON from a string.-j, --jq-likeAllow jq-style paths without a leading $.-p, --prettyPretty-print JSON output.-o {json,pretty,raw,lines}Output format (default: json).-e, --empty-arrayReturn [] instead of nothing when -a matches nothing.Exit codes

CodeMeaning0Success.1No match found, or --strict delete found nothing.EINVALUsage error, JSON parse error, JSONPath compilation error, or invalid JSON value.errnoI/O error (e.g. ENOENT, EACCES).
———
🔌 Custom Backends

csonpath is designed to be backend-agnostic. It can work with any data structure that supports array, object, and scalar semantics.

To create a custom backend, define the required macros and types in a header file (similar to csonpath_json-c.h or csonpath_python.c), then include your backend header before csonpath.h. This allows you to adapt csonpath for manipulating data in any format that supports array/object semantics, giving you full flexibility beyond just JSON.

For more details, see the existing backend implementations:

csonpath_json-c.h — json-c backend

csonpath_yyjson.h — yyjson backend

csonpath_python.c — Python backend

rust/csonpath_rust_backend.h — Rust / serde_json backend

———
🧪 Running Tests

C Tests

make tests-cRust Tests

cd rust cargo testPython Tests

make tests-pyAll Tests

make tests
———
📁 Directory Structure

File / DirectoryDescriptioncsonpath.h, csonpath_do.hCore implementation (header-only style)csonpath_json-c.hjson-c backendcsonpath_yyjson.hyyjson backendcsonpath_python.cPython C extension backendrust/Rust backend (safe API, FFI, C glue)csonpath_my_fuzzing.hFuzzer helpers (C)tests/C and Python test suitesbench/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

.