csonpath 0.16.0__tar.gz → 0.17.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {csonpath-0.16.0 → csonpath-0.17.0}/Makefile +14 -4
  2. {csonpath-0.16.0/csonpath.egg-info → csonpath-0.17.0}/PKG-INFO +98 -2
  3. {csonpath-0.16.0 → csonpath-0.17.0}/README.md +97 -1
  4. {csonpath-0.16.0 → csonpath-0.17.0/csonpath.egg-info}/PKG-INFO +98 -2
  5. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath.egg-info/SOURCES.txt +1 -0
  6. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath.h +41 -16
  7. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath_do.h +2 -2
  8. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath_json-c.h +10 -0
  9. csonpath-0.17.0/csonpath_my_fuzzing.h +440 -0
  10. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath_python.c +106 -0
  11. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath_yyjson.h +2 -0
  12. {csonpath-0.16.0 → csonpath-0.17.0}/pyproject.toml +2 -2
  13. {csonpath-0.16.0 → csonpath-0.17.0}/LICENSE +0 -0
  14. {csonpath-0.16.0 → csonpath-0.17.0}/MANIFEST.in +0 -0
  15. {csonpath-0.16.0 → csonpath-0.17.0}/configure +0 -0
  16. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath.egg-info/dependency_links.txt +0 -0
  17. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath.egg-info/requires.txt +0 -0
  18. {csonpath-0.16.0 → csonpath-0.17.0}/csonpath.egg-info/top_level.txt +0 -0
  19. {csonpath-0.16.0 → csonpath-0.17.0}/setup.cfg +0 -0
  20. {csonpath-0.16.0 → csonpath-0.17.0}/setup.py +0 -0
  21. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/.git +0 -0
  22. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/.github/workflows/c-cpp.yml +0 -0
  23. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/.gitignore +0 -0
  24. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/LICENSE +0 -0
  25. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/Makefile +0 -0
  26. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/README.md +0 -0
  27. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/clib.json +0 -0
  28. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/formal_verification.md +0 -0
  29. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/re.c +0 -0
  30. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/re.h +0 -0
  31. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/scripts/exrex.py +0 -0
  32. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/scripts/regex_test.py +0 -0
  33. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/scripts/regex_test_neg.py +0 -0
  34. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test1.c +0 -0
  35. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test2.c +0 -0
  36. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test3.c +0 -0
  37. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test_compile.c +0 -0
  38. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test_print.c +0 -0
  39. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test_rand.c +0 -0
  40. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test_rand_neg.c +0 -0
  41. {csonpath-0.16.0 → csonpath-0.17.0}/tiny-regex-c/tests/test_thread.c +0 -0
@@ -5,7 +5,7 @@ YYJSON_LDFLAGS=$(shell pkg-config --libs yyjson)
5
5
 
6
6
  include config.mk
7
7
 
8
- all: test-json-c-get-a test-json-update test-json-filter test-json-subpath test-json-c-array-root test-json-filter-and-missing-key test-json-get-array-big-index test-json-union test-json-audit-bugs test-json-crash-vectors
8
+ all: test-json-c-get-a test-json-update test-json-filter test-json-subpath test-json-c-array-root test-json-filter-and-missing-key test-json-get-array-big-index test-json-union test-json-audit-bugs test-json-crash-vectors test-json-my-fuzz
9
9
 
10
10
  YYJSON_TESTS=test-yyjson
11
11
 
@@ -15,7 +15,7 @@ bench:
15
15
  bench-clean:
16
16
  make -C bench clean
17
17
 
18
- .PHONY: all clean tests pip-dev pip-dev bench bench-clean
18
+ .PHONY: all clean tests pip-dev pip-dev bench bench-clean tests-cli
19
19
 
20
20
  CFLAGS+= -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -O0 -g
21
21
 
@@ -49,10 +49,13 @@ test-json-audit-bugs: tests/json-c/audit-bugs.c csonpath_json-c.h csonpath.h cso
49
49
  test-json-crash-vectors: tests/json-c/crash-vectors.c csonpath_json-c.h csonpath.h csonpath_do.h
50
50
  $(CC) tests/json-c/crash-vectors.c $(EXTRA_FILES) $(JSON_C_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-json-crash-vectors $(JSON_C_LDFLAGS) $(LDFLAGS)
51
51
 
52
+ test-json-my-fuzz: tests/json-c/my_fuzz.c csonpath_json-c.h csonpath_my_fuzzing.h csonpath.h csonpath_do.h
53
+ $(CC) tests/json-c/my_fuzz.c $(EXTRA_FILES) $(JSON_C_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-json-my-fuzz $(JSON_C_LDFLAGS) $(LDFLAGS)
54
+
52
55
  test-yyjson: tests/yyjson/test-yyjson.c csonpath_yyjson.h csonpath.h csonpath_do.h
53
56
  $(CC) tests/yyjson/test-yyjson.c $(EXTRA_FILES) $(YYJSON_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-yyjson $(YYJSON_LDFLAGS) $(LDFLAGS)
54
57
 
55
- tests-c: test-json-c-get-a test-json-update test-json-filter test-json-subpath test-json-c-array-root test-json-filter-and-missing-key test-json-get-array-big-index test-json-union test-json-audit-bugs test-json-crash-vectors test-yyjson
58
+ tests-c: test-json-c-get-a test-json-update test-json-filter test-json-subpath test-json-c-array-root test-json-filter-and-missing-key test-json-get-array-big-index test-json-union test-json-audit-bugs test-json-crash-vectors test-yyjson test-json-my-fuzz
56
59
  ./test-json-c-get-a
57
60
  ./test-json-update
58
61
  ./test-json-filter
@@ -64,6 +67,13 @@ test-yyjson: tests/yyjson/test-yyjson.c csonpath_yyjson.h csonpath.h csonpath_do
64
67
  ./test-json-audit-bugs
65
68
  ./test-json-crash-vectors
66
69
  ./test-yyjson
70
+ ./test-json-my-fuzz
71
+
72
+ csonpath: cli/csonpath_cli.c csonpath_json-c.h csonpath.h csonpath_do.h
73
+ $(CC) cli/csonpath_cli.c $(EXTRA_FILES) $(JSON_C_CFLAGS) $(CFLAGS) -I./ -o csonpath $(JSON_C_LDFLAGS) $(LDFLAGS)
74
+
75
+ tests-cli: csonpath
76
+ bash tests/cli/run.sh
67
77
 
68
78
  pip-dev:
69
79
  pip install -e .[dev] --force-reinstall
@@ -74,5 +84,5 @@ tests-py: pip-dev
74
84
  tests: tests-py tests-c
75
85
 
76
86
  clean:
77
- rm -rvf test-json-c-get-a test-json-update test-json-filter test-json-subpath test-json-c-array-root test-json-filter-and-missing-key test-json-get-array-big-index test-json-union test-json-audit-bugs test-json-crash-vectors test-yyjson
87
+ rm -rvf test-json-c-get-a test-json-update test-json-filter test-json-subpath test-json-c-array-root test-json-filter-and-missing-key test-json-get-array-big-index test-json-union test-json-audit-bugs test-json-crash-vectors test-yyjson test-json-my-fuzz csonpath
78
88
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csonpath
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Description-Content-Type: text/markdown
5
5
  License-File: LICENSE
6
6
  Provides-Extra: dev
@@ -15,7 +15,7 @@ Dynamic: license-file
15
15
 
16
16
  **csonpath** is a partial [JSONPath](https://goessner.net/articles/JsonPath/) implementation in C, with Python bindings. It allows you to query, update, and remove data from JSON objects using path expressions.
17
17
 
18
- 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](https://github.com/json-c/json-c) and Python are provided.
18
+ 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](https://github.com/json-c/json-c), [yyjson](https://github.com/ibireme/yyjson), Python and Rust (via `serde_json`) are provided.
19
19
 
20
20
  ---
21
21
 
@@ -62,6 +62,7 @@ Unlike many JSONPath libraries, csonpath is **backend-agnostic**: it can work wi
62
62
  - [Usage](#-usage)
63
63
  - [C API Reference](#-c-api-reference)
64
64
  - [Python API Reference](#-python-api-reference)
65
+ - [CLI](#%EF%B8%8F-cli)
65
66
  - [Custom Backends](#-custom-backends)
66
67
  - [Running Tests](#-running-tests)
67
68
  - [Directory Structure](#-directory-structure)
@@ -87,6 +88,20 @@ Make sure to link against `json-c` when compiling:
87
88
  gcc myapp.c -o myapp $(pkg-config --cflags --libs json-c)
88
89
  ```
89
90
 
91
+ ### C (Rust backend)
92
+ The Rust backend is located in `rust/`. It builds both a safe Rust API and the C glue required by the csonpath core.
93
+
94
+ ```sh
95
+ cd rust
96
+ cargo build
97
+ cargo test
98
+ ```
99
+
100
+ To use it from C, include the backend header and link against the produced static library:
101
+ ```c
102
+ #include "rust/csonpath_rust_backend.h"
103
+ ```
104
+
90
105
  ### Python
91
106
  Install from PyPI:
92
107
  ```sh
@@ -252,6 +267,76 @@ Free the csonpath object.
252
267
 
253
268
  ---
254
269
 
270
+ ## 🖥️ CLI
271
+
272
+ 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.
273
+
274
+ ```sh
275
+ make csonpath # build ./csonpath
276
+ make tests-cli # run shell tests
277
+ ```
278
+
279
+ It reads JSON from stdin, a file (`-f`), or a string (`-s`) and exposes the library operations through action flags.
280
+
281
+ ```sh
282
+ # Get the first match (default)
283
+ echo '{"a": "value", "array": [1, 2, 3]}' | ./csonpath '$.a'
284
+ # => "value"
285
+
286
+ # Find all matches
287
+ echo '{"items": [{"price": 5}, {"price": 15}]}' | ./csonpath -a '$.items[?price > 10]'
288
+ # => [{"price":15}]
289
+
290
+ # One match per line
291
+ echo '{"array": [1, 2, 3]}' | ./csonpath -a -o lines '$.array[*]'
292
+ # => 1
293
+ # => 2
294
+ # => 3
295
+
296
+ # Set or create a value
297
+ echo '{"a": 1}' | ./csonpath --set '42' '$.x.y.z'
298
+ # => {"a":1,"x":{"y":{"z":42}}}
299
+
300
+ # Or with a positional value
301
+ echo '{"a": 1}' | ./csonpath '$.x.y.z' '42'
302
+
303
+ # Remove matches
304
+ echo '{"a": 1, "b": 2}' | ./csonpath -d '$.b'
305
+ # => {"a":1}
306
+
307
+ # Edit a file in place
308
+ ./csonpath -f data.json -p -i --set '"2.0"' '$.version'
309
+ ```
310
+
311
+ ### Options
312
+
313
+ | Option | Description |
314
+ |---|---|
315
+ | `-a`, `--all` | Return all matches instead of the first one. |
316
+ | `-d`, `--delete` | Remove matches and print the modified JSON. |
317
+ | `--set VALUE` | Set `PATH` to `VALUE` (JSON). |
318
+ | `VALUE` (positional) | Alternative to `--set`. |
319
+ | `-r`, `--raw` | Treat the value as a raw string. |
320
+ | `-i`, `--in-place` | Edit `FILE` in place (requires `--file`). |
321
+ | `--strict` | Exit with an error if `--delete` removes nothing. |
322
+ | `-f FILE`, `--file FILE` | Read JSON from `FILE` instead of stdin. |
323
+ | `-s JSON`, `--string JSON` | Read JSON from a string. |
324
+ | `-j`, `--jq-like` | Allow jq-style paths without a leading `$`. |
325
+ | `-p`, `--pretty` | Pretty-print JSON output. |
326
+ | `-o {json,pretty,raw,lines}` | Output format (default: `json`). |
327
+ | `-e`, `--empty-array` | Return `[]` instead of nothing when `-a` matches nothing. |
328
+
329
+ ### Exit codes
330
+
331
+ | Code | Meaning |
332
+ |---|---|
333
+ | `0` | Success. |
334
+ | `1` | No match found, or `--strict` delete found nothing. |
335
+ | `EINVAL` | Usage error, JSON parse error, JSONPath compilation error, or invalid JSON value. |
336
+ | `errno` | I/O error (e.g. `ENOENT`, `EACCES`). |
337
+
338
+ ---
339
+
255
340
  ## 🔌 Custom Backends
256
341
 
257
342
  csonpath is designed to be backend-agnostic. It can work with any data structure that supports array, object, and scalar semantics.
@@ -260,7 +345,9 @@ To create a custom backend, define the required macros and types in a header fil
260
345
 
261
346
  For more details, see the existing backend implementations:
262
347
  - `csonpath_json-c.h` — json-c backend
348
+ - `csonpath_yyjson.h` — yyjson backend
263
349
  - `csonpath_python.c` — Python backend
350
+ - `rust/csonpath_rust_backend.h` — Rust / serde_json backend
264
351
 
265
352
  ---
266
353
 
@@ -271,6 +358,12 @@ For more details, see the existing backend implementations:
271
358
  make tests-c
272
359
  ```
273
360
 
361
+ ### Rust Tests
362
+ ```sh
363
+ cd rust
364
+ cargo test
365
+ ```
366
+
274
367
  ### Python Tests
275
368
  ```sh
276
369
  make tests-py
@@ -289,7 +382,10 @@ make tests
289
382
  |---|---|
290
383
  | `csonpath.h`, `csonpath_do.h` | Core implementation (header-only style) |
291
384
  | `csonpath_json-c.h` | json-c backend |
385
+ | `csonpath_yyjson.h` | yyjson backend |
292
386
  | `csonpath_python.c` | Python C extension backend |
387
+ | `rust/` | Rust backend (safe API, FFI, C glue) |
388
+ | `csonpath_my_fuzzing.h` | Fuzzer helpers (C) |
293
389
  | `tests/` | C and Python test suites |
294
390
  | `bench/` | Performance benchmarks |
295
391
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **csonpath** is a partial [JSONPath](https://goessner.net/articles/JsonPath/) implementation in C, with Python bindings. It allows you to query, update, and remove data from JSON objects using path expressions.
8
8
 
9
- 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](https://github.com/json-c/json-c) and Python are provided.
9
+ 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](https://github.com/json-c/json-c), [yyjson](https://github.com/ibireme/yyjson), Python and Rust (via `serde_json`) are provided.
10
10
 
11
11
  ---
12
12
 
@@ -53,6 +53,7 @@ Unlike many JSONPath libraries, csonpath is **backend-agnostic**: it can work wi
53
53
  - [Usage](#-usage)
54
54
  - [C API Reference](#-c-api-reference)
55
55
  - [Python API Reference](#-python-api-reference)
56
+ - [CLI](#%EF%B8%8F-cli)
56
57
  - [Custom Backends](#-custom-backends)
57
58
  - [Running Tests](#-running-tests)
58
59
  - [Directory Structure](#-directory-structure)
@@ -78,6 +79,20 @@ Make sure to link against `json-c` when compiling:
78
79
  gcc myapp.c -o myapp $(pkg-config --cflags --libs json-c)
79
80
  ```
80
81
 
82
+ ### C (Rust backend)
83
+ The Rust backend is located in `rust/`. It builds both a safe Rust API and the C glue required by the csonpath core.
84
+
85
+ ```sh
86
+ cd rust
87
+ cargo build
88
+ cargo test
89
+ ```
90
+
91
+ To use it from C, include the backend header and link against the produced static library:
92
+ ```c
93
+ #include "rust/csonpath_rust_backend.h"
94
+ ```
95
+
81
96
  ### Python
82
97
  Install from PyPI:
83
98
  ```sh
@@ -243,6 +258,76 @@ Free the csonpath object.
243
258
 
244
259
  ---
245
260
 
261
+ ## 🖥️ CLI
262
+
263
+ 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.
264
+
265
+ ```sh
266
+ make csonpath # build ./csonpath
267
+ make tests-cli # run shell tests
268
+ ```
269
+
270
+ It reads JSON from stdin, a file (`-f`), or a string (`-s`) and exposes the library operations through action flags.
271
+
272
+ ```sh
273
+ # Get the first match (default)
274
+ echo '{"a": "value", "array": [1, 2, 3]}' | ./csonpath '$.a'
275
+ # => "value"
276
+
277
+ # Find all matches
278
+ echo '{"items": [{"price": 5}, {"price": 15}]}' | ./csonpath -a '$.items[?price > 10]'
279
+ # => [{"price":15}]
280
+
281
+ # One match per line
282
+ echo '{"array": [1, 2, 3]}' | ./csonpath -a -o lines '$.array[*]'
283
+ # => 1
284
+ # => 2
285
+ # => 3
286
+
287
+ # Set or create a value
288
+ echo '{"a": 1}' | ./csonpath --set '42' '$.x.y.z'
289
+ # => {"a":1,"x":{"y":{"z":42}}}
290
+
291
+ # Or with a positional value
292
+ echo '{"a": 1}' | ./csonpath '$.x.y.z' '42'
293
+
294
+ # Remove matches
295
+ echo '{"a": 1, "b": 2}' | ./csonpath -d '$.b'
296
+ # => {"a":1}
297
+
298
+ # Edit a file in place
299
+ ./csonpath -f data.json -p -i --set '"2.0"' '$.version'
300
+ ```
301
+
302
+ ### Options
303
+
304
+ | Option | Description |
305
+ |---|---|
306
+ | `-a`, `--all` | Return all matches instead of the first one. |
307
+ | `-d`, `--delete` | Remove matches and print the modified JSON. |
308
+ | `--set VALUE` | Set `PATH` to `VALUE` (JSON). |
309
+ | `VALUE` (positional) | Alternative to `--set`. |
310
+ | `-r`, `--raw` | Treat the value as a raw string. |
311
+ | `-i`, `--in-place` | Edit `FILE` in place (requires `--file`). |
312
+ | `--strict` | Exit with an error if `--delete` removes nothing. |
313
+ | `-f FILE`, `--file FILE` | Read JSON from `FILE` instead of stdin. |
314
+ | `-s JSON`, `--string JSON` | Read JSON from a string. |
315
+ | `-j`, `--jq-like` | Allow jq-style paths without a leading `$`. |
316
+ | `-p`, `--pretty` | Pretty-print JSON output. |
317
+ | `-o {json,pretty,raw,lines}` | Output format (default: `json`). |
318
+ | `-e`, `--empty-array` | Return `[]` instead of nothing when `-a` matches nothing. |
319
+
320
+ ### Exit codes
321
+
322
+ | Code | Meaning |
323
+ |---|---|
324
+ | `0` | Success. |
325
+ | `1` | No match found, or `--strict` delete found nothing. |
326
+ | `EINVAL` | Usage error, JSON parse error, JSONPath compilation error, or invalid JSON value. |
327
+ | `errno` | I/O error (e.g. `ENOENT`, `EACCES`). |
328
+
329
+ ---
330
+
246
331
  ## 🔌 Custom Backends
247
332
 
248
333
  csonpath is designed to be backend-agnostic. It can work with any data structure that supports array, object, and scalar semantics.
@@ -251,7 +336,9 @@ To create a custom backend, define the required macros and types in a header fil
251
336
 
252
337
  For more details, see the existing backend implementations:
253
338
  - `csonpath_json-c.h` — json-c backend
339
+ - `csonpath_yyjson.h` — yyjson backend
254
340
  - `csonpath_python.c` — Python backend
341
+ - `rust/csonpath_rust_backend.h` — Rust / serde_json backend
255
342
 
256
343
  ---
257
344
 
@@ -262,6 +349,12 @@ For more details, see the existing backend implementations:
262
349
  make tests-c
263
350
  ```
264
351
 
352
+ ### Rust Tests
353
+ ```sh
354
+ cd rust
355
+ cargo test
356
+ ```
357
+
265
358
  ### Python Tests
266
359
  ```sh
267
360
  make tests-py
@@ -280,7 +373,10 @@ make tests
280
373
  |---|---|
281
374
  | `csonpath.h`, `csonpath_do.h` | Core implementation (header-only style) |
282
375
  | `csonpath_json-c.h` | json-c backend |
376
+ | `csonpath_yyjson.h` | yyjson backend |
283
377
  | `csonpath_python.c` | Python C extension backend |
378
+ | `rust/` | Rust backend (safe API, FFI, C glue) |
379
+ | `csonpath_my_fuzzing.h` | Fuzzer helpers (C) |
284
380
  | `tests/` | C and Python test suites |
285
381
  | `bench/` | Performance benchmarks |
286
382
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csonpath
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Description-Content-Type: text/markdown
5
5
  License-File: LICENSE
6
6
  Provides-Extra: dev
@@ -15,7 +15,7 @@ Dynamic: license-file
15
15
 
16
16
  **csonpath** is a partial [JSONPath](https://goessner.net/articles/JsonPath/) implementation in C, with Python bindings. It allows you to query, update, and remove data from JSON objects using path expressions.
17
17
 
18
- 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](https://github.com/json-c/json-c) and Python are provided.
18
+ 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](https://github.com/json-c/json-c), [yyjson](https://github.com/ibireme/yyjson), Python and Rust (via `serde_json`) are provided.
19
19
 
20
20
  ---
21
21
 
@@ -62,6 +62,7 @@ Unlike many JSONPath libraries, csonpath is **backend-agnostic**: it can work wi
62
62
  - [Usage](#-usage)
63
63
  - [C API Reference](#-c-api-reference)
64
64
  - [Python API Reference](#-python-api-reference)
65
+ - [CLI](#%EF%B8%8F-cli)
65
66
  - [Custom Backends](#-custom-backends)
66
67
  - [Running Tests](#-running-tests)
67
68
  - [Directory Structure](#-directory-structure)
@@ -87,6 +88,20 @@ Make sure to link against `json-c` when compiling:
87
88
  gcc myapp.c -o myapp $(pkg-config --cflags --libs json-c)
88
89
  ```
89
90
 
91
+ ### C (Rust backend)
92
+ The Rust backend is located in `rust/`. It builds both a safe Rust API and the C glue required by the csonpath core.
93
+
94
+ ```sh
95
+ cd rust
96
+ cargo build
97
+ cargo test
98
+ ```
99
+
100
+ To use it from C, include the backend header and link against the produced static library:
101
+ ```c
102
+ #include "rust/csonpath_rust_backend.h"
103
+ ```
104
+
90
105
  ### Python
91
106
  Install from PyPI:
92
107
  ```sh
@@ -252,6 +267,76 @@ Free the csonpath object.
252
267
 
253
268
  ---
254
269
 
270
+ ## 🖥️ CLI
271
+
272
+ 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.
273
+
274
+ ```sh
275
+ make csonpath # build ./csonpath
276
+ make tests-cli # run shell tests
277
+ ```
278
+
279
+ It reads JSON from stdin, a file (`-f`), or a string (`-s`) and exposes the library operations through action flags.
280
+
281
+ ```sh
282
+ # Get the first match (default)
283
+ echo '{"a": "value", "array": [1, 2, 3]}' | ./csonpath '$.a'
284
+ # => "value"
285
+
286
+ # Find all matches
287
+ echo '{"items": [{"price": 5}, {"price": 15}]}' | ./csonpath -a '$.items[?price > 10]'
288
+ # => [{"price":15}]
289
+
290
+ # One match per line
291
+ echo '{"array": [1, 2, 3]}' | ./csonpath -a -o lines '$.array[*]'
292
+ # => 1
293
+ # => 2
294
+ # => 3
295
+
296
+ # Set or create a value
297
+ echo '{"a": 1}' | ./csonpath --set '42' '$.x.y.z'
298
+ # => {"a":1,"x":{"y":{"z":42}}}
299
+
300
+ # Or with a positional value
301
+ echo '{"a": 1}' | ./csonpath '$.x.y.z' '42'
302
+
303
+ # Remove matches
304
+ echo '{"a": 1, "b": 2}' | ./csonpath -d '$.b'
305
+ # => {"a":1}
306
+
307
+ # Edit a file in place
308
+ ./csonpath -f data.json -p -i --set '"2.0"' '$.version'
309
+ ```
310
+
311
+ ### Options
312
+
313
+ | Option | Description |
314
+ |---|---|
315
+ | `-a`, `--all` | Return all matches instead of the first one. |
316
+ | `-d`, `--delete` | Remove matches and print the modified JSON. |
317
+ | `--set VALUE` | Set `PATH` to `VALUE` (JSON). |
318
+ | `VALUE` (positional) | Alternative to `--set`. |
319
+ | `-r`, `--raw` | Treat the value as a raw string. |
320
+ | `-i`, `--in-place` | Edit `FILE` in place (requires `--file`). |
321
+ | `--strict` | Exit with an error if `--delete` removes nothing. |
322
+ | `-f FILE`, `--file FILE` | Read JSON from `FILE` instead of stdin. |
323
+ | `-s JSON`, `--string JSON` | Read JSON from a string. |
324
+ | `-j`, `--jq-like` | Allow jq-style paths without a leading `$`. |
325
+ | `-p`, `--pretty` | Pretty-print JSON output. |
326
+ | `-o {json,pretty,raw,lines}` | Output format (default: `json`). |
327
+ | `-e`, `--empty-array` | Return `[]` instead of nothing when `-a` matches nothing. |
328
+
329
+ ### Exit codes
330
+
331
+ | Code | Meaning |
332
+ |---|---|
333
+ | `0` | Success. |
334
+ | `1` | No match found, or `--strict` delete found nothing. |
335
+ | `EINVAL` | Usage error, JSON parse error, JSONPath compilation error, or invalid JSON value. |
336
+ | `errno` | I/O error (e.g. `ENOENT`, `EACCES`). |
337
+
338
+ ---
339
+
255
340
  ## 🔌 Custom Backends
256
341
 
257
342
  csonpath is designed to be backend-agnostic. It can work with any data structure that supports array, object, and scalar semantics.
@@ -260,7 +345,9 @@ To create a custom backend, define the required macros and types in a header fil
260
345
 
261
346
  For more details, see the existing backend implementations:
262
347
  - `csonpath_json-c.h` — json-c backend
348
+ - `csonpath_yyjson.h` — yyjson backend
263
349
  - `csonpath_python.c` — Python backend
350
+ - `rust/csonpath_rust_backend.h` — Rust / serde_json backend
264
351
 
265
352
  ---
266
353
 
@@ -271,6 +358,12 @@ For more details, see the existing backend implementations:
271
358
  make tests-c
272
359
  ```
273
360
 
361
+ ### Rust Tests
362
+ ```sh
363
+ cd rust
364
+ cargo test
365
+ ```
366
+
274
367
  ### Python Tests
275
368
  ```sh
276
369
  make tests-py
@@ -289,7 +382,10 @@ make tests
289
382
  |---|---|
290
383
  | `csonpath.h`, `csonpath_do.h` | Core implementation (header-only style) |
291
384
  | `csonpath_json-c.h` | json-c backend |
385
+ | `csonpath_yyjson.h` | yyjson backend |
292
386
  | `csonpath_python.c` | Python C extension backend |
387
+ | `rust/` | Rust backend (safe API, FFI, C glue) |
388
+ | `csonpath_my_fuzzing.h` | Fuzzer helpers (C) |
293
389
  | `tests/` | C and Python test suites |
294
390
  | `bench/` | Performance benchmarks |
295
391
 
@@ -6,6 +6,7 @@ configure
6
6
  csonpath.h
7
7
  csonpath_do.h
8
8
  csonpath_json-c.h
9
+ csonpath_my_fuzzing.h
9
10
  csonpath_python.c
10
11
  csonpath_yyjson.h
11
12
  pyproject.toml
@@ -9,6 +9,12 @@
9
9
  # error "some definitions are missing"
10
10
  #endif
11
11
 
12
+ /* Backends that never allocate in GET_STR leave this empty; backends that do
13
+ * (e.g. Rust) define it to the cleanup attribute, see CSONPATH_CLEANUP_STR. */
14
+ #ifndef CSONPATH_CLEANUP_STR
15
+ # define CSONPATH_CLEANUP_STR
16
+ #endif
17
+
12
18
  #ifndef CSONPATH_NO_REGEX
13
19
  # if defined CSONPATH_TINY_REGEX
14
20
  # include <tiny-regex-c/re.h>
@@ -35,6 +41,10 @@ typedef regex_t csonpath_reg_t;
35
41
  #define CSONPATH_UNUSED __attribute__((unused))
36
42
  #define MAY_ALIAS __attribute__((__may_alias__))
37
43
 
44
+ #ifndef CSONPATH_STATINLINE
45
+ # define CSONPATH_STATINLINE static inline
46
+ #endif
47
+
38
48
  #ifndef CSONPATH_FOREACH
39
49
  # define CSONPATH_FOREACH(obj, el, code) \
40
50
  CSONPATH_FOREACH_EXT(obj, el, code, key_idx)
@@ -280,7 +290,7 @@ static inline struct csonpath_child_info *csonpath_child_info_set(struct csonpat
280
290
  return child_info;
281
291
  }
282
292
 
283
- static inline void csonpath_destroy(struct csonpath *cjp)
293
+ CSONPATH_STATINLINE void csonpath_destroy(struct csonpath *cjp)
284
294
  {
285
295
  if (!cjp)
286
296
  return;
@@ -314,7 +324,7 @@ static int csonpath_compile_do(struct csonpath *cjp, const char orig[static 1],
314
324
  int *inst_idx, int flag, int end_path, const char **end_sentinel);
315
325
 
316
326
 
317
- static inline struct csonpath *csonpath_new_ex(const char path[static 1], int flag) {
327
+ CSONPATH_STATINLINE struct csonpath *csonpath_new_ex(const char path[static 1], int flag) {
318
328
  /*
319
329
  * max inst is use so we know, we will never overflow.
320
330
  */
@@ -340,11 +350,11 @@ static inline struct csonpath *csonpath_new_ex(const char path[static 1], int fl
340
350
  return ret;
341
351
  }
342
352
 
343
- static inline struct csonpath *csonpath_new(const char path[static 1]) {
353
+ CSONPATH_STATINLINE struct csonpath *csonpath_new(const char path[static 1]) {
344
354
  return csonpath_new_ex(path, 0);
345
355
  }
346
356
 
347
- static inline struct csonpath *csonpath_set_path(struct csonpath cjp[static 1],
357
+ CSONPATH_STATINLINE struct csonpath *csonpath_set_path(struct csonpath cjp[static 1],
348
358
  const char path[static 1])
349
359
  {
350
360
  csonpath_destroy(cjp);
@@ -1524,7 +1534,9 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1524
1534
  return CSONPATH_GET_NUM(el2) != CSONPATH_GET_NUM(jret);
1525
1535
  }
1526
1536
  if (CSONPATH_IS_STR(el2) && CSONPATH_IS_STR(jret)) {
1527
- return (!!strcmp(CSONPATH_GET_STR(el2), CSONPATH_GET_STR(jret)));
1537
+ CSONPATH_CLEANUP_STR const char *s1 = CSONPATH_GET_STR(el2);
1538
+ CSONPATH_CLEANUP_STR const char *s2 = CSONPATH_GET_STR(jret);
1539
+ return (!!strcmp(s1, s2));
1528
1540
  }
1529
1541
  return 0;
1530
1542
  case CSONPATH_INST_FILTER_KEY_EQ:
@@ -1532,7 +1544,9 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1532
1544
  return CSONPATH_GET_NUM(el2) == CSONPATH_GET_NUM(jret);
1533
1545
  }
1534
1546
  if (CSONPATH_IS_STR(el2) && CSONPATH_IS_STR(jret)) {
1535
- return (!strcmp(CSONPATH_GET_STR(el2), CSONPATH_GET_STR(jret)));
1547
+ CSONPATH_CLEANUP_STR const char *s1 = CSONPATH_GET_STR(el2);
1548
+ CSONPATH_CLEANUP_STR const char *s2 = CSONPATH_GET_STR(jret);
1549
+ return (!strcmp(s1, s2));
1536
1550
  }
1537
1551
  return 0;
1538
1552
  case CSONPATH_INST_FILTER_KEY_SUPERIOR_EQ:
@@ -1545,9 +1559,13 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1545
1559
  }
1546
1560
  if (CSONPATH_IS_STR(el2) && CSONPATH_IS_STR(jret)) {
1547
1561
  if (operation == CSONPATH_INST_FILTER_KEY_SUPERIOR_EQ) {
1548
- return strcmp(CSONPATH_GET_STR(el2), CSONPATH_GET_STR(jret)) >= 0;
1562
+ CSONPATH_CLEANUP_STR const char *s1 = CSONPATH_GET_STR(el2);
1563
+ CSONPATH_CLEANUP_STR const char *s2 = CSONPATH_GET_STR(jret);
1564
+ return strcmp(s1, s2) >= 0;
1549
1565
  }
1550
- return strcmp(CSONPATH_GET_STR(el2), CSONPATH_GET_STR(jret)) > 0;
1566
+ CSONPATH_CLEANUP_STR const char *s1 = CSONPATH_GET_STR(el2);
1567
+ CSONPATH_CLEANUP_STR const char *s2 = CSONPATH_GET_STR(jret);
1568
+ return strcmp(s1, s2) > 0;
1551
1569
  }
1552
1570
  return 0;
1553
1571
  case CSONPATH_INST_FILTER_KEY_INFERIOR:
@@ -1560,9 +1578,13 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1560
1578
  }
1561
1579
  if (CSONPATH_IS_STR(el2) && CSONPATH_IS_STR(jret)) {
1562
1580
  if (operation == CSONPATH_INST_FILTER_KEY_INFERIOR_EQ) {
1563
- return strcmp(CSONPATH_GET_STR(el2), CSONPATH_GET_STR(jret)) <= 0;
1581
+ CSONPATH_CLEANUP_STR const char *s1 = CSONPATH_GET_STR(el2);
1582
+ CSONPATH_CLEANUP_STR const char *s2 = CSONPATH_GET_STR(jret);
1583
+ return strcmp(s1, s2) <= 0;
1564
1584
  }
1565
- return strcmp(CSONPATH_GET_STR(el2), CSONPATH_GET_STR(jret)) < 0;
1585
+ CSONPATH_CLEANUP_STR const char *s1 = CSONPATH_GET_STR(el2);
1586
+ CSONPATH_CLEANUP_STR const char *s2 = CSONPATH_GET_STR(jret);
1587
+ return strcmp(s1, s2) < 0;
1566
1588
  }
1567
1589
  return 0;
1568
1590
  default:
@@ -1586,10 +1608,11 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1586
1608
  if (operand_instruction == CSONPATH_INST_GET_OBJ) {
1587
1609
  if (!CSONPATH_IS_STR(el2))
1588
1610
  break;
1611
+ CSONPATH_CLEANUP_STR const char *s = CSONPATH_GET_STR(el2);
1589
1612
  if (operation == CSONPATH_INST_FILTER_KEY_SUPERIOR_EQ)
1590
- match = strcmp(CSONPATH_GET_STR(el2), *owalker) >= 0;
1613
+ match = strcmp(s, *owalker) >= 0;
1591
1614
  else
1592
- match = strcmp(CSONPATH_GET_STR(el2), *owalker) > 0;
1615
+ match = strcmp(s, *owalker) > 0;
1593
1616
  *owalker += strlen(*owalker) + 1;
1594
1617
  } else {
1595
1618
  if (!CSONPATH_IS_NUM(el2))
@@ -1609,10 +1632,11 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1609
1632
  if (operand_instruction == CSONPATH_INST_GET_OBJ) {
1610
1633
  if (!CSONPATH_IS_STR(el2))
1611
1634
  break;
1635
+ CSONPATH_CLEANUP_STR const char *s = CSONPATH_GET_STR(el2);
1612
1636
  if (operation == CSONPATH_INST_FILTER_KEY_INFERIOR_EQ)
1613
- match = strcmp(CSONPATH_GET_STR(el2), *owalker) <= 0;
1637
+ match = strcmp(s, *owalker) <= 0;
1614
1638
  else
1615
- match = strcmp(CSONPATH_GET_STR(el2), *owalker) < 0;
1639
+ match = strcmp(s, *owalker) < 0;
1616
1640
  *owalker += strlen(*owalker) + 1;
1617
1641
  } else {
1618
1642
  if (!CSONPATH_IS_NUM(el2))
@@ -1636,10 +1660,11 @@ static _Bool csonpath_make_match(const struct csonpath cjp[const static 1],
1636
1660
  if (CSONPATH_IS_STR(el2)) {
1637
1661
  int regex_idx = **owalker;
1638
1662
  ++*owalker;
1663
+ CSONPATH_CLEANUP_STR const char *s = CSONPATH_GET_STR(el2);
1639
1664
  # if !defined CSONPATH_PCRE2
1640
- match = csonpath_reg_exec(cjp->regexs[regex_idx], CSONPATH_GET_STR(el2));
1665
+ match = csonpath_reg_exec(cjp->regexs[regex_idx], s);
1641
1666
  # else
1642
- const unsigned char *str = (const unsigned char *)CSONPATH_GET_STR(el2);
1667
+ const unsigned char *str = (const unsigned char *)s;
1643
1668
  int ret = pcre2_match(cjp->regexs[regex_idx], str, PCRE2_ZERO_TERMINATED, 0,
1644
1669
  0, cjp->match_datas[regex_idx], NULL);
1645
1670
 
@@ -384,7 +384,7 @@ static CSONPATH_DO_RET_TYPE csonpath_do_internal(const struct csonpath cjp[const
384
384
  }
385
385
  CSONPATH_DO_POST_FIND_ARRAY;
386
386
  } else if (CSONPATH_IS_STR(jret)) {
387
- const char *this_idx = CSONPATH_GET_STR(jret);
387
+ CSONPATH_CLEANUP_STR const char *this_idx = CSONPATH_GET_STR(jret);
388
388
  CSONPATH_PRE_GET_OBJ(this_idx);
389
389
  tmp = CSONPATH_GET(tmp, this_idx);
390
390
  if (tmp == CSONPATH_NULL) {
@@ -462,7 +462,7 @@ static CSONPATH_DO_RET_TYPE csonpath_do_internal(const struct csonpath cjp[const
462
462
  #define csonpath_do__(name) CAT(csonpath_, name)
463
463
  #define csonpath_do_ csonpath_do__(CSONPATH_DO_FUNC_NAME)
464
464
 
465
- static CSONPATH_DO_RET_TYPE csonpath_do_(struct csonpath cjp[static 1],
465
+ CSONPATH_STATINLINE CSONPATH_DO_RET_TYPE csonpath_do_(struct csonpath cjp[static 1],
466
466
  CSONPATH_JSON value CSONPATH_DO_EXTRA_ARGS)
467
467
  {
468
468
  char *walker = cjp->data;