jsonatapy 2.2.5__tar.gz → 2.2.7__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 (62) hide show
  1. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/CHANGELOG.md +64 -0
  2. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/Cargo.lock +44 -33
  3. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/Cargo.toml +1 -1
  4. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/PKG-INFO +13 -6
  5. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/README.md +12 -5
  6. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/bindings/c/README.md +10 -1
  7. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/bindings/c/examples/smoke.c +58 -0
  8. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/bindings/c/jsonata.h +35 -0
  9. jsonatapy-2.2.7/examples/host_functions.rs +113 -0
  10. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/pyproject.toml +1 -1
  11. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/__init__.py +68 -0
  12. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/capi.rs +352 -4
  13. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/evaluator.rs +171 -8
  14. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/lib.rs +153 -2
  15. jsonatapy-2.2.7/tests/host_functions_test.rs +188 -0
  16. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.gitignore +0 -0
  17. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.gitmodules +0 -0
  18. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/.gitignore +0 -0
  19. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/architecture.md +0 -0
  20. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/conventions.md +0 -0
  21. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/core.md +0 -0
  22. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/memory_maintenance.md +0 -0
  23. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/suggested_commands.md +0 -0
  24. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/task_completion.md +0 -0
  25. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/memories/tech_stack.md +0 -0
  26. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/.serena/project.yml +0 -0
  27. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/LICENSE +0 -0
  28. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/benches/evaluator_bench.rs +0 -0
  29. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/examples/evaluator_demo.rs +0 -0
  30. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/examples/parser_demo.rs +0 -0
  31. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/examples/simd_json_bench.rs +0 -0
  32. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/__main__.py +0 -0
  33. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/_cli/__init__.py +0 -0
  34. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/_cli/bindings.py +0 -0
  35. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/_cli/error_format.py +0 -0
  36. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/_cli/mcp_server.py +0 -0
  37. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/_cli/resolve.py +0 -0
  38. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/_cli/run.py +0 -0
  39. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/python/jsonatapy/py.typed +0 -0
  40. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/ast.rs +0 -0
  41. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/ast_transform.rs +0 -0
  42. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/bin/jsonata/bindings.rs +0 -0
  43. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/bin/jsonata/error_format.rs +0 -0
  44. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/bin/jsonata/main.rs +0 -0
  45. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/bin/jsonata/resolve.rs +0 -0
  46. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/compiler.rs +0 -0
  47. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/datetime.rs +0 -0
  48. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/functions.rs +0 -0
  49. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/lazy.rs +0 -0
  50. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/parser/README.md +0 -0
  51. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/parser.rs +0 -0
  52. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/signature.rs +0 -0
  53. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/value.rs +0 -0
  54. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/src/vm.rs +0 -0
  55. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/study/cli_fixtures.json +0 -0
  56. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/study/cli_fixtures_testdata.json +0 -0
  57. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/study/cli_spec.md +0 -0
  58. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/tests/cli_fixtures_test.rs +0 -0
  59. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/tests/cli_test.rs +0 -0
  60. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/tests/datetime_picture_suite.rs +0 -0
  61. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/tests/integration_test.rs +0 -0
  62. {jsonatapy-2.2.5 → jsonatapy-2.2.7}/tests/parent_and_focus_binding_suite.rs +0 -0
@@ -19,6 +19,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  ### Security
21
21
 
22
+ ## [2.2.7] - 2026-07-21
23
+
24
+ ### Added
25
+
26
+ ### Changed
27
+ - Dependabot no longer auto-bumps the `tests/jsonata-js` reference submodule (removed the
28
+ `gitsubmodule` ecosystem entry). Reference-suite updates are handled by the
29
+ `sync-jsonata.yml` workflow, which runs the conformance suite against each new jsonata-js
30
+ release and opens a tracking issue (or a clean PR) — avoiding context-free failing bump
31
+ PRs like #82.
32
+
33
+ ### Deprecated
34
+
35
+ ### Removed
36
+
37
+ ### Fixed
38
+ - Ensures compliance with the **jsonata-js 2.2.2** reference test suite (reference submodule
39
+ bumped to `6c7e95f`); the full reference suite — 1686 cases — passes. Three behavior
40
+ changes were required to match jsonata-js 2.2.2:
41
+ - `$contains(str, token)` now returns `undefined` when either argument is undefined,
42
+ instead of raising a type error (jsonata-js #809).
43
+ - `$each(obj, fn)` now returns `undefined` when its first argument is undefined, instead
44
+ of raising `each() first argument must be an object`.
45
+ - An object constructor (group-by) applied to an empty or undefined sequence now yields
46
+ an empty object `{}` instead of `undefined` (jsonata-js #817, "correctly handle empty
47
+ joins"); `null` input still returns `null` and non-empty grouping is unchanged.
48
+
49
+ ### Security
50
+
51
+ ## [2.2.6] - 2026-07-20
52
+
53
+ ### Added
54
+ - Host-callable custom functions (Rust core): `Evaluator::register_fn` and
55
+ `Evaluator::register_fn_override` let a host register native functions callable
56
+ from an expression as `$name(...)` — the equivalent of jsonata-js's
57
+ `registerFunction`. Functions are plain closures
58
+ (`Fn(&[JValue]) -> Result<JValue, EvaluatorError>`) and resolve after the
59
+ expression's own bindings/lambdas and before built-ins. `register_fn` rejects
60
+ collisions with built-ins; `register_fn_override` allows deliberately replacing
61
+ the impure built-ins (`$now`, `$millis`, `$random`, `$eval`) for determinism
62
+ injection or sandboxing. Evaluation stays synchronous. See
63
+ `examples/host_functions.rs` and the Rust crate docs.
64
+ - Host-callable custom functions (Python binding): `JsonataExpression.register(name,
65
+ func)` and `.register_override(name, func)` expose the above to Python. The callable
66
+ receives already-evaluated positional arguments and must return a JSON-compatible
67
+ value synchronously; an `async def` (coroutine) is rejected at call time with
68
+ guidance to await I/O outside jsonata and pass results via `bindings`. Collision and
69
+ compilable-builtin-override rules are validated at `register()` time.
70
+ - Host-callable custom functions (C ABI): `jsonata_register_fn(expr, name, fn, user_data)`
71
+ and `jsonata_register_fn_override(...)` expose the feature to C and any language with C
72
+ interop. The callback receives its arguments as a JSON array string and returns a JSON
73
+ result string (jsonata copies it; the host retains ownership), or NULL to signal an error.
74
+ See `bindings/c/jsonata.h`, `bindings/c/README.md`, and `bindings/c/examples/smoke.c`.
75
+
76
+ ### Changed
77
+
78
+ ### Deprecated
79
+
80
+ ### Removed
81
+
82
+ ### Fixed
83
+
84
+ ### Security
85
+
22
86
  ## [2.2.5] - 2026-07-14
23
87
 
24
88
  ### Added
@@ -240,9 +240,9 @@ dependencies = [
240
240
 
241
241
  [[package]]
242
242
  name = "clap"
243
- version = "4.6.1"
243
+ version = "4.6.3"
244
244
  source = "registry+https://github.com/rust-lang/crates.io-index"
245
- checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
245
+ checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776"
246
246
  dependencies = [
247
247
  "clap_builder",
248
248
  "clap_derive",
@@ -250,9 +250,9 @@ dependencies = [
250
250
 
251
251
  [[package]]
252
252
  name = "clap_builder"
253
- version = "4.6.0"
253
+ version = "4.6.2"
254
254
  source = "registry+https://github.com/rust-lang/crates.io-index"
255
- checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
255
+ checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
256
256
  dependencies = [
257
257
  "anstream",
258
258
  "anstyle",
@@ -262,14 +262,14 @@ dependencies = [
262
262
 
263
263
  [[package]]
264
264
  name = "clap_derive"
265
- version = "4.6.1"
265
+ version = "4.6.3"
266
266
  source = "registry+https://github.com/rust-lang/crates.io-index"
267
- checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
267
+ checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077"
268
268
  dependencies = [
269
269
  "heck",
270
270
  "proc-macro2",
271
271
  "quote",
272
- "syn",
272
+ "syn 2.0.118",
273
273
  ]
274
274
 
275
275
  [[package]]
@@ -586,7 +586,7 @@ dependencies = [
586
586
 
587
587
  [[package]]
588
588
  name = "jsonata-core"
589
- version = "2.2.5"
589
+ version = "2.2.7"
590
590
  dependencies = [
591
591
  "assert_cmd",
592
592
  "base64",
@@ -849,7 +849,7 @@ dependencies = [
849
849
  "proc-macro2",
850
850
  "pyo3-macros-backend",
851
851
  "quote",
852
- "syn",
852
+ "syn 2.0.118",
853
853
  ]
854
854
 
855
855
  [[package]]
@@ -861,7 +861,7 @@ dependencies = [
861
861
  "heck",
862
862
  "proc-macro2",
863
863
  "quote",
864
- "syn",
864
+ "syn 2.0.118",
865
865
  ]
866
866
 
867
867
  [[package]]
@@ -983,14 +983,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
983
983
  dependencies = [
984
984
  "proc-macro2",
985
985
  "quote",
986
- "syn",
986
+ "syn 2.0.118",
987
987
  ]
988
988
 
989
989
  [[package]]
990
990
  name = "regex"
991
- version = "1.13.0"
991
+ version = "1.13.1"
992
992
  source = "registry+https://github.com/rust-lang/crates.io-index"
993
- checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
993
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
994
994
  dependencies = [
995
995
  "aho-corasick",
996
996
  "memchr",
@@ -1000,9 +1000,9 @@ dependencies = [
1000
1000
 
1001
1001
  [[package]]
1002
1002
  name = "regex-automata"
1003
- version = "0.4.14"
1003
+ version = "0.4.16"
1004
1004
  source = "registry+https://github.com/rust-lang/crates.io-index"
1005
- checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
1005
+ checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
1006
1006
  dependencies = [
1007
1007
  "aho-corasick",
1008
1008
  "memchr",
@@ -1063,9 +1063,9 @@ dependencies = [
1063
1063
 
1064
1064
  [[package]]
1065
1065
  name = "serde"
1066
- version = "1.0.228"
1066
+ version = "1.0.229"
1067
1067
  source = "registry+https://github.com/rust-lang/crates.io-index"
1068
- checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1068
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
1069
1069
  dependencies = [
1070
1070
  "serde_core",
1071
1071
  "serde_derive",
@@ -1073,29 +1073,29 @@ dependencies = [
1073
1073
 
1074
1074
  [[package]]
1075
1075
  name = "serde_core"
1076
- version = "1.0.228"
1076
+ version = "1.0.229"
1077
1077
  source = "registry+https://github.com/rust-lang/crates.io-index"
1078
- checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1078
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
1079
1079
  dependencies = [
1080
1080
  "serde_derive",
1081
1081
  ]
1082
1082
 
1083
1083
  [[package]]
1084
1084
  name = "serde_derive"
1085
- version = "1.0.228"
1085
+ version = "1.0.229"
1086
1086
  source = "registry+https://github.com/rust-lang/crates.io-index"
1087
- checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1087
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
1088
1088
  dependencies = [
1089
1089
  "proc-macro2",
1090
1090
  "quote",
1091
- "syn",
1091
+ "syn 3.0.2",
1092
1092
  ]
1093
1093
 
1094
1094
  [[package]]
1095
1095
  name = "serde_json"
1096
- version = "1.0.150"
1096
+ version = "1.0.151"
1097
1097
  source = "registry+https://github.com/rust-lang/crates.io-index"
1098
- checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
1098
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
1099
1099
  dependencies = [
1100
1100
  "indexmap",
1101
1101
  "itoa",
@@ -1167,6 +1167,17 @@ dependencies = [
1167
1167
  "unicode-ident",
1168
1168
  ]
1169
1169
 
1170
+ [[package]]
1171
+ name = "syn"
1172
+ version = "3.0.2"
1173
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1174
+ checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
1175
+ dependencies = [
1176
+ "proc-macro2",
1177
+ "quote",
1178
+ "unicode-ident",
1179
+ ]
1180
+
1170
1181
  [[package]]
1171
1182
  name = "target-lexicon"
1172
1183
  version = "0.13.5"
@@ -1194,22 +1205,22 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
1194
1205
 
1195
1206
  [[package]]
1196
1207
  name = "thiserror"
1197
- version = "2.0.18"
1208
+ version = "2.0.19"
1198
1209
  source = "registry+https://github.com/rust-lang/crates.io-index"
1199
- checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
1210
+ checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
1200
1211
  dependencies = [
1201
1212
  "thiserror-impl",
1202
1213
  ]
1203
1214
 
1204
1215
  [[package]]
1205
1216
  name = "thiserror-impl"
1206
- version = "2.0.18"
1217
+ version = "2.0.19"
1207
1218
  source = "registry+https://github.com/rust-lang/crates.io-index"
1208
- checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
1219
+ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
1209
1220
  dependencies = [
1210
1221
  "proc-macro2",
1211
1222
  "quote",
1212
- "syn",
1223
+ "syn 3.0.2",
1213
1224
  ]
1214
1225
 
1215
1226
  [[package]]
@@ -1312,7 +1323,7 @@ dependencies = [
1312
1323
  "bumpalo",
1313
1324
  "proc-macro2",
1314
1325
  "quote",
1315
- "syn",
1326
+ "syn 2.0.118",
1316
1327
  "wasm-bindgen-shared",
1317
1328
  ]
1318
1329
 
@@ -1387,7 +1398,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
1387
1398
  dependencies = [
1388
1399
  "proc-macro2",
1389
1400
  "quote",
1390
- "syn",
1401
+ "syn 2.0.118",
1391
1402
  ]
1392
1403
 
1393
1404
  [[package]]
@@ -1398,7 +1409,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
1398
1409
  dependencies = [
1399
1410
  "proc-macro2",
1400
1411
  "quote",
1401
- "syn",
1412
+ "syn 2.0.118",
1402
1413
  ]
1403
1414
 
1404
1415
  [[package]]
@@ -1457,7 +1468,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
1457
1468
  dependencies = [
1458
1469
  "proc-macro2",
1459
1470
  "quote",
1460
- "syn",
1471
+ "syn 2.0.118",
1461
1472
  ]
1462
1473
 
1463
1474
  [[package]]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonata-core"
3
- version = "2.2.5"
3
+ version = "2.2.7"
4
4
  edition = "2021"
5
5
  authors = ["txjmb <txjmb@users.noreply.github.com>"]
6
6
  description = "High-performance Rust implementation of JSONata query and transformation language"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonatapy
3
- Version: 2.2.5
3
+ Version: 2.2.7
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -47,19 +47,26 @@ Project-URL: Documentation, https://github.com/txjmb/jsonata-core
47
47
  Project-URL: Homepage, https://github.com/txjmb/jsonata-core
48
48
  Project-URL: Repository, https://github.com/txjmb/jsonata-core
49
49
 
50
- # jsonata-core + jsonatapy
50
+ # jsonata-core (rust) + jsonatapy + jsonata C-ABI library + jsonata cli
51
+ #### Pypi stats
52
+ [![jsonatapy Downloads Last Month](https://assets.piptrends.com/get-last-month-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Month by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Downloads Last Week](https://assets.piptrends.com/get-last-week-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Week by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Average Daily Downloads](https://assets.piptrends.com/get-average-downloads-badge/jsonatapy.svg 'jsonatapy Average Daily Downloads by pip Trends')](https://piptrends.com/package/jsonatapy)
51
53
 
52
- High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python bindings.
54
+ #### Crates.io stats
55
+ ![downloads](https://shieldcn.dev/crates/d/jsonata-core.svg)
56
+
57
+ High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python binding and C ABI/library. If you use this library, please add a github star!
53
58
 
54
59
  Much of this project was built using Claude Code with significant human oversight. There was no performant
55
60
  JSONata implementation in Python, so the goal was to port JSONata to Rust (with a PyO3 wrapper
56
61
  for Python) and see how fast it could go. The answer: faster than V8 for most expression
57
- workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi.
62
+ workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi. There is also a command-line binary and Python command-line available (works great with uvx) for use in scripting. The Python library is also usable in a command-line fashion, and a C-compatible library is available for those who want to easily use jsonata in C/C++.
58
63
 
59
- Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
64
+ Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1682/1682 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
60
65
 
61
66
  Release versions will follow the reference jsonata-js project major and minor release numbers, but not necessarily patches. This will make it easier for adopters of this library to understand each release's JSONata API compatibility. As an example, 2.1.7 should be compliant with 2.1.x jsonata-js tests, but may have fixes specific to this library. If a patch release for jsonata-js is relevant for this project, it will be included in a patch release that may or may not follow the patch numbers of the upstream project.
62
67
 
68
+ This project currently chooses not to implement async, because it has limited value for most of the most common use-cases. We
69
+
63
70
  [![Crates.io](https://img.shields.io/crates/v/jsonata-core.svg)](https://crates.io/crates/jsonata-core)
64
71
  [![PyPI version](https://badge.fury.io/py/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
65
72
  [![Python versions](https://img.shields.io/pypi/pyversions/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
@@ -141,7 +148,7 @@ Supports Python 3.10, 3.11, 3.12, 3.13, 3.14 on Linux, macOS (Intel & ARM), and
141
148
 
142
149
  ## Command-line quick start
143
150
 
144
- Both packages also ship a CLI, `jq`-shaped, with an identical contract:
151
+ Both packages also ship a binary CLI and a Python-based CLI, `jq`-shaped, with an identical contract:
145
152
 
146
153
  ```bash
147
154
  pip install jsonatapy
@@ -1,16 +1,23 @@
1
- # jsonata-core + jsonatapy
1
+ # jsonata-core (rust) + jsonatapy + jsonata C-ABI library + jsonata cli
2
+ #### Pypi stats
3
+ [![jsonatapy Downloads Last Month](https://assets.piptrends.com/get-last-month-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Month by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Downloads Last Week](https://assets.piptrends.com/get-last-week-downloads-badge/jsonatapy.svg 'jsonatapy Downloads Last Week by pip Trends')](https://piptrends.com/package/jsonatapy) [![jsonatapy Average Daily Downloads](https://assets.piptrends.com/get-average-downloads-badge/jsonatapy.svg 'jsonatapy Average Daily Downloads by pip Trends')](https://piptrends.com/package/jsonatapy)
2
4
 
3
- High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python bindings.
5
+ #### Crates.io stats
6
+ ![downloads](https://shieldcn.dev/crates/d/jsonata-core.svg)
7
+
8
+ High-performance [JSONata](https://jsonata.org/) implementation in Rust, with Python binding and C ABI/library. If you use this library, please add a github star!
4
9
 
5
10
  Much of this project was built using Claude Code with significant human oversight. There was no performant
6
11
  JSONata implementation in Python, so the goal was to port JSONata to Rust (with a PyO3 wrapper
7
12
  for Python) and see how fast it could go. The answer: faster than V8 for most expression
8
- workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi.
13
+ workloads, and faster than the next pure-Rust implementation. The rust versions are published on crates.io, and the python wheels on pypi. There is also a command-line binary and Python command-line available (works great with uvx) for use in scripting. The Python library is also usable in a command-line fashion, and a C-compatible library is available for those who want to easily use jsonata in C/C++.
9
14
 
10
- Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
15
+ Many, many thanks to the incredible work of all the maintainers of the [JSONata](https://github.com/jsonata-js/jsonata) reference library. JSONata is a very powerful, well-designed, and useful language that has made an impact on many projects. This project leverages their outstanding work to extend that capability to Python and Rust and would not be possible without that project. The implementation in Rust was strongly influenced by their implementation. The 1600+ (1682/1682 passing for last build of this project) tests they created provided the scaffolding and validation for all of this project. This project will continue to follow and be a derivative of the reference project as the JSONata reference library evolves.
11
16
 
12
17
  Release versions will follow the reference jsonata-js project major and minor release numbers, but not necessarily patches. This will make it easier for adopters of this library to understand each release's JSONata API compatibility. As an example, 2.1.7 should be compliant with 2.1.x jsonata-js tests, but may have fixes specific to this library. If a patch release for jsonata-js is relevant for this project, it will be included in a patch release that may or may not follow the patch numbers of the upstream project.
13
18
 
19
+ This project currently chooses not to implement async, because it has limited value for most of the most common use-cases. We
20
+
14
21
  [![Crates.io](https://img.shields.io/crates/v/jsonata-core.svg)](https://crates.io/crates/jsonata-core)
15
22
  [![PyPI version](https://badge.fury.io/py/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
16
23
  [![Python versions](https://img.shields.io/pypi/pyversions/jsonatapy.svg)](https://pypi.org/project/jsonatapy/)
@@ -92,7 +99,7 @@ Supports Python 3.10, 3.11, 3.12, 3.13, 3.14 on Linux, macOS (Intel & ARM), and
92
99
 
93
100
  ## Command-line quick start
94
101
 
95
- Both packages also ship a CLI, `jq`-shaped, with an identical contract:
102
+ Both packages also ship a binary CLI and a Python-based CLI, `jq`-shaped, with an identical contract:
96
103
 
97
104
  ```bash
98
105
  pip install jsonatapy
@@ -132,7 +132,16 @@ Rust library rebuilds with your project.)
132
132
  6. **Variables:** `jsonata_bind_var(expr, "x", "[1,2,3]")` binds `$x` for
133
133
  all subsequent evaluations on that handle (values are JSON text;
134
134
  re-binding replaces).
135
- 7. **Panics:** internal engine panics are caught at the boundary and
135
+ 7. **Host functions:** `jsonata_register_fn(expr, "name", fn, user_data)`
136
+ exposes a C callback to the expression as `$name(...)`. The callback
137
+ receives its arguments as a JSON array string and returns a JSON result
138
+ string that jsonata copies (you keep ownership — do not expect jsonata to
139
+ free it); returning NULL signals an error. A name colliding with a
140
+ built-in is rejected; `jsonata_register_fn_override` replaces a built-in
141
+ deliberately (e.g. a frozen `$now`, or a disabled `$eval`). `user_data`
142
+ must outlive the handle. Registering a host function routes evaluation
143
+ through the tree-walker, like variable bindings.
144
+ 8. **Panics:** internal engine panics are caught at the boundary and
136
145
  surface as errors prefixed `internal error:` — they will not abort
137
146
  your process.
138
147
 
@@ -27,6 +27,31 @@ static int failures = 0;
27
27
 
28
28
  static char *take_error(void) { return jsonata_last_error_message(); }
29
29
 
30
+ /*
31
+ * Host function: given the JSON array ["<name>"], returns "hello <name>".
32
+ * Uses a static buffer — valid until the next call, which is all jsonata needs
33
+ * (it copies the result before returning to the expression).
34
+ */
35
+ static char host_buf[256];
36
+ static const char *greet_cb(void *user_data, const char *args_json) {
37
+ (void)user_data;
38
+ const char *open = strchr(args_json, '"');
39
+ if (!open) return NULL;
40
+ const char *start = open + 1;
41
+ const char *end = strchr(start, '"');
42
+ if (!end) return NULL;
43
+ int len = (int)(end - start);
44
+ snprintf(host_buf, sizeof(host_buf), "\"hello %.*s\"", len, start);
45
+ return host_buf;
46
+ }
47
+
48
+ /* Host override for $now(): a frozen timestamp (static string, always valid). */
49
+ static const char *frozen_now_cb(void *user_data, const char *args_json) {
50
+ (void)user_data;
51
+ (void)args_json;
52
+ return "\"2020-01-01T00:00:00.000Z\"";
53
+ }
54
+
30
55
  int main(void) {
31
56
  /* version */
32
57
  const char *v = jsonata_version();
@@ -119,6 +144,39 @@ int main(void) {
119
144
  jsonata_free_expr(e);
120
145
  }
121
146
 
147
+ /* host function registration + call */
148
+ {
149
+ JsonataExpr *e = jsonata_compile("$greet(name)");
150
+ int rc = jsonata_register_fn(e, "greet", greet_cb, NULL);
151
+ CHECK(rc == 0, "register_fn succeeds");
152
+ char *r = jsonata_evaluate(e, "{\"name\":\"Ada\"}");
153
+ CHECK(r != NULL && strcmp(r, "\"hello Ada\"") == 0, "host function call");
154
+ jsonata_free_string(r);
155
+ jsonata_free_expr(e);
156
+ }
157
+
158
+ /* host function override of an impure built-in ($now) */
159
+ {
160
+ JsonataExpr *e = jsonata_compile("$now()");
161
+ int rc = jsonata_register_fn_override(e, "now", frozen_now_cb, NULL);
162
+ CHECK(rc == 0, "register_fn_override succeeds");
163
+ char *r = jsonata_evaluate(e, "null");
164
+ CHECK(r != NULL && strcmp(r, "\"2020-01-01T00:00:00.000Z\"") == 0,
165
+ "override $now");
166
+ jsonata_free_string(r);
167
+ jsonata_free_expr(e);
168
+ }
169
+
170
+ /* collision with a built-in is rejected */
171
+ {
172
+ JsonataExpr *e = jsonata_compile("$sum(x)");
173
+ int rc = jsonata_register_fn(e, "sum", greet_cb, NULL);
174
+ char *err = take_error();
175
+ CHECK(rc == -1 && err != NULL, "register_fn collision rejected");
176
+ jsonata_free_string(err);
177
+ jsonata_free_expr(e);
178
+ }
179
+
122
180
  /* NULL tolerance of free functions */
123
181
  jsonata_free_expr(NULL);
124
182
  jsonata_free_string(NULL);
@@ -75,6 +75,41 @@ char *jsonata_evaluate(JsonataExpr *expr, const char *json_utf8);
75
75
  int jsonata_bind_var(JsonataExpr *expr, const char *name,
76
76
  const char *json_value_utf8);
77
77
 
78
+ /*
79
+ * A host function callback, callable from the expression as $name(...).
80
+ * user_data: the pointer supplied at registration (opaque to jsonata).
81
+ * args_json: a NUL-terminated UTF-8 JSON ARRAY of the (already evaluated)
82
+ * arguments.
83
+ * Returns a NUL-terminated UTF-8 JSON string with the result, which must stay
84
+ * valid until the call returns — jsonata COPIES it and does NOT free it — or
85
+ * NULL to signal an error.
86
+ */
87
+ typedef const char *(*jsonata_host_fn)(void *user_data, const char *args_json);
88
+
89
+ /*
90
+ * Register a host function callable from the expression as $name(...) — the
91
+ * C equivalent of jsonata-js's registerFunction. Applies to every subsequent
92
+ * jsonata_evaluate() on this handle. A leading '$' in name is accepted and
93
+ * stripped; re-registering a name replaces it. Host functions resolve after
94
+ * the expression's own bindings/lambdas and before built-ins.
95
+ *
96
+ * user_data is passed back to the callback unchanged and must remain valid for
97
+ * the lifetime of the handle. Returns 0 on success, -1 on error (slot set) —
98
+ * including when name collides with a built-in (use
99
+ * jsonata_register_fn_override to replace a built-in deliberately).
100
+ */
101
+ int jsonata_register_fn(JsonataExpr *expr, const char *name,
102
+ jsonata_host_fn fn, void *user_data);
103
+
104
+ /*
105
+ * Like jsonata_register_fn, but deliberately replaces a built-in of the same
106
+ * name — for determinism injection (a frozen $now, seeded $random) or
107
+ * sandboxing (disabling $eval). Overriding a built-in that participates in the
108
+ * compiled fast path returns -1 with an error.
109
+ */
110
+ int jsonata_register_fn_override(JsonataExpr *expr, const char *name,
111
+ jsonata_host_fn fn, void *user_data);
112
+
78
113
  /* Free a handle returned by jsonata_compile(). NULL is a no-op. */
79
114
  void jsonata_free_expr(JsonataExpr *expr);
80
115
 
@@ -0,0 +1,113 @@
1
+ // Host-callable custom functions
2
+ //
3
+ // Shows how a host application registers native Rust functions that a JSONata
4
+ // expression can call as `$name(...)`:
5
+ // - enrichment/lookup functions (the canonical use case)
6
+ // - determinism injection: overriding an impure built-in ($now) with a frozen
7
+ // implementation for reproducible output
8
+ // - sandboxing: overriding a powerful built-in ($eval) to disable it
9
+ //
10
+ // Run with: cargo run --example host_functions
11
+
12
+ use jsonata_core::value::JValue;
13
+ use jsonata_core::{evaluator::Evaluator, parser::parse};
14
+ use serde_json::json;
15
+
16
+ fn main() {
17
+ demo_enrichment_lookup();
18
+ demo_multi_arg();
19
+ demo_override_now();
20
+ demo_sandbox_eval();
21
+ demo_collision_is_rejected();
22
+ }
23
+
24
+ fn eval(ev: &mut Evaluator, expr: &str, data: serde_json::Value) -> JValue {
25
+ let ast = parse(expr).expect("expression parses");
26
+ ev.evaluate(&ast, &JValue::from(data))
27
+ .expect("evaluation succeeds")
28
+ }
29
+
30
+ /// The canonical use case: a lookup backed by host-owned data. The expression
31
+ /// stays a clean artifact; the host owns the (here trivial) data source.
32
+ fn demo_enrichment_lookup() {
33
+ let mut ev = Evaluator::new();
34
+ ev.register_fn("productName", |args: &[JValue]| {
35
+ let sku = args.first().and_then(|v| v.as_str()).unwrap_or("");
36
+ let name = match sku {
37
+ "A-1" => "Widget",
38
+ "B-2" => "Gadget",
39
+ _ => "Unknown",
40
+ };
41
+ Ok(JValue::from(name))
42
+ })
43
+ .unwrap();
44
+
45
+ let out = eval(
46
+ &mut ev,
47
+ "items.{ 'sku': sku, 'name': $productName(sku) }",
48
+ json!({ "items": [ { "sku": "A-1" }, { "sku": "B-2" } ] }),
49
+ );
50
+ println!("enrichment lookup: {}", out.to_json_string().unwrap());
51
+ }
52
+
53
+ /// Host functions receive all arguments already evaluated.
54
+ fn demo_multi_arg() {
55
+ let mut ev = Evaluator::new();
56
+ ev.register_fn("convert", |args: &[JValue]| {
57
+ let amount = args.first().and_then(|v| v.as_f64()).unwrap_or(0.0);
58
+ let rate = match args.get(1).and_then(|v| v.as_str()) {
59
+ Some("EUR") => 1.1,
60
+ _ => 1.0,
61
+ };
62
+ Ok(JValue::from_f64(amount * rate))
63
+ })
64
+ .unwrap();
65
+
66
+ let out = eval(
67
+ &mut ev,
68
+ "$convert(amount, currency)",
69
+ json!({ "amount": 10, "currency": "EUR" }),
70
+ );
71
+ println!("multi-arg convert: {}", out.to_json_string().unwrap());
72
+ }
73
+
74
+ /// Determinism injection: freeze `$now()` so output is reproducible in tests.
75
+ /// `$now` is a non-compilable (impure) built-in, so overriding it is allowed.
76
+ fn demo_override_now() {
77
+ let mut ev = Evaluator::new();
78
+ ev.register_fn_override("now", |_args: &[JValue]| {
79
+ Ok(JValue::from("2020-01-01T00:00:00.000Z"))
80
+ })
81
+ .unwrap();
82
+
83
+ let out = eval(&mut ev, "{ 'generatedAt': $now() }", json!(null));
84
+ println!("frozen $now: {}", out.to_json_string().unwrap());
85
+ }
86
+
87
+ /// Sandboxing: disable the dynamic-evaluation built-in `$eval` when running
88
+ /// semi-trusted expressions.
89
+ fn demo_sandbox_eval() {
90
+ let mut ev = Evaluator::new();
91
+ ev.register_fn_override("eval", |_args: &[JValue]| {
92
+ Err(jsonata_core::evaluator::EvaluatorError::EvaluationError(
93
+ "$eval is disabled in this environment".to_string(),
94
+ ))
95
+ })
96
+ .unwrap();
97
+
98
+ let ast = parse("$eval('1 + 1')").unwrap();
99
+ match ev.evaluate(&ast, &JValue::Null) {
100
+ Ok(v) => println!("sandbox $eval: unexpectedly returned {v:?}"),
101
+ Err(e) => println!("sandbox $eval: blocked as expected -> {e}"),
102
+ }
103
+ }
104
+
105
+ /// Registering a name that collides with a built-in is refused; use
106
+ /// `register_fn_override` to replace a built-in deliberately.
107
+ fn demo_collision_is_rejected() {
108
+ let mut ev = Evaluator::new();
109
+ match ev.register_fn("sum", |_args: &[JValue]| Ok(JValue::Null)) {
110
+ Ok(()) => println!("collision: unexpectedly accepted"),
111
+ Err(e) => println!("collision: rejected as expected -> {e}"),
112
+ }
113
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "jsonatapy"
7
- version = "2.2.5"
7
+ version = "2.2.7"
8
8
  description = "High-performance Python/Rust implementation of JSONata query and transformation language"
9
9
  authors = [
10
10
  {name = "txjmb", email = "txjmb@users.noreply.github.com"}