csonpath 0.14.0__tar.gz → 0.16.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.
- {csonpath-0.14.0 → csonpath-0.16.0}/Makefile +23 -3
- csonpath-0.16.0/PKG-INFO +310 -0
- csonpath-0.16.0/README.md +301 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/configure +5 -0
- csonpath-0.16.0/csonpath.egg-info/PKG-INFO +310 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath.h +308 -120
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath_do.h +67 -17
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath_json-c.h +11 -9
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath_python.c +69 -33
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath_yyjson.h +20 -17
- {csonpath-0.14.0 → csonpath-0.16.0}/pyproject.toml +2 -2
- csonpath-0.16.0/setup.py +64 -0
- csonpath-0.14.0/PKG-INFO +0 -183
- csonpath-0.14.0/README.md +0 -174
- csonpath-0.14.0/csonpath.egg-info/PKG-INFO +0 -183
- csonpath-0.14.0/setup.py +0 -35
- {csonpath-0.14.0 → csonpath-0.16.0}/LICENSE +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/MANIFEST.in +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath.egg-info/SOURCES.txt +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath.egg-info/dependency_links.txt +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath.egg-info/requires.txt +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/csonpath.egg-info/top_level.txt +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/setup.cfg +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/.git +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/.github/workflows/c-cpp.yml +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/.gitignore +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/LICENSE +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/Makefile +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/README.md +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/clib.json +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/formal_verification.md +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/re.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/re.h +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/scripts/exrex.py +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/scripts/regex_test.py +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/scripts/regex_test_neg.py +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test1.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test2.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test3.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test_compile.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test_print.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test_rand.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test_rand_neg.c +0 -0
- {csonpath-0.14.0 → csonpath-0.16.0}/tiny-regex-c/tests/test_thread.c +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
JSON_C_CFLAGS=$(shell pkg-config --cflags json-c)
|
|
2
2
|
JSON_C_LDFLAGS=$(shell pkg-config --libs json-c)
|
|
3
|
+
YYJSON_CFLAGS=$(shell pkg-config --cflags yyjson)
|
|
4
|
+
YYJSON_LDFLAGS=$(shell pkg-config --libs yyjson)
|
|
3
5
|
|
|
4
6
|
include config.mk
|
|
5
7
|
|
|
6
|
-
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
|
|
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
|
|
9
|
+
|
|
10
|
+
YYJSON_TESTS=test-yyjson
|
|
7
11
|
|
|
8
12
|
bench:
|
|
9
13
|
make -C bench
|
|
@@ -36,7 +40,19 @@ test-json-filter-and-missing-key: tests/json-c/filter-and-missing-key.c csonpath
|
|
|
36
40
|
test-json-get-array-big-index: tests/json-c/get-array-big-index.c csonpath_json-c.h csonpath.h csonpath_do.h
|
|
37
41
|
$(CC) tests/json-c/get-array-big-index.c $(EXTRA_FILES) $(JSON_C_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-json-get-array-big-index $(JSON_C_LDFLAGS) $(LDFLAGS)
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
test-json-union: tests/json-c/union.c csonpath_json-c.h csonpath.h csonpath_do.h
|
|
44
|
+
$(CC) tests/json-c/union.c $(EXTRA_FILES) $(JSON_C_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-json-union $(JSON_C_LDFLAGS) $(LDFLAGS)
|
|
45
|
+
|
|
46
|
+
test-json-audit-bugs: tests/json-c/audit-bugs.c csonpath_json-c.h csonpath.h csonpath_do.h
|
|
47
|
+
$(CC) tests/json-c/audit-bugs.c $(EXTRA_FILES) $(JSON_C_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-json-audit-bugs $(JSON_C_LDFLAGS) $(LDFLAGS)
|
|
48
|
+
|
|
49
|
+
test-json-crash-vectors: tests/json-c/crash-vectors.c csonpath_json-c.h csonpath.h csonpath_do.h
|
|
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
|
+
|
|
52
|
+
test-yyjson: tests/yyjson/test-yyjson.c csonpath_yyjson.h csonpath.h csonpath_do.h
|
|
53
|
+
$(CC) tests/yyjson/test-yyjson.c $(EXTRA_FILES) $(YYJSON_CFLAGS) $(CFLAGS) -Wno-format -I./ -o test-yyjson $(YYJSON_LDFLAGS) $(LDFLAGS)
|
|
54
|
+
|
|
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
|
|
40
56
|
./test-json-c-get-a
|
|
41
57
|
./test-json-update
|
|
42
58
|
./test-json-filter
|
|
@@ -44,6 +60,10 @@ tests-c: test-json-c-get-a test-json-update test-json-filter test-json-subpath t
|
|
|
44
60
|
./test-json-c-array-root
|
|
45
61
|
./test-json-filter-and-missing-key
|
|
46
62
|
./test-json-get-array-big-index
|
|
63
|
+
./test-json-union
|
|
64
|
+
./test-json-audit-bugs
|
|
65
|
+
./test-json-crash-vectors
|
|
66
|
+
./test-yyjson
|
|
47
67
|
|
|
48
68
|
pip-dev:
|
|
49
69
|
pip install -e .[dev] --force-reinstall
|
|
@@ -54,5 +74,5 @@ tests-py: pip-dev
|
|
|
54
74
|
tests: tests-py tests-c
|
|
55
75
|
|
|
56
76
|
clean:
|
|
57
|
-
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
|
|
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
|
|
58
78
|
|
csonpath-0.16.0/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csonpath
|
|
3
|
+
Version: 0.16.0
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
8
|
+
Dynamic: license-file
|
|
9
|
+
|
|
10
|
+
# csonpath
|
|
11
|
+
|
|
12
|
+
> That's not my path, that's not your path, but **csonpath**.
|
|
13
|
+
|
|
14
|
+
[](https://docs.outscale.com/en/userguide/Open-Source-Projects.html)
|
|
15
|
+
|
|
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
|
+
|
|
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.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 Features
|
|
23
|
+
|
|
24
|
+
### JSONPath Syntax
|
|
25
|
+
|
|
26
|
+
| Feature | Example | Description |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Dot notation | `$.a.b` | Access nested object fields |
|
|
29
|
+
| Bracket notation | `$['a']['b']` | Alternative object/array access |
|
|
30
|
+
| Array index | `$.array[0]` | Access by zero-based index |
|
|
31
|
+
| Wildcard `[*]` | `$.array[*].field` | Iterate all array elements |
|
|
32
|
+
| Recursive descent `..` | `$..name` | Search recursively for a key |
|
|
33
|
+
| Union `,` (inside brackets) | `$['a','b']`, `$.array[0,1]`, `$.items[?n==1, ?n==2]` | Match all listed selectors at once |
|
|
34
|
+
| 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. |
|
|
35
|
+
| Filters | `$.items[?price > 10]` | Filter array elements |
|
|
36
|
+
| Regex filters | `$.items[?name =~ "foo"]` | POSIX regex-based filtering |
|
|
37
|
+
| Multiple filters (`&`) | `$.items[?a=1 & b=2]` | Combine conditions |
|
|
38
|
+
| Subpath expressions | `$.obj[$.key]` | Use JSON values as dynamic path keys |
|
|
39
|
+
| `@` current object | `$.items[?@.price > 10]` | Reference the current element in filters |
|
|
40
|
+
|
|
41
|
+
### Operations
|
|
42
|
+
- **Find First** — retrieve the first match.
|
|
43
|
+
- **Find All** — retrieve all matches (returns an array).
|
|
44
|
+
- **Update or Create** — modify existing values or create missing ones.
|
|
45
|
+
- **Remove** — delete matching elements.
|
|
46
|
+
- **Callback** — execute a custom callback on each match.
|
|
47
|
+
- **Update or Create Callback** — traverse the path, creating missing intermediate objects and arrays, then invoke the callback on each leaf node.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🌐 Links
|
|
52
|
+
|
|
53
|
+
- Project website: https://github.com/outscale/csonpath
|
|
54
|
+
- Join our community on [Discord](https://discord.gg/HUVtY5gT6s)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 📄 Table of Contents
|
|
59
|
+
|
|
60
|
+
- [Features](#-features)
|
|
61
|
+
- [Installation](#-installation)
|
|
62
|
+
- [Usage](#-usage)
|
|
63
|
+
- [C API Reference](#-c-api-reference)
|
|
64
|
+
- [Python API Reference](#-python-api-reference)
|
|
65
|
+
- [Custom Backends](#-custom-backends)
|
|
66
|
+
- [Running Tests](#-running-tests)
|
|
67
|
+
- [Directory Structure](#-directory-structure)
|
|
68
|
+
- [Contributing](#-contributing)
|
|
69
|
+
- [License](#-license)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 📦 Installation
|
|
74
|
+
|
|
75
|
+
### Prerequisites
|
|
76
|
+
- C compiler (`gcc` or `clang`)
|
|
77
|
+
- [json-c](https://github.com/json-c/json-c) library (for C usage)
|
|
78
|
+
- Python 3.x (for Python bindings)
|
|
79
|
+
|
|
80
|
+
### C (json-c)
|
|
81
|
+
Just include the header in your project. There is no separate install step required:
|
|
82
|
+
```c
|
|
83
|
+
#include "csonpath_json-c.h"
|
|
84
|
+
```
|
|
85
|
+
Make sure to link against `json-c` when compiling:
|
|
86
|
+
```sh
|
|
87
|
+
gcc myapp.c -o myapp $(pkg-config --cflags --libs json-c)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Python
|
|
91
|
+
Install from PyPI:
|
|
92
|
+
```sh
|
|
93
|
+
pip install csonpath
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To install from source (development):
|
|
97
|
+
```sh
|
|
98
|
+
pip install .
|
|
99
|
+
# or
|
|
100
|
+
make pip-dev
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🛠️ Usage
|
|
106
|
+
|
|
107
|
+
### C (json-c)
|
|
108
|
+
|
|
109
|
+
```c
|
|
110
|
+
#include "csonpath_json-c.h"
|
|
111
|
+
|
|
112
|
+
static void my_cb(json_object *parent, struct csonpath_child_info *info,
|
|
113
|
+
json_object *current, void *ud)
|
|
114
|
+
{
|
|
115
|
+
json_object_set_string(current, "modified");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
int main(void)
|
|
119
|
+
{
|
|
120
|
+
struct json_object *jobj = json_tokener_parse(json_str);
|
|
121
|
+
struct csonpath *p = csonpath_new("$.a");
|
|
122
|
+
|
|
123
|
+
/* Find First: return the first match, or NULL */
|
|
124
|
+
struct json_object *ret = csonpath_find_first(p, jobj);
|
|
125
|
+
|
|
126
|
+
/* Find All: return a NEW json_object array. Caller must free it. */
|
|
127
|
+
ret = csonpath_find_all(p, jobj);
|
|
128
|
+
json_object_put(ret);
|
|
129
|
+
|
|
130
|
+
/* Remove: delete matching keys (or set array slots to null). Returns count. */
|
|
131
|
+
int removed = csonpath_remove(p, jobj);
|
|
132
|
+
|
|
133
|
+
/* Update or Create: replace matches, or create the full path if missing. */
|
|
134
|
+
csonpath_update_or_create(p, jobj, json_object_new_string("new_value"));
|
|
135
|
+
|
|
136
|
+
/* Callback: call a user function for every match. */
|
|
137
|
+
csonpath_callback(p, jobj, my_cb, NULL);
|
|
138
|
+
|
|
139
|
+
/* Update or Create Callback: like callback, but creates missing parents first,
|
|
140
|
+
then invokes the callback on every leaf (existing or newly created). */
|
|
141
|
+
csonpath_update_or_create_callback(p, jobj, my_cb, NULL);
|
|
142
|
+
|
|
143
|
+
csonpath_destroy(p);
|
|
144
|
+
json_object_put(jobj);
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Python
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import csonpath
|
|
153
|
+
|
|
154
|
+
data = {"a": "value", "array": [1, 2, 3]}
|
|
155
|
+
p = csonpath.CsonPath("$.a")
|
|
156
|
+
|
|
157
|
+
# Find First / Find All
|
|
158
|
+
p.find_first(data) # -> "value"
|
|
159
|
+
p.find_all(data) # -> ["value"]
|
|
160
|
+
|
|
161
|
+
# Remove: returns number of removed items
|
|
162
|
+
p.set_path("$.array[*]")
|
|
163
|
+
p.remove(data)
|
|
164
|
+
|
|
165
|
+
# Update or Create: builds missing objects/arrays automatically
|
|
166
|
+
p.set_path("$.x.y.z")
|
|
167
|
+
p.update_or_create(data, [])
|
|
168
|
+
# data is now {"a": "value", "array": [1, 2, 3], "x": {"y": {"z": []}}}
|
|
169
|
+
|
|
170
|
+
# Callback
|
|
171
|
+
p.set_path("$.a")
|
|
172
|
+
p.callback(data, lambda parent, idx, cur, _: parent.__setitem__(idx, cur.upper()))
|
|
173
|
+
|
|
174
|
+
# Update or Create Callback: creates parents, then calls cb on each leaf
|
|
175
|
+
p.set_path("$[*].a")
|
|
176
|
+
p.update_or_create_callback(dst, my_sync_fn, userdata)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 📘 C API Reference
|
|
182
|
+
|
|
183
|
+
```c
|
|
184
|
+
struct csonpath *csonpath_new(const char *path);
|
|
185
|
+
```
|
|
186
|
+
Create and initialize a new csonpath object.
|
|
187
|
+
|
|
188
|
+
```c
|
|
189
|
+
int csonpath_set_path(struct csonpath *p, const char *path);
|
|
190
|
+
```
|
|
191
|
+
Change the path of an existing object.
|
|
192
|
+
|
|
193
|
+
```c
|
|
194
|
+
int csonpath_compile(struct csonpath *p);
|
|
195
|
+
```
|
|
196
|
+
Compile the path expression. This is optional—paths are compiled automatically on first use—but explicit compilation can help catch syntax errors earlier.
|
|
197
|
+
|
|
198
|
+
```c
|
|
199
|
+
void csonpath_print_instruction(struct csonpath *p);
|
|
200
|
+
```
|
|
201
|
+
Print the compiled bytecode instructions (useful for debugging).
|
|
202
|
+
|
|
203
|
+
```c
|
|
204
|
+
struct json_object *csonpath_find_first(struct csonpath *p, struct json_object *json);
|
|
205
|
+
```
|
|
206
|
+
Return the first matching value, or `NULL` if none is found.
|
|
207
|
+
|
|
208
|
+
```c
|
|
209
|
+
struct json_object *csonpath_find_all(struct csonpath *p, struct json_object *json);
|
|
210
|
+
```
|
|
211
|
+
Return a new `json_object` array containing all matches. **Must be freed with `json_object_put()`.**
|
|
212
|
+
|
|
213
|
+
```c
|
|
214
|
+
int csonpath_remove(struct csonpath *p, struct json_object *json);
|
|
215
|
+
```
|
|
216
|
+
Remove all matching elements. Returns the number of elements removed.
|
|
217
|
+
|
|
218
|
+
```c
|
|
219
|
+
int csonpath_update_or_create(struct csonpath *p, struct json_object *json, struct json_object *new_val);
|
|
220
|
+
```
|
|
221
|
+
Replace matching values with `new_val`, or create the path if it does not exist.
|
|
222
|
+
|
|
223
|
+
```c
|
|
224
|
+
int csonpath_callback(struct csonpath *p, struct json_object *json,
|
|
225
|
+
json_c_callback callback, void *userdata);
|
|
226
|
+
```
|
|
227
|
+
Invoke `callback` for every match.
|
|
228
|
+
|
|
229
|
+
```c
|
|
230
|
+
int csonpath_update_or_create_callback(struct csonpath *p, struct json_object *json,
|
|
231
|
+
json_c_callback callback, void *userdata);
|
|
232
|
+
```
|
|
233
|
+
Like `callback`, but traverses the path while updating/creating missing intermediate objects.
|
|
234
|
+
|
|
235
|
+
```c
|
|
236
|
+
void csonpath_destroy(struct csonpath *p);
|
|
237
|
+
```
|
|
238
|
+
Free the csonpath object.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 📗 Python API Reference
|
|
243
|
+
|
|
244
|
+
- **`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 `$`.
|
|
245
|
+
- **`set_path(path)`** — Change the path expression.
|
|
246
|
+
- **`find_first(json)`** — Return the first match, or `None`.
|
|
247
|
+
- **`find_all(json)`** — Return a list of all matches, or `None` (or `[]` if configured).
|
|
248
|
+
- **`remove(json)`** — Remove all matches. Returns the number of removed items.
|
|
249
|
+
- **`update_or_create(json, value)`** — Replace matches with `value`, or create the path.
|
|
250
|
+
- **`callback(json, callback, callback_data=None)`** — Call `callback(parent, idx, current, callback_data)` for every match.
|
|
251
|
+
- **`update_or_create_callback(json, callback, callback_data=None)`** — Same as `callback`, but creates missing parent objects along the path.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 🔌 Custom Backends
|
|
256
|
+
|
|
257
|
+
csonpath is designed to be backend-agnostic. It can work with any data structure that supports array, object, and scalar semantics.
|
|
258
|
+
|
|
259
|
+
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.
|
|
260
|
+
|
|
261
|
+
For more details, see the existing backend implementations:
|
|
262
|
+
- `csonpath_json-c.h` — json-c backend
|
|
263
|
+
- `csonpath_python.c` — Python backend
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 🧪 Running Tests
|
|
268
|
+
|
|
269
|
+
### C Tests
|
|
270
|
+
```sh
|
|
271
|
+
make tests-c
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Python Tests
|
|
275
|
+
```sh
|
|
276
|
+
make tests-py
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### All Tests
|
|
280
|
+
```sh
|
|
281
|
+
make tests
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 📁 Directory Structure
|
|
287
|
+
|
|
288
|
+
| File / Directory | Description |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `csonpath.h`, `csonpath_do.h` | Core implementation (header-only style) |
|
|
291
|
+
| `csonpath_json-c.h` | json-c backend |
|
|
292
|
+
| `csonpath_python.c` | Python C extension backend |
|
|
293
|
+
| `tests/` | C and Python test suites |
|
|
294
|
+
| `bench/` | Performance benchmarks |
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 🤝 Contributing
|
|
299
|
+
|
|
300
|
+
We welcome contributions!
|
|
301
|
+
|
|
302
|
+
Please read our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) before submitting a pull request.
|
|
303
|
+
|
|
304
|
+
Feel free to open issues or pull requests!
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 📜 License
|
|
309
|
+
|
|
310
|
+
BSD 3-Clause. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# csonpath
|
|
2
|
+
|
|
3
|
+
> That's not my path, that's not your path, but **csonpath**.
|
|
4
|
+
|
|
5
|
+
[](https://docs.outscale.com/en/userguide/Open-Source-Projects.html)
|
|
6
|
+
|
|
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
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🚀 Features
|
|
14
|
+
|
|
15
|
+
### JSONPath Syntax
|
|
16
|
+
|
|
17
|
+
| Feature | Example | Description |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Dot notation | `$.a.b` | Access nested object fields |
|
|
20
|
+
| Bracket notation | `$['a']['b']` | Alternative object/array access |
|
|
21
|
+
| Array index | `$.array[0]` | Access by zero-based index |
|
|
22
|
+
| Wildcard `[*]` | `$.array[*].field` | Iterate all array elements |
|
|
23
|
+
| Recursive descent `..` | `$..name` | Search recursively for a key |
|
|
24
|
+
| Union `,` (inside brackets) | `$['a','b']`, `$.array[0,1]`, `$.items[?n==1, ?n==2]` | Match all listed selectors at once |
|
|
25
|
+
| 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. |
|
|
26
|
+
| Filters | `$.items[?price > 10]` | Filter array elements |
|
|
27
|
+
| Regex filters | `$.items[?name =~ "foo"]` | POSIX regex-based filtering |
|
|
28
|
+
| Multiple filters (`&`) | `$.items[?a=1 & b=2]` | Combine conditions |
|
|
29
|
+
| Subpath expressions | `$.obj[$.key]` | Use JSON values as dynamic path keys |
|
|
30
|
+
| `@` current object | `$.items[?@.price > 10]` | Reference the current element in filters |
|
|
31
|
+
|
|
32
|
+
### Operations
|
|
33
|
+
- **Find First** — retrieve the first match.
|
|
34
|
+
- **Find All** — retrieve all matches (returns an array).
|
|
35
|
+
- **Update or Create** — modify existing values or create missing ones.
|
|
36
|
+
- **Remove** — delete matching elements.
|
|
37
|
+
- **Callback** — execute a custom callback on each match.
|
|
38
|
+
- **Update or Create Callback** — traverse the path, creating missing intermediate objects and arrays, then invoke the callback on each leaf node.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🌐 Links
|
|
43
|
+
|
|
44
|
+
- Project website: https://github.com/outscale/csonpath
|
|
45
|
+
- Join our community on [Discord](https://discord.gg/HUVtY5gT6s)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📄 Table of Contents
|
|
50
|
+
|
|
51
|
+
- [Features](#-features)
|
|
52
|
+
- [Installation](#-installation)
|
|
53
|
+
- [Usage](#-usage)
|
|
54
|
+
- [C API Reference](#-c-api-reference)
|
|
55
|
+
- [Python API Reference](#-python-api-reference)
|
|
56
|
+
- [Custom Backends](#-custom-backends)
|
|
57
|
+
- [Running Tests](#-running-tests)
|
|
58
|
+
- [Directory Structure](#-directory-structure)
|
|
59
|
+
- [Contributing](#-contributing)
|
|
60
|
+
- [License](#-license)
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📦 Installation
|
|
65
|
+
|
|
66
|
+
### Prerequisites
|
|
67
|
+
- C compiler (`gcc` or `clang`)
|
|
68
|
+
- [json-c](https://github.com/json-c/json-c) library (for C usage)
|
|
69
|
+
- Python 3.x (for Python bindings)
|
|
70
|
+
|
|
71
|
+
### C (json-c)
|
|
72
|
+
Just include the header in your project. There is no separate install step required:
|
|
73
|
+
```c
|
|
74
|
+
#include "csonpath_json-c.h"
|
|
75
|
+
```
|
|
76
|
+
Make sure to link against `json-c` when compiling:
|
|
77
|
+
```sh
|
|
78
|
+
gcc myapp.c -o myapp $(pkg-config --cflags --libs json-c)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Python
|
|
82
|
+
Install from PyPI:
|
|
83
|
+
```sh
|
|
84
|
+
pip install csonpath
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
To install from source (development):
|
|
88
|
+
```sh
|
|
89
|
+
pip install .
|
|
90
|
+
# or
|
|
91
|
+
make pip-dev
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🛠️ Usage
|
|
97
|
+
|
|
98
|
+
### C (json-c)
|
|
99
|
+
|
|
100
|
+
```c
|
|
101
|
+
#include "csonpath_json-c.h"
|
|
102
|
+
|
|
103
|
+
static void my_cb(json_object *parent, struct csonpath_child_info *info,
|
|
104
|
+
json_object *current, void *ud)
|
|
105
|
+
{
|
|
106
|
+
json_object_set_string(current, "modified");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
int main(void)
|
|
110
|
+
{
|
|
111
|
+
struct json_object *jobj = json_tokener_parse(json_str);
|
|
112
|
+
struct csonpath *p = csonpath_new("$.a");
|
|
113
|
+
|
|
114
|
+
/* Find First: return the first match, or NULL */
|
|
115
|
+
struct json_object *ret = csonpath_find_first(p, jobj);
|
|
116
|
+
|
|
117
|
+
/* Find All: return a NEW json_object array. Caller must free it. */
|
|
118
|
+
ret = csonpath_find_all(p, jobj);
|
|
119
|
+
json_object_put(ret);
|
|
120
|
+
|
|
121
|
+
/* Remove: delete matching keys (or set array slots to null). Returns count. */
|
|
122
|
+
int removed = csonpath_remove(p, jobj);
|
|
123
|
+
|
|
124
|
+
/* Update or Create: replace matches, or create the full path if missing. */
|
|
125
|
+
csonpath_update_or_create(p, jobj, json_object_new_string("new_value"));
|
|
126
|
+
|
|
127
|
+
/* Callback: call a user function for every match. */
|
|
128
|
+
csonpath_callback(p, jobj, my_cb, NULL);
|
|
129
|
+
|
|
130
|
+
/* Update or Create Callback: like callback, but creates missing parents first,
|
|
131
|
+
then invokes the callback on every leaf (existing or newly created). */
|
|
132
|
+
csonpath_update_or_create_callback(p, jobj, my_cb, NULL);
|
|
133
|
+
|
|
134
|
+
csonpath_destroy(p);
|
|
135
|
+
json_object_put(jobj);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Python
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import csonpath
|
|
144
|
+
|
|
145
|
+
data = {"a": "value", "array": [1, 2, 3]}
|
|
146
|
+
p = csonpath.CsonPath("$.a")
|
|
147
|
+
|
|
148
|
+
# Find First / Find All
|
|
149
|
+
p.find_first(data) # -> "value"
|
|
150
|
+
p.find_all(data) # -> ["value"]
|
|
151
|
+
|
|
152
|
+
# Remove: returns number of removed items
|
|
153
|
+
p.set_path("$.array[*]")
|
|
154
|
+
p.remove(data)
|
|
155
|
+
|
|
156
|
+
# Update or Create: builds missing objects/arrays automatically
|
|
157
|
+
p.set_path("$.x.y.z")
|
|
158
|
+
p.update_or_create(data, [])
|
|
159
|
+
# data is now {"a": "value", "array": [1, 2, 3], "x": {"y": {"z": []}}}
|
|
160
|
+
|
|
161
|
+
# Callback
|
|
162
|
+
p.set_path("$.a")
|
|
163
|
+
p.callback(data, lambda parent, idx, cur, _: parent.__setitem__(idx, cur.upper()))
|
|
164
|
+
|
|
165
|
+
# Update or Create Callback: creates parents, then calls cb on each leaf
|
|
166
|
+
p.set_path("$[*].a")
|
|
167
|
+
p.update_or_create_callback(dst, my_sync_fn, userdata)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 📘 C API Reference
|
|
173
|
+
|
|
174
|
+
```c
|
|
175
|
+
struct csonpath *csonpath_new(const char *path);
|
|
176
|
+
```
|
|
177
|
+
Create and initialize a new csonpath object.
|
|
178
|
+
|
|
179
|
+
```c
|
|
180
|
+
int csonpath_set_path(struct csonpath *p, const char *path);
|
|
181
|
+
```
|
|
182
|
+
Change the path of an existing object.
|
|
183
|
+
|
|
184
|
+
```c
|
|
185
|
+
int csonpath_compile(struct csonpath *p);
|
|
186
|
+
```
|
|
187
|
+
Compile the path expression. This is optional—paths are compiled automatically on first use—but explicit compilation can help catch syntax errors earlier.
|
|
188
|
+
|
|
189
|
+
```c
|
|
190
|
+
void csonpath_print_instruction(struct csonpath *p);
|
|
191
|
+
```
|
|
192
|
+
Print the compiled bytecode instructions (useful for debugging).
|
|
193
|
+
|
|
194
|
+
```c
|
|
195
|
+
struct json_object *csonpath_find_first(struct csonpath *p, struct json_object *json);
|
|
196
|
+
```
|
|
197
|
+
Return the first matching value, or `NULL` if none is found.
|
|
198
|
+
|
|
199
|
+
```c
|
|
200
|
+
struct json_object *csonpath_find_all(struct csonpath *p, struct json_object *json);
|
|
201
|
+
```
|
|
202
|
+
Return a new `json_object` array containing all matches. **Must be freed with `json_object_put()`.**
|
|
203
|
+
|
|
204
|
+
```c
|
|
205
|
+
int csonpath_remove(struct csonpath *p, struct json_object *json);
|
|
206
|
+
```
|
|
207
|
+
Remove all matching elements. Returns the number of elements removed.
|
|
208
|
+
|
|
209
|
+
```c
|
|
210
|
+
int csonpath_update_or_create(struct csonpath *p, struct json_object *json, struct json_object *new_val);
|
|
211
|
+
```
|
|
212
|
+
Replace matching values with `new_val`, or create the path if it does not exist.
|
|
213
|
+
|
|
214
|
+
```c
|
|
215
|
+
int csonpath_callback(struct csonpath *p, struct json_object *json,
|
|
216
|
+
json_c_callback callback, void *userdata);
|
|
217
|
+
```
|
|
218
|
+
Invoke `callback` for every match.
|
|
219
|
+
|
|
220
|
+
```c
|
|
221
|
+
int csonpath_update_or_create_callback(struct csonpath *p, struct json_object *json,
|
|
222
|
+
json_c_callback callback, void *userdata);
|
|
223
|
+
```
|
|
224
|
+
Like `callback`, but traverses the path while updating/creating missing intermediate objects.
|
|
225
|
+
|
|
226
|
+
```c
|
|
227
|
+
void csonpath_destroy(struct csonpath *p);
|
|
228
|
+
```
|
|
229
|
+
Free the csonpath object.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 📗 Python API Reference
|
|
234
|
+
|
|
235
|
+
- **`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 `$`.
|
|
236
|
+
- **`set_path(path)`** — Change the path expression.
|
|
237
|
+
- **`find_first(json)`** — Return the first match, or `None`.
|
|
238
|
+
- **`find_all(json)`** — Return a list of all matches, or `None` (or `[]` if configured).
|
|
239
|
+
- **`remove(json)`** — Remove all matches. Returns the number of removed items.
|
|
240
|
+
- **`update_or_create(json, value)`** — Replace matches with `value`, or create the path.
|
|
241
|
+
- **`callback(json, callback, callback_data=None)`** — Call `callback(parent, idx, current, callback_data)` for every match.
|
|
242
|
+
- **`update_or_create_callback(json, callback, callback_data=None)`** — Same as `callback`, but creates missing parent objects along the path.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 🔌 Custom Backends
|
|
247
|
+
|
|
248
|
+
csonpath is designed to be backend-agnostic. It can work with any data structure that supports array, object, and scalar semantics.
|
|
249
|
+
|
|
250
|
+
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.
|
|
251
|
+
|
|
252
|
+
For more details, see the existing backend implementations:
|
|
253
|
+
- `csonpath_json-c.h` — json-c backend
|
|
254
|
+
- `csonpath_python.c` — Python backend
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 🧪 Running Tests
|
|
259
|
+
|
|
260
|
+
### C Tests
|
|
261
|
+
```sh
|
|
262
|
+
make tests-c
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Python Tests
|
|
266
|
+
```sh
|
|
267
|
+
make tests-py
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### All Tests
|
|
271
|
+
```sh
|
|
272
|
+
make tests
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 📁 Directory Structure
|
|
278
|
+
|
|
279
|
+
| File / Directory | Description |
|
|
280
|
+
|---|---|
|
|
281
|
+
| `csonpath.h`, `csonpath_do.h` | Core implementation (header-only style) |
|
|
282
|
+
| `csonpath_json-c.h` | json-c backend |
|
|
283
|
+
| `csonpath_python.c` | Python C extension backend |
|
|
284
|
+
| `tests/` | C and Python test suites |
|
|
285
|
+
| `bench/` | Performance benchmarks |
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 🤝 Contributing
|
|
290
|
+
|
|
291
|
+
We welcome contributions!
|
|
292
|
+
|
|
293
|
+
Please read our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) before submitting a pull request.
|
|
294
|
+
|
|
295
|
+
Feel free to open issues or pull requests!
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 📜 License
|
|
300
|
+
|
|
301
|
+
BSD 3-Clause. See [LICENSE](LICENSE).
|
|
@@ -21,12 +21,17 @@ for arg in $@; do
|
|
|
21
21
|
CFLAGS="${CFLAGS} -DCSONPATH_PCRE2_POSIX $(pkg-config --cflags libpcre2-posix)"
|
|
22
22
|
LDFLAGS="${LDFLAGS} $(pkg-config --libs libpcre2-posix)"
|
|
23
23
|
fi
|
|
24
|
+
if [ "--asan" = $arg ]; then
|
|
25
|
+
CFLAGS="${CFLAGS} -fsanitize=address -fno-omit-frame-pointer"
|
|
26
|
+
LDFLAGS="${LDFLAGS} -fsanitize=address"
|
|
27
|
+
fi
|
|
24
28
|
if [ "--help" = $arg ]; then
|
|
25
29
|
echo "help"
|
|
26
30
|
echo " --tiny-regex"
|
|
27
31
|
echo " --pcre2-regex"
|
|
28
32
|
echo " --pcre2-posix"
|
|
29
33
|
echo " --no-regex"
|
|
34
|
+
echo " --asan"
|
|
30
35
|
fi
|
|
31
36
|
done
|
|
32
37
|
|