rusty-jq 0.1.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.
@@ -0,0 +1,31 @@
1
+ /target
2
+ Cargo.lock
3
+
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+ .Python
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ share/python-wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+ MANIFEST
26
+
27
+ .venv/
28
+ venv/
29
+ ENV/
30
+ env/
31
+ .env
@@ -0,0 +1,439 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "allocator-api2"
7
+ version = "0.2.21"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
10
+
11
+ [[package]]
12
+ name = "autocfg"
13
+ version = "1.5.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
16
+
17
+ [[package]]
18
+ name = "bitflags"
19
+ version = "2.11.0"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
22
+
23
+ [[package]]
24
+ name = "cfg-if"
25
+ version = "1.0.4"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
28
+
29
+ [[package]]
30
+ name = "equivalent"
31
+ version = "1.0.2"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
34
+
35
+ [[package]]
36
+ name = "float-cmp"
37
+ version = "0.10.0"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
40
+ dependencies = [
41
+ "num-traits",
42
+ ]
43
+
44
+ [[package]]
45
+ name = "foldhash"
46
+ version = "0.2.0"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
49
+
50
+ [[package]]
51
+ name = "halfbrown"
52
+ version = "0.4.0"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "0c7ed2f2edad8a14c8186b847909a41fbb9c3eafa44f88bd891114ed5019da09"
55
+ dependencies = [
56
+ "hashbrown",
57
+ "serde",
58
+ ]
59
+
60
+ [[package]]
61
+ name = "hashbrown"
62
+ version = "0.16.1"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
65
+ dependencies = [
66
+ "allocator-api2",
67
+ "equivalent",
68
+ "foldhash",
69
+ ]
70
+
71
+ [[package]]
72
+ name = "heck"
73
+ version = "0.4.1"
74
+ source = "registry+https://github.com/rust-lang/crates.io-index"
75
+ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
76
+
77
+ [[package]]
78
+ name = "indoc"
79
+ version = "2.0.7"
80
+ source = "registry+https://github.com/rust-lang/crates.io-index"
81
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
82
+ dependencies = [
83
+ "rustversion",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "itoa"
88
+ version = "1.0.17"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
91
+
92
+ [[package]]
93
+ name = "libc"
94
+ version = "0.2.182"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
97
+
98
+ [[package]]
99
+ name = "lock_api"
100
+ version = "0.4.14"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
103
+ dependencies = [
104
+ "scopeguard",
105
+ ]
106
+
107
+ [[package]]
108
+ name = "memchr"
109
+ version = "2.8.0"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
112
+
113
+ [[package]]
114
+ name = "memoffset"
115
+ version = "0.9.1"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
118
+ dependencies = [
119
+ "autocfg",
120
+ ]
121
+
122
+ [[package]]
123
+ name = "minimal-lexical"
124
+ version = "0.2.1"
125
+ source = "registry+https://github.com/rust-lang/crates.io-index"
126
+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
127
+
128
+ [[package]]
129
+ name = "nom"
130
+ version = "7.1.3"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
133
+ dependencies = [
134
+ "memchr",
135
+ "minimal-lexical",
136
+ ]
137
+
138
+ [[package]]
139
+ name = "num-traits"
140
+ version = "0.2.19"
141
+ source = "registry+https://github.com/rust-lang/crates.io-index"
142
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
143
+ dependencies = [
144
+ "autocfg",
145
+ ]
146
+
147
+ [[package]]
148
+ name = "once_cell"
149
+ version = "1.21.3"
150
+ source = "registry+https://github.com/rust-lang/crates.io-index"
151
+ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
152
+
153
+ [[package]]
154
+ name = "parking_lot"
155
+ version = "0.12.5"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
158
+ dependencies = [
159
+ "lock_api",
160
+ "parking_lot_core",
161
+ ]
162
+
163
+ [[package]]
164
+ name = "parking_lot_core"
165
+ version = "0.9.12"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
168
+ dependencies = [
169
+ "cfg-if",
170
+ "libc",
171
+ "redox_syscall",
172
+ "smallvec",
173
+ "windows-link",
174
+ ]
175
+
176
+ [[package]]
177
+ name = "portable-atomic"
178
+ version = "1.13.1"
179
+ source = "registry+https://github.com/rust-lang/crates.io-index"
180
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
181
+
182
+ [[package]]
183
+ name = "proc-macro2"
184
+ version = "1.0.106"
185
+ source = "registry+https://github.com/rust-lang/crates.io-index"
186
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
187
+ dependencies = [
188
+ "unicode-ident",
189
+ ]
190
+
191
+ [[package]]
192
+ name = "pyo3"
193
+ version = "0.20.3"
194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
195
+ checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233"
196
+ dependencies = [
197
+ "cfg-if",
198
+ "indoc",
199
+ "libc",
200
+ "memoffset",
201
+ "parking_lot",
202
+ "portable-atomic",
203
+ "pyo3-build-config",
204
+ "pyo3-ffi",
205
+ "pyo3-macros",
206
+ "unindent",
207
+ ]
208
+
209
+ [[package]]
210
+ name = "pyo3-build-config"
211
+ version = "0.20.3"
212
+ source = "registry+https://github.com/rust-lang/crates.io-index"
213
+ checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7"
214
+ dependencies = [
215
+ "once_cell",
216
+ "target-lexicon",
217
+ ]
218
+
219
+ [[package]]
220
+ name = "pyo3-ffi"
221
+ version = "0.20.3"
222
+ source = "registry+https://github.com/rust-lang/crates.io-index"
223
+ checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa"
224
+ dependencies = [
225
+ "libc",
226
+ "pyo3-build-config",
227
+ ]
228
+
229
+ [[package]]
230
+ name = "pyo3-macros"
231
+ version = "0.20.3"
232
+ source = "registry+https://github.com/rust-lang/crates.io-index"
233
+ checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158"
234
+ dependencies = [
235
+ "proc-macro2",
236
+ "pyo3-macros-backend",
237
+ "quote",
238
+ "syn",
239
+ ]
240
+
241
+ [[package]]
242
+ name = "pyo3-macros-backend"
243
+ version = "0.20.3"
244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
245
+ checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185"
246
+ dependencies = [
247
+ "heck",
248
+ "proc-macro2",
249
+ "pyo3-build-config",
250
+ "quote",
251
+ "syn",
252
+ ]
253
+
254
+ [[package]]
255
+ name = "quote"
256
+ version = "1.0.44"
257
+ source = "registry+https://github.com/rust-lang/crates.io-index"
258
+ checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
259
+ dependencies = [
260
+ "proc-macro2",
261
+ ]
262
+
263
+ [[package]]
264
+ name = "redox_syscall"
265
+ version = "0.5.18"
266
+ source = "registry+https://github.com/rust-lang/crates.io-index"
267
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
268
+ dependencies = [
269
+ "bitflags",
270
+ ]
271
+
272
+ [[package]]
273
+ name = "ref-cast"
274
+ version = "1.0.25"
275
+ source = "registry+https://github.com/rust-lang/crates.io-index"
276
+ checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
277
+ dependencies = [
278
+ "ref-cast-impl",
279
+ ]
280
+
281
+ [[package]]
282
+ name = "ref-cast-impl"
283
+ version = "1.0.25"
284
+ source = "registry+https://github.com/rust-lang/crates.io-index"
285
+ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
286
+ dependencies = [
287
+ "proc-macro2",
288
+ "quote",
289
+ "syn",
290
+ ]
291
+
292
+ [[package]]
293
+ name = "rustversion"
294
+ version = "1.0.22"
295
+ source = "registry+https://github.com/rust-lang/crates.io-index"
296
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
297
+
298
+ [[package]]
299
+ name = "rusty-jq"
300
+ version = "0.1.0"
301
+ dependencies = [
302
+ "nom",
303
+ "pyo3",
304
+ "simd-json",
305
+ ]
306
+
307
+ [[package]]
308
+ name = "ryu"
309
+ version = "1.0.23"
310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
311
+ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
312
+
313
+ [[package]]
314
+ name = "scopeguard"
315
+ version = "1.2.0"
316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
317
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
318
+
319
+ [[package]]
320
+ name = "serde"
321
+ version = "1.0.228"
322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
323
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
324
+ dependencies = [
325
+ "serde_core",
326
+ "serde_derive",
327
+ ]
328
+
329
+ [[package]]
330
+ name = "serde_core"
331
+ version = "1.0.228"
332
+ source = "registry+https://github.com/rust-lang/crates.io-index"
333
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
334
+ dependencies = [
335
+ "serde_derive",
336
+ ]
337
+
338
+ [[package]]
339
+ name = "serde_derive"
340
+ version = "1.0.228"
341
+ source = "registry+https://github.com/rust-lang/crates.io-index"
342
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
343
+ dependencies = [
344
+ "proc-macro2",
345
+ "quote",
346
+ "syn",
347
+ ]
348
+
349
+ [[package]]
350
+ name = "serde_json"
351
+ version = "1.0.149"
352
+ source = "registry+https://github.com/rust-lang/crates.io-index"
353
+ checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
354
+ dependencies = [
355
+ "itoa",
356
+ "memchr",
357
+ "serde",
358
+ "serde_core",
359
+ "zmij",
360
+ ]
361
+
362
+ [[package]]
363
+ name = "simd-json"
364
+ version = "0.17.0"
365
+ source = "registry+https://github.com/rust-lang/crates.io-index"
366
+ checksum = "4255126f310d2ba20048db6321c81ab376f6a6735608bf11f0785c41f01f64e3"
367
+ dependencies = [
368
+ "halfbrown",
369
+ "ref-cast",
370
+ "serde",
371
+ "serde_json",
372
+ "simdutf8",
373
+ "value-trait",
374
+ ]
375
+
376
+ [[package]]
377
+ name = "simdutf8"
378
+ version = "0.1.5"
379
+ source = "registry+https://github.com/rust-lang/crates.io-index"
380
+ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
381
+
382
+ [[package]]
383
+ name = "smallvec"
384
+ version = "1.15.1"
385
+ source = "registry+https://github.com/rust-lang/crates.io-index"
386
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
387
+
388
+ [[package]]
389
+ name = "syn"
390
+ version = "2.0.116"
391
+ source = "registry+https://github.com/rust-lang/crates.io-index"
392
+ checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
393
+ dependencies = [
394
+ "proc-macro2",
395
+ "quote",
396
+ "unicode-ident",
397
+ ]
398
+
399
+ [[package]]
400
+ name = "target-lexicon"
401
+ version = "0.12.16"
402
+ source = "registry+https://github.com/rust-lang/crates.io-index"
403
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
404
+
405
+ [[package]]
406
+ name = "unicode-ident"
407
+ version = "1.0.24"
408
+ source = "registry+https://github.com/rust-lang/crates.io-index"
409
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
410
+
411
+ [[package]]
412
+ name = "unindent"
413
+ version = "0.2.4"
414
+ source = "registry+https://github.com/rust-lang/crates.io-index"
415
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
416
+
417
+ [[package]]
418
+ name = "value-trait"
419
+ version = "0.12.1"
420
+ source = "registry+https://github.com/rust-lang/crates.io-index"
421
+ checksum = "8e80f0c733af0720a501b3905d22e2f97662d8eacfe082a75ed7ffb5ab08cb59"
422
+ dependencies = [
423
+ "float-cmp",
424
+ "halfbrown",
425
+ "itoa",
426
+ "ryu",
427
+ ]
428
+
429
+ [[package]]
430
+ name = "windows-link"
431
+ version = "0.2.1"
432
+ source = "registry+https://github.com/rust-lang/crates.io-index"
433
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
434
+
435
+ [[package]]
436
+ name = "zmij"
437
+ version = "1.0.21"
438
+ source = "registry+https://github.com/rust-lang/crates.io-index"
439
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "rusty-jq"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ readme = "README.md"
6
+
7
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8
+ [lib]
9
+ name = "rusty_jq"
10
+ crate-type = ["cdylib"]
11
+
12
+ [dependencies]
13
+ pyo3 = { version = "0.20.0", features = ["extension-module"] }
14
+ nom = "7.1.3"
15
+ simd-json = "0.17"
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: rusty-jq
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Requires-Python: >=3.7
@@ -0,0 +1,51 @@
1
+ # 🦀 rusty-jq
2
+
3
+ A **blazing-fast** jq-like JSON query engine for Python, written in Rust.
4
+
5
+ `rusty-jq` compiles jq filter expressions into an optimized Rust pipeline and processes JSON using [simd-json](https://github.com/simd-lite/simd-json) for SIMD-accelerated parsing — delivering **lower latency** than the standard `jq` Python bindings.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ - **jq-compatible syntax** — familiar `.field`, `.[n]`, `.[]`, pipe `|`, and object construction `{}`
12
+ - **Zero-copy where possible** — uses `Cow` semantics to avoid unnecessary allocations
13
+ - **Compile-once, run-many** — pre-compile queries and reuse them across inputs
14
+ - **Native Python types** — results are returned as plain `dict`, `list`, `str`, `int`, `float`, etc.
15
+
16
+ ---
17
+
18
+ ## 🚀 Installation
19
+
20
+ ### Prerequisites
21
+
22
+ - Python ≥ 3.7
23
+ - Rust toolchain (for building from source)
24
+ - [Maturin](https://github.com/PyO3/maturin)
25
+
26
+ ### Supported Filters
27
+
28
+ | Filter | Syntax | Description |
29
+ |---|---|---|
30
+ | **Identity** | `.` | Returns the input unchanged |
31
+ | **Field access** | `.field` | Select a key from an object |
32
+ | **Index** | `.[n]` | Access an array element (supports negative indices) |
33
+ | **Iterator** | `.[]` | Iterate over all elements of an array |
34
+ | **Pipe** | `\|` | Chain filters together |
35
+ | **Object construction** | `{key: .field}` | Build a new object from selected fields |
36
+
37
+ ---
38
+
39
+ ## 🏗️ Architecture
40
+
41
+ | Module | Role |
42
+ |---|---|
43
+ | `lib.rs` | PyO3 bindings — exposes `compile()` and `JqProgram.input()` to Python |
44
+ | `parser.rs` | Query parser built with [nom](https://github.com/rust-bakery/nom) — tokenizes jq expressions into a `Vec<JrFilter>` |
45
+ | `engine.rs` | Execution engine — walks the parsed filter chain over `simd_json::BorrowedValue` using `Cow` for zero-copy traversal |
46
+
47
+ ---
48
+
49
+ ## 📄 License
50
+
51
+ MIT
@@ -0,0 +1,15 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "rusty-jq"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.7"
9
+ classifiers = [
10
+ "Programming Language :: Rust",
11
+ "Programming Language :: Python :: Implementation :: CPython",
12
+ "Programming Language :: Python :: Implementation :: PyPy",
13
+ ]
14
+
15
+ [tool.maturin]
@@ -0,0 +1,115 @@
1
+ use std::borrow::Cow;
2
+ use simd_json::BorrowedValue;
3
+ use simd_json::borrowed::Object;
4
+ use simd_json::prelude::*;
5
+
6
+ use crate::parser::RustyFilter;
7
+
8
+ // applies a chain of RustyFilter to a JSON value and returns all matching results
9
+ pub fn process_rust_value<'a>(root: Cow<'a, BorrowedValue<'a>>, filters: &[RustyFilter]) -> Vec<Cow<'a, BorrowedValue<'a>>> {
10
+ // seed the pipeline with the root value
11
+ let mut current_results: Vec<Cow<'a, BorrowedValue<'a>>> = vec![root];
12
+
13
+ for filter in filters {
14
+ let mut next_results = Vec::new();
15
+
16
+ for value in current_results {
17
+ match filter {
18
+ RustyFilter::Identity => {
19
+ next_results.push(value);
20
+ },
21
+ RustyFilter::Select(key) => {
22
+ match value {
23
+ // borrowed path, hand out a sub-reference without cloning
24
+ Cow::Borrowed(b_val) => {
25
+ if let Some(obj) = b_val.as_object() {
26
+ if let Some(child) = obj.get(key.as_str()) {
27
+ next_results.push(Cow::Borrowed(child));
28
+ }
29
+ }
30
+ },
31
+ // owned path, the child must be cloned out of the owned object
32
+ Cow::Owned(o_val) => {
33
+ if let Some(obj) = o_val.as_object() {
34
+ if let Some(child) = obj.get(key.as_str()) {
35
+ next_results.push(Cow::Owned(child.clone()));
36
+ }
37
+ }
38
+ }
39
+ }
40
+ },
41
+ RustyFilter::Index(idx) => {
42
+ match value {
43
+ Cow::Borrowed(b_val) => {
44
+ if let Some(arr) = b_val.as_array() {
45
+ let len = arr.len() as isize;
46
+ let abs_idx = if *idx < 0 { len + *idx as isize } else { *idx as isize };
47
+ if abs_idx >= 0 && (abs_idx as usize) < (len as usize) {
48
+ next_results.push(Cow::Borrowed(&arr[abs_idx as usize]));
49
+ }
50
+ }
51
+ },
52
+ Cow::Owned(o_val) => {
53
+ if let Some(arr) = o_val.as_array() {
54
+ let len = arr.len() as isize;
55
+ let abs_idx = if *idx < 0 { len + *idx as isize } else { *idx as isize };
56
+ if abs_idx >= 0 && (abs_idx as usize) < (len as usize) {
57
+ next_results.push(Cow::Owned(arr[abs_idx as usize].clone()));
58
+ }
59
+ }
60
+ }
61
+ }
62
+ },
63
+ RustyFilter::Iterator => {
64
+ match value {
65
+ // borrows from the parse buffer
66
+ Cow::Borrowed(b_val) => {
67
+ if let Some(arr) = b_val.as_array() {
68
+ next_results.extend(arr.iter().map(|v| Cow::Borrowed(v)));
69
+ }
70
+ },
71
+ // the parent is an owned temporary
72
+ Cow::Owned(o_val) => {
73
+ if let Some(arr) = o_val.as_array() {
74
+ next_results.extend(arr.iter().cloned().map(|v| Cow::Owned(v)));
75
+ }
76
+ }
77
+ }
78
+ },
79
+ RustyFilter::Object(pairs) => {
80
+ let mut product_objects: Vec<Object> = vec![Object::new()];
81
+
82
+ for (key, sub_query) in pairs {
83
+ // recursively evaluate the sub-query for this field.
84
+ let field_results = process_rust_value(value.clone(), sub_query);
85
+
86
+ // if any field yields no results the whole object is dropped
87
+ if field_results.is_empty() {
88
+ product_objects.clear();
89
+ break;
90
+ }
91
+
92
+ let mut new_product_objects = Vec::new();
93
+ for partial_obj in &product_objects {
94
+ for field_val in &field_results {
95
+ let mut new_obj: Object = partial_obj.clone();
96
+ new_obj.insert(
97
+ Cow::Owned(key.clone()),
98
+ field_val.clone().into_owned()
99
+ );
100
+ new_product_objects.push(new_obj);
101
+ }
102
+ }
103
+ product_objects = new_product_objects;
104
+ }
105
+
106
+ for obj in product_objects {
107
+ next_results.push(Cow::Owned(BorrowedValue::Object(Box::new(obj))));
108
+ }
109
+ }
110
+ }
111
+ }
112
+ current_results = next_results;
113
+ }
114
+ current_results
115
+ }
@@ -0,0 +1,94 @@
1
+ use pyo3::prelude::*;
2
+ use pyo3::types::{PyDict, PyList};
3
+ use simd_json::{BorrowedValue, StaticNode};
4
+ use std::borrow::Cow;
5
+
6
+ mod parser;
7
+ use parser::{parse_query, RustyFilter};
8
+
9
+ mod engine;
10
+ use engine::process_rust_value;
11
+
12
+ // converts a simd-json BorrowedValue into a native Python object
13
+ // operates on zero-copy references
14
+ // Python allocation happens at the end, so hot path stays allocation-free
15
+ fn value_to_py(py: Python, val: &BorrowedValue) -> PyResult<PyObject> {
16
+ match val {
17
+ BorrowedValue::Static(StaticNode::Null) => Ok(py.None()),
18
+ BorrowedValue::Static(StaticNode::Bool(b)) => Ok(b.into_py(py)),
19
+ BorrowedValue::Static(StaticNode::I64(i)) => Ok(i.into_py(py)),
20
+ BorrowedValue::Static(StaticNode::U64(u)) => Ok(u.into_py(py)),
21
+ BorrowedValue::Static(StaticNode::F64(f)) => Ok(f.into_py(py)),
22
+
23
+ BorrowedValue::String(s) => Ok(s.as_ref().into_py(py)),
24
+
25
+ BorrowedValue::Array(arr) => {
26
+ let list = PyList::new(py, arr.iter().map(|item| {
27
+ value_to_py(py, item).unwrap()
28
+ }));
29
+ Ok(list.into())
30
+ },
31
+ BorrowedValue::Object(map) => {
32
+ let dict = PyDict::new(py);
33
+ for (k, v) in map.iter() {
34
+ dict.set_item(k.as_ref(), value_to_py(py, v)?)?;
35
+ }
36
+ Ok(dict.into())
37
+ }
38
+ }
39
+ }
40
+
41
+ // compiled jq-style query, exposed to Python as RustyProgram
42
+ #[pyclass]
43
+ struct RustyProgram {
44
+ filters: Vec<RustyFilter>,
45
+ }
46
+
47
+ #[pymethods]
48
+ impl RustyProgram {
49
+ // execute compiled query against given JSON text
50
+ fn input(&self, py: Python, json_text: &str) -> PyResult<PyObject> {
51
+ // converts string into a mutable buffer of bytes
52
+ let mut bytes = json_text.as_bytes().to_vec();
53
+ // parse the buffer in place and returns a BorrowedValue
54
+ let json_data = simd_json::to_borrowed_value(&mut bytes)
55
+ .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
56
+ // run filter pipeline
57
+ let result = process_rust_value(Cow::Borrowed(&json_data), &self.filters);
58
+ // unwrapping the result
59
+ match result.as_slice() {
60
+ [] => Ok(py.None()),
61
+ [val] => value_to_py(py, &*val),
62
+ _ => {
63
+ let items: PyResult<Vec<PyObject>> = result.iter()
64
+ .map(|v| value_to_py(py, &*v))
65
+ .collect();
66
+ Ok(PyList::new(py, items?).into_py(py))
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ #[pyfunction]
73
+ fn compile(query: &str) -> PyResult<RustyProgram> {
74
+ // returns IResult<&str, Vec<RustyFilter>>
75
+ let (remaining, filters) = match parse_query(query) {
76
+ Ok(x) => x,
77
+ Err(e) => return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid query syntax: {}", e))),
78
+ };
79
+
80
+ // ensure the parser consumed the entire query string
81
+ if !remaining.trim().is_empty() {
82
+ return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>("Extra chars"));
83
+ }
84
+
85
+ Ok(RustyProgram { filters })
86
+ }
87
+
88
+ // PyO3 module initialisation (entry-point)
89
+ #[pymodule]
90
+ fn rusty_jq(_py: Python, m: &PyModule) -> PyResult<()> {
91
+ m.add_function(wrap_pyfunction!(compile, m)?)?;
92
+ m.add_class::<RustyProgram>()?;
93
+ Ok(())
94
+ }
@@ -0,0 +1,115 @@
1
+ use nom::{
2
+ branch::alt,
3
+ bytes::complete::tag,
4
+ character::complete::{alphanumeric1, char, digit1, multispace0},
5
+ combinator::{map, map_res, opt, recognize},
6
+ multi::{separated_list1, many0},
7
+ sequence::{delimited, pair, preceded, separated_pair},
8
+ IResult,
9
+ };
10
+
11
+ // Represents filters operation in a jq-style query
12
+ #[derive(Debug, Clone)]
13
+ pub enum RustyFilter {
14
+ Identity,
15
+ Select(String),
16
+ Index(i32),
17
+ Iterator,
18
+ Object(Vec<(String, Vec<RustyFilter>)>),
19
+ }
20
+
21
+ fn parse_dot(input: &str) -> IResult<&str, &str> {
22
+ tag(".")(input)
23
+ }
24
+
25
+ fn parse_word(input: &str) -> IResult<&str, &str> {
26
+ recognize(pair(
27
+ alt((alphanumeric1, tag("_"))),
28
+ opt(recognize(many0(alt((alphanumeric1, tag("_"), tag("-"))))))
29
+ ))(input)
30
+ }
31
+ fn parse_select(input: &str) -> IResult<&str, RustyFilter> {
32
+ map(
33
+ preceded(
34
+ parse_dot,
35
+ parse_word
36
+ ),
37
+ |s: &str| RustyFilter::Select(s.to_string())
38
+ )(input)
39
+ }
40
+
41
+ fn parse_index(input: &str) -> IResult<&str, RustyFilter> {
42
+ map(
43
+ preceded(
44
+ parse_dot,
45
+ delimited(
46
+ char('['),
47
+ map_res(
48
+ recognize(pair(opt(char('-')), digit1)),
49
+ |s: &str| s.parse::<i32>()
50
+ ),
51
+ char(']')
52
+ )
53
+ ),
54
+ RustyFilter::Index
55
+ )(input)
56
+ }
57
+
58
+ fn parse_iterator(input: &str) -> IResult<&str, RustyFilter> {
59
+ map(
60
+ preceded(parse_dot, tag("[]")),
61
+ |_| RustyFilter::Iterator
62
+ )(input)
63
+ }
64
+
65
+ fn parse_identity(input: &str) -> IResult<&str, RustyFilter> {
66
+ map(parse_dot, |_| RustyFilter::Identity)(input)
67
+ }
68
+
69
+ fn parse_key_value_pair(input: &str) -> IResult<&str, (String, Vec<RustyFilter>)> {
70
+ map(
71
+ separated_pair(
72
+ parse_word,
73
+ delimited(multispace0, char(':'), multispace0),
74
+ parse_query,
75
+ ),
76
+ |(k, v)| (k.to_string(), v),
77
+ )(input)
78
+ }
79
+
80
+ fn parse_object(input: &str) -> IResult<&str, RustyFilter> {
81
+ map(
82
+ delimited(
83
+ char('{'),
84
+ delimited(
85
+ multispace0,
86
+ separated_list1(
87
+ delimited(multispace0, char(','), multispace0),
88
+ parse_key_value_pair
89
+ ),
90
+ multispace0
91
+ ),
92
+ char('}')
93
+ ),
94
+ RustyFilter::Object
95
+ )(input)
96
+ }
97
+
98
+ // parses any single filter token
99
+ fn parse_single_filter(input: &str) -> IResult<&str, RustyFilter> {
100
+ alt((
101
+ parse_iterator,
102
+ parse_index,
103
+ parse_select,
104
+ parse_object,
105
+ parse_identity
106
+ ))(input)
107
+ }
108
+
109
+ // parses a full jq-style query string into a list of RustyFilter
110
+ pub fn parse_query(input: &str) -> IResult<&str, Vec<RustyFilter>> {
111
+ separated_list1(
112
+ delimited(multispace0, char('|'), multispace0),
113
+ parse_single_filter
114
+ )(input)
115
+ }
@@ -0,0 +1,125 @@
1
+ import json
2
+ import time
3
+ import statistics
4
+ import subprocess
5
+ import jq
6
+ import rusty-jq
7
+
8
+ DATA = {
9
+ "metadata": {"source": "payment_gateway", "timestamp": 1700000000},
10
+ "users": [
11
+ {
12
+ "id": 1,
13
+ "name": "John",
14
+ "profile": {"title": "Data Engineer", "location": "Hong Kong"},
15
+ "transactions": [
16
+ {"id": 101, "amount": 500, "currency": "HKD"},
17
+ {"id": 102, "amount": 1200, "currency": "USD"},
18
+ ],
19
+ },
20
+ {
21
+ "id": 2,
22
+ "name": "Bob",
23
+ "profile": {"title": "Manager", "location": "London"},
24
+ "transactions": [],
25
+ },
26
+ ] * 5000,
27
+ }
28
+
29
+ JSON_TEXT = json.dumps(DATA)
30
+
31
+ QUERIES = [
32
+ # Simple Access
33
+ ".metadata | .timestamp",
34
+ # Deep Access
35
+ ".users | .[0] | .profile | .location",
36
+ # Array Slicing + Object Access
37
+ ".users | .[0] | .transactions | .[-1] | .amount",
38
+ # Iterator
39
+ ".users | .[] | .id",
40
+ # Constructor
41
+ ".users | .[] | {user_id: .id, city: .profile | .location}",
42
+ ]
43
+
44
+ def run_jaq_cli(query, json_str):
45
+ """
46
+ Runs jaq binary AND parses the result back to Python.
47
+ """
48
+ process = subprocess.run(
49
+ ["jaq", "-c", query],
50
+ input=json_str,
51
+ text=True,
52
+ capture_output=True
53
+ )
54
+ if process.returncode != 0:
55
+ raise Exception(process.stderr)
56
+
57
+ output_str = process.stdout.strip()
58
+ if not output_str:
59
+ return None
60
+
61
+ try:
62
+ return json.loads(output_str)
63
+ except json.JSONDecodeError:
64
+ return [json.loads(line) for line in output_str.splitlines()]
65
+
66
+ def bench(name, fn, iters=1000):
67
+ for _ in range(100):
68
+ fn()
69
+
70
+ times = []
71
+ for _ in range(iters):
72
+ t0 = time.perf_counter()
73
+ fn()
74
+ t1 = time.perf_counter()
75
+ times.append((t1 - t0) * 1000.0)
76
+
77
+ return {
78
+ "mean": statistics.mean(times),
79
+ "median": statistics.median(times),
80
+ "stdev": statistics.stdev(times)
81
+ }
82
+
83
+ def run_comparison():
84
+ print(f"--- BENCHMARK START ---")
85
+ print(f"Data Size: {len(JSON_TEXT) / 1024:.2f} KB")
86
+ print(f"Users: {len(DATA['users'])}")
87
+ print("-" * 60)
88
+
89
+ for query in QUERIES:
90
+ print(f"\nQuery: {query}")
91
+
92
+ def run_jq():
93
+ return list(jq.compile(query).input(text=JSON_TEXT))
94
+
95
+ def run_jaq():
96
+ return run_jaq_cli(query, JSON_TEXT)
97
+
98
+ def run_rusty():
99
+ return rusty.compile(query).input(JSON_TEXT)
100
+
101
+ res_jq = run_jq()
102
+ res_jaq = run_jaq()
103
+ res_rust = run_rusty()
104
+
105
+ stats_jq = bench("jq", run_jq)
106
+ stats_jaq = bench("jaq", run_jaq)
107
+ stats_rust = bench("rusty", run_rusty)
108
+
109
+ print(f" jq (official): {stats_jq['median']:.4f} ms")
110
+ print(f" jaq (binary) : {stats_jaq['median']:.4f} ms")
111
+ print(f" rusty_jq : {stats_rust['median']:.4f} ms")
112
+
113
+ speedup = stats_jq['median'] / stats_rust['median']
114
+ speedup_jaq = stats_jaq['median'] / stats_rust['median']
115
+ if speedup > 1:
116
+ print(f" 🚀 RESULT: Rusty is {speedup:.2f}x FASTER")
117
+ else:
118
+ print(f" 🐢 RESULT: Rusty is {1/speedup:.2f}x SLOWER")
119
+ if speedup_jaq > 1:
120
+ print(f" 🚀 RESULT: Rusty is {speedup_jaq:.2f}x FASTER")
121
+ else:
122
+ print(f" 🐢 RESULT: Rusty is {1/speedup_jaq:.2f}x SLOWER")
123
+
124
+ if __name__ == "__main__":
125
+ run_comparison()
@@ -0,0 +1,76 @@
1
+ import pytest
2
+ import json
3
+ import rusty-jq
4
+ import jq
5
+
6
+ @pytest.fixture
7
+ def complex_data():
8
+ return {
9
+ "metadata": {
10
+ "source": "payment_gateway",
11
+ "timestamp": 1700000000
12
+ },
13
+ "users": [
14
+ {
15
+ "id": 1,
16
+ "name": "John",
17
+ "profile": {"title": "Data Engineer", "location": "Hong Kong"},
18
+ "transactions": [
19
+ {"id": 101, "amount": 500, "currency": "HKD"},
20
+ {"id": 102, "amount": 1200, "currency": "USD"}
21
+ ]
22
+ },
23
+ {
24
+ "id": 2,
25
+ "name": "Bob",
26
+ "profile": {"title": "Manager", "location": "London"},
27
+ "transactions": []
28
+ }
29
+ ]
30
+ }
31
+
32
+ @pytest.fixture
33
+ def json_string(complex_data):
34
+ return json.dumps(complex_data)
35
+
36
+ @pytest.mark.parametrize("query,expected", [
37
+ # 1. Deep Dive
38
+ (".users | .[0] | .profile | .location", "Hong Kong"),
39
+
40
+ # 2. Negative Indexing + Pipe
41
+ (".users | .[0] | .transactions | .[-1] | .amount", 1200),
42
+
43
+ # 3. Empty Array Handling
44
+ (".users | .[1] | .transactions | .[0]", None),
45
+
46
+ # 4. Safety Check
47
+ (".metadata | .source | .something", None),
48
+
49
+ # 5. Root Object Access
50
+ (".metadata | .timestamp", 1700000000),
51
+
52
+ # 6. Iterator
53
+ (".users | .[] | .id", [1, 2]),
54
+ ])
55
+ def test_jq_queries(complex_data, json_string, query, expected):
56
+ assert rusty.compile(query).input(json_string) == expected
57
+
58
+ @pytest.mark.parametrize("query,expected", [
59
+ # 7. smaller object out of each user
60
+ (".users | .[] | {id: .id, loc: .profile | .location}", [
61
+ {"id": 1, "loc": "Hong Kong"},
62
+ {"id": 2, "loc": "London"},
63
+ ]),
64
+
65
+ # 8. nested constructor + selecting fields
66
+ (".users | .[0] | {name: .name, profile: {title: .profile | .title}}", {
67
+ "name": "John",
68
+ "profile": {"title": "Data Engineer"},
69
+ }),
70
+
71
+ # 9. constructor from root
72
+ (".metadata | {src: .source, ts: .timestamp}", {"src": "payment_gateway", "ts": 1700000000}),
73
+ ])
74
+
75
+ def test_object_constructor_json_only(json_string, query, expected):
76
+ assert rusty.compile(query).input(json_string) == expected