jsonatapy 2.2.4__tar.gz → 2.2.6__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.
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/CHANGELOG.md +62 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/Cargo.lock +1 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/Cargo.toml +2 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/PKG-INFO +31 -6
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/README.md +30 -5
- jsonatapy-2.2.6/bindings/c/README.md +169 -0
- jsonatapy-2.2.6/bindings/c/examples/smoke.c +191 -0
- jsonatapy-2.2.6/bindings/c/jsonata.h +139 -0
- jsonatapy-2.2.6/examples/host_functions.rs +113 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/pyproject.toml +1 -1
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/__init__.py +73 -0
- jsonatapy-2.2.6/src/capi.rs +891 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/evaluator.rs +148 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/lib.rs +191 -7
- jsonatapy-2.2.6/tests/host_functions_test.rs +188 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.gitignore +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.gitmodules +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/.gitignore +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/architecture.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/conventions.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/core.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/memory_maintenance.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/suggested_commands.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/task_completion.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/memories/tech_stack.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/.serena/project.yml +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/LICENSE +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/benches/evaluator_bench.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/examples/evaluator_demo.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/examples/parser_demo.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/examples/simd_json_bench.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/__main__.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/__init__.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/bindings.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/error_format.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/mcp_server.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/resolve.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/_cli/run.py +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/python/jsonatapy/py.typed +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/ast.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/ast_transform.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/bindings.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/error_format.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/main.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/bin/jsonata/resolve.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/compiler.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/datetime.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/functions.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/lazy.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/parser/README.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/parser.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/signature.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/value.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/src/vm.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/study/cli_fixtures.json +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/study/cli_fixtures_testdata.json +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/study/cli_spec.md +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/cli_fixtures_test.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/cli_test.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/datetime_picture_suite.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/integration_test.rs +0 -0
- {jsonatapy-2.2.4 → jsonatapy-2.2.6}/tests/parent_and_focus_binding_suite.rs +0 -0
|
@@ -19,6 +19,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
### Security
|
|
21
21
|
|
|
22
|
+
## [2.2.6] - 2026-07-20
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Host-callable custom functions (Rust core): `Evaluator::register_fn` and
|
|
26
|
+
`Evaluator::register_fn_override` let a host register native functions callable
|
|
27
|
+
from an expression as `$name(...)` — the equivalent of jsonata-js's
|
|
28
|
+
`registerFunction`. Functions are plain closures
|
|
29
|
+
(`Fn(&[JValue]) -> Result<JValue, EvaluatorError>`) and resolve after the
|
|
30
|
+
expression's own bindings/lambdas and before built-ins. `register_fn` rejects
|
|
31
|
+
collisions with built-ins; `register_fn_override` allows deliberately replacing
|
|
32
|
+
the impure built-ins (`$now`, `$millis`, `$random`, `$eval`) for determinism
|
|
33
|
+
injection or sandboxing. Evaluation stays synchronous. See
|
|
34
|
+
`examples/host_functions.rs` and the Rust crate docs.
|
|
35
|
+
- Host-callable custom functions (Python binding): `JsonataExpression.register(name,
|
|
36
|
+
func)` and `.register_override(name, func)` expose the above to Python. The callable
|
|
37
|
+
receives already-evaluated positional arguments and must return a JSON-compatible
|
|
38
|
+
value synchronously; an `async def` (coroutine) is rejected at call time with
|
|
39
|
+
guidance to await I/O outside jsonata and pass results via `bindings`. Collision and
|
|
40
|
+
compilable-builtin-override rules are validated at `register()` time.
|
|
41
|
+
- Host-callable custom functions (C ABI): `jsonata_register_fn(expr, name, fn, user_data)`
|
|
42
|
+
and `jsonata_register_fn_override(...)` expose the feature to C and any language with C
|
|
43
|
+
interop. The callback receives its arguments as a JSON array string and returns a JSON
|
|
44
|
+
result string (jsonata copies it; the host retains ownership), or NULL to signal an error.
|
|
45
|
+
See `bindings/c/jsonata.h`, `bindings/c/README.md`, and `bindings/c/examples/smoke.c`.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
### Deprecated
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
### Security
|
|
56
|
+
|
|
57
|
+
## [2.2.5] - 2026-07-14
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- C API (`capi` cargo feature): use the engine from C, C++, or any language with C interop.
|
|
61
|
+
Eight functions, JSON text in/out (`jsonata_compile`, `jsonata_evaluate`, `jsonata_bind_var`,
|
|
62
|
+
`jsonata_free_expr`, `jsonata_free_string`, `jsonata_last_error_message`,
|
|
63
|
+
`jsonata_last_error_code`, `jsonata_version`), thread-local error slot, and engine panics
|
|
64
|
+
caught at the boundary instead of aborting the host process. Ships with a hand-written
|
|
65
|
+
header (`bindings/c/jsonata.h`), build/link/CMake documentation (`bindings/c/README.md`),
|
|
66
|
+
and a CI-gated smoke test compiled as both C and C++. Build with
|
|
67
|
+
`cargo build --release --features capi`.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
- Performance regression in v2.2.4 on small/fast expressions (issue #74): the
|
|
71
|
+
`JSONATAPY_FORCE_TREE_WALKER` test toggle read the environment variable on every
|
|
72
|
+
evaluation (both `evaluate()` and `evaluate_json()`), costing ~100-200ns per call —
|
|
73
|
+
10-30% of a sub-microsecond expression. The toggle is now a process-wide atomic seeded
|
|
74
|
+
from the environment once at import (whole-process forcing works unchanged) and flippable
|
|
75
|
+
via a private test hook. Small-expression benchmarks recover 5-16%; the remaining few
|
|
76
|
+
percent vs v2.2.3 on tiny payloads is the documented cost of lazy conversion (which makes
|
|
77
|
+
realistic workloads up to 48% faster, see 2.2.4 notes).
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
- Benchmark tooling: PR benchmark comments and release regression issues now state their
|
|
81
|
+
comparison baseline explicitly (which commit/release, recorded when, on which runner),
|
|
82
|
+
and the vs-jsonata-js comparison is labeled as such.
|
|
83
|
+
|
|
22
84
|
## [2.2.4] - 2026-07-13
|
|
23
85
|
|
|
24
86
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "jsonata-core"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.6"
|
|
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"
|
|
@@ -63,6 +63,7 @@ simd = ["dep:simd-json"]
|
|
|
63
63
|
python = ["dep:pyo3"]
|
|
64
64
|
cli = ["dep:clap"]
|
|
65
65
|
bench = [] # exposes _bench facade for Criterion benchmarks
|
|
66
|
+
capi = []
|
|
66
67
|
|
|
67
68
|
[dev-dependencies]
|
|
68
69
|
criterion = "0.8"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonatapy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.6
|
|
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
|
+
[](https://piptrends.com/package/jsonatapy) [](https://piptrends.com/package/jsonatapy) [](https://piptrends.com/package/jsonatapy)
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
#### Crates.io stats
|
|
55
|
+

|
|
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
|
[](https://crates.io/crates/jsonata-core)
|
|
64
71
|
[](https://pypi.org/project/jsonatapy/)
|
|
65
72
|
[](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
|
|
@@ -151,6 +158,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
|
|
|
151
158
|
|
|
152
159
|
See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
|
|
153
160
|
|
|
161
|
+
## C / C++ quick start
|
|
162
|
+
|
|
163
|
+
The engine exposes a small C ABI (8 functions, JSON text in/out), usable
|
|
164
|
+
from C, C++, or any language with C interop:
|
|
165
|
+
|
|
166
|
+
```c
|
|
167
|
+
JsonataExpr *expr = jsonata_compile("$sum(items.price)");
|
|
168
|
+
char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
|
|
169
|
+
// result: "5"
|
|
170
|
+
jsonata_free_string(result);
|
|
171
|
+
jsonata_free_expr(expr);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Build with `cargo build --release --features capi` and include
|
|
175
|
+
[`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
|
|
176
|
+
[C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
|
|
177
|
+
CMake), the memory/threading contract, and error handling.
|
|
178
|
+
|
|
154
179
|
---
|
|
155
180
|
|
|
156
181
|
## What is JSONata?
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
# jsonata-core + jsonatapy
|
|
1
|
+
# jsonata-core (rust) + jsonatapy + jsonata C-ABI library + jsonata cli
|
|
2
|
+
#### Pypi stats
|
|
3
|
+
[](https://piptrends.com/package/jsonatapy) [](https://piptrends.com/package/jsonatapy) [](https://piptrends.com/package/jsonatapy)
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
#### Crates.io stats
|
|
6
|
+

|
|
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
|
[](https://crates.io/crates/jsonata-core)
|
|
15
22
|
[](https://pypi.org/project/jsonatapy/)
|
|
16
23
|
[](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
|
|
@@ -102,6 +109,24 @@ echo '{"orders":[{"product":"Laptop","price":1200}]}' | jsonatapy 'orders[price
|
|
|
102
109
|
|
|
103
110
|
See [CLI Reference](docs/cli.md) for the full flag/exit-code contract.
|
|
104
111
|
|
|
112
|
+
## C / C++ quick start
|
|
113
|
+
|
|
114
|
+
The engine exposes a small C ABI (8 functions, JSON text in/out), usable
|
|
115
|
+
from C, C++, or any language with C interop:
|
|
116
|
+
|
|
117
|
+
```c
|
|
118
|
+
JsonataExpr *expr = jsonata_compile("$sum(items.price)");
|
|
119
|
+
char *result = jsonata_evaluate(expr, "{\"items\":[{\"price\":2},{\"price\":3}]}");
|
|
120
|
+
// result: "5"
|
|
121
|
+
jsonata_free_string(result);
|
|
122
|
+
jsonata_free_expr(expr);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Build with `cargo build --release --features capi` and include
|
|
126
|
+
[`bindings/c/jsonata.h`](bindings/c/jsonata.h). See the
|
|
127
|
+
[C API guide](bindings/c/README.md) for linking (gcc/clang, Makefile,
|
|
128
|
+
CMake), the memory/threading contract, and error handling.
|
|
129
|
+
|
|
105
130
|
---
|
|
106
131
|
|
|
107
132
|
## What is JSONata?
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# jsonata-core C API
|
|
2
|
+
|
|
3
|
+
Use [JSONata](https://jsonata.org) — the JSON query and transformation
|
|
4
|
+
language — from C, C++, or any language with C interop, backed by
|
|
5
|
+
[jsonata-core](https://github.com/txjmb/jsonata-core), a high-performance
|
|
6
|
+
Rust implementation with 100% reference-suite compatibility against
|
|
7
|
+
jsonata-js.
|
|
8
|
+
|
|
9
|
+
The API is deliberately small: JSON text in, JSON text out, eight
|
|
10
|
+
functions. See [`jsonata.h`](jsonata.h) for the authoritative contract.
|
|
11
|
+
|
|
12
|
+
```c
|
|
13
|
+
#include "jsonata.h"
|
|
14
|
+
#include <stdio.h>
|
|
15
|
+
|
|
16
|
+
int main(void) {
|
|
17
|
+
JsonataExpr *expr = jsonata_compile("$sum(order.items.(price * qty))");
|
|
18
|
+
if (!expr) {
|
|
19
|
+
char *err = jsonata_last_error_message();
|
|
20
|
+
fprintf(stderr, "compile failed: %s\n", err);
|
|
21
|
+
jsonata_free_string(err);
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
char *result = jsonata_evaluate(expr,
|
|
26
|
+
"{\"order\":{\"items\":[{\"price\":9.99,\"qty\":2},{\"price\":5,\"qty\":1}]}}");
|
|
27
|
+
if (result) {
|
|
28
|
+
printf("total: %s\n", result); /* total: 24.98 */
|
|
29
|
+
jsonata_free_string(result);
|
|
30
|
+
} else {
|
|
31
|
+
char *err = jsonata_last_error_message();
|
|
32
|
+
if (err) { /* error */
|
|
33
|
+
fprintf(stderr, "evaluate failed: %s\n", err);
|
|
34
|
+
jsonata_free_string(err);
|
|
35
|
+
} else { /* JSONata "undefined" — no match */
|
|
36
|
+
printf("no result\n");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
jsonata_free_expr(expr);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Building the shared library
|
|
46
|
+
|
|
47
|
+
Requires a Rust toolchain (rustc 1.70+, [rustup.rs](https://rustup.rs)):
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
git clone https://github.com/txjmb/jsonata-core
|
|
51
|
+
cd jsonata-core
|
|
52
|
+
cargo build --release --features capi
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This produces the shared library in `target/release/`:
|
|
56
|
+
|
|
57
|
+
| Platform | Library |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Linux | `libjsonata_core.so` |
|
|
60
|
+
| macOS | `libjsonata_core.dylib` |
|
|
61
|
+
| Windows | `jsonata_core.dll` (+ `jsonata_core.dll.lib` import lib) |
|
|
62
|
+
|
|
63
|
+
The only header you need is `bindings/c/jsonata.h`.
|
|
64
|
+
|
|
65
|
+
## Compiling and linking
|
|
66
|
+
|
|
67
|
+
**C** (gcc/clang):
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
gcc -Wall -o myapp myapp.c -I/path/to/jsonata-core/bindings/c \
|
|
71
|
+
-L/path/to/jsonata-core/target/release -ljsonata_core
|
|
72
|
+
LD_LIBRARY_PATH=/path/to/jsonata-core/target/release ./myapp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**C++**: the header is C++-safe (`extern "C"` guarded); compile and link
|
|
76
|
+
exactly as above with `g++`/`clang++`.
|
|
77
|
+
|
|
78
|
+
For deployment, install the library somewhere on the loader path (or set
|
|
79
|
+
`rpath`): `-Wl,-rpath,/opt/myapp/lib` at link time avoids
|
|
80
|
+
`LD_LIBRARY_PATH` at run time.
|
|
81
|
+
|
|
82
|
+
**Makefile snippet:**
|
|
83
|
+
|
|
84
|
+
```make
|
|
85
|
+
JSONATA_DIR := /path/to/jsonata-core
|
|
86
|
+
CFLAGS += -I$(JSONATA_DIR)/bindings/c
|
|
87
|
+
LDFLAGS += -L$(JSONATA_DIR)/target/release -Wl,-rpath,$(JSONATA_DIR)/target/release
|
|
88
|
+
LDLIBS += -ljsonata_core
|
|
89
|
+
|
|
90
|
+
myapp: myapp.o
|
|
91
|
+
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**CMake snippet:**
|
|
95
|
+
|
|
96
|
+
```cmake
|
|
97
|
+
set(JSONATA_DIR /path/to/jsonata-core)
|
|
98
|
+
|
|
99
|
+
add_library(jsonata_core SHARED IMPORTED)
|
|
100
|
+
set_target_properties(jsonata_core PROPERTIES
|
|
101
|
+
IMPORTED_LOCATION ${JSONATA_DIR}/target/release/libjsonata_core.so
|
|
102
|
+
INTERFACE_INCLUDE_DIRECTORIES ${JSONATA_DIR}/bindings/c)
|
|
103
|
+
|
|
104
|
+
target_link_libraries(myapp PRIVATE jsonata_core)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
(Optionally wrap the `cargo build` in a CMake `add_custom_command` so the
|
|
108
|
+
Rust library rebuilds with your project.)
|
|
109
|
+
|
|
110
|
+
## API rules (the short version)
|
|
111
|
+
|
|
112
|
+
1. **Ownership:** every `char*` the library returns is yours — free it with
|
|
113
|
+
`jsonata_free_string()`. The single exception is `jsonata_version()`
|
|
114
|
+
(static; never free). Never free library strings with your own
|
|
115
|
+
`free()` — allocators may differ.
|
|
116
|
+
2. **NULL from `jsonata_evaluate` is ambiguous by design:** check
|
|
117
|
+
`jsonata_last_error_message()`. Non-NULL → error. NULL → the JSONata
|
|
118
|
+
result was *undefined* (a normal outcome — e.g. a path that matched
|
|
119
|
+
nothing). This mirrors JSONata semantics, where `undefined` and `null`
|
|
120
|
+
are distinct.
|
|
121
|
+
3. **Error codes:** `jsonata_last_error_code()` returns the JSONata spec
|
|
122
|
+
code (`"T2002"`, `"S0201"`, `"D3030"`, …) when the failure has one,
|
|
123
|
+
NULL otherwise. Error source positions are not exposed (the engine
|
|
124
|
+
does not currently track them).
|
|
125
|
+
4. **Threading:** one `JsonataExpr*` per thread — create, use, and free a
|
|
126
|
+
handle on the same thread. Handles are cheap; compile per thread rather
|
|
127
|
+
than sharing. Different threads with their own handles are fine, and
|
|
128
|
+
the error slot is thread-local.
|
|
129
|
+
5. **Reuse handles:** `jsonata_compile` once, `jsonata_evaluate` many
|
|
130
|
+
times — the expression is JIT-compiled to bytecode on first evaluation
|
|
131
|
+
and reused. Compilation is the expensive step.
|
|
132
|
+
6. **Variables:** `jsonata_bind_var(expr, "x", "[1,2,3]")` binds `$x` for
|
|
133
|
+
all subsequent evaluations on that handle (values are JSON text;
|
|
134
|
+
re-binding replaces).
|
|
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
|
|
145
|
+
surface as errors prefixed `internal error:` — they will not abort
|
|
146
|
+
your process.
|
|
147
|
+
|
|
148
|
+
## Smoke test
|
|
149
|
+
|
|
150
|
+
[`examples/smoke.c`](examples/smoke.c) exercises the full API surface and
|
|
151
|
+
doubles as usage documentation. Build and run it from the repo root:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
cargo build --release --features capi
|
|
155
|
+
gcc -Wall -Wextra -Werror -o smoke bindings/c/examples/smoke.c \
|
|
156
|
+
-Ltarget/release -ljsonata_core
|
|
157
|
+
LD_LIBRARY_PATH=target/release ./smoke
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
CI compiles and runs it (as both C and C++) on every change to the C ABI.
|
|
161
|
+
|
|
162
|
+
## Related
|
|
163
|
+
|
|
164
|
+
- [jsonatapy](https://pypi.org/project/jsonatapy/) — the Python binding of
|
|
165
|
+
this engine (PyPI, zero runtime dependencies).
|
|
166
|
+
- [`jsonata` CLI](../../docs/) — a jq-style command-line interface to the
|
|
167
|
+
same engine.
|
|
168
|
+
- [jsonata-js](https://github.com/jsonata-js/jsonata) — the reference
|
|
169
|
+
implementation this engine tracks (1682/1682 reference tests passing).
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* smoke.c — compile-and-run smoke test for the jsonata-core C ABI.
|
|
3
|
+
*
|
|
4
|
+
* Exercises every function in jsonata.h against the real library and exits
|
|
5
|
+
* non-zero on the first failure. CI builds and runs this on every PR that
|
|
6
|
+
* touches the C ABI; see bindings/c/README.md for the manual build/run
|
|
7
|
+
* commands.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include <stdio.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
|
|
14
|
+
#include "../jsonata.h"
|
|
15
|
+
|
|
16
|
+
static int failures = 0;
|
|
17
|
+
|
|
18
|
+
#define CHECK(cond, name) \
|
|
19
|
+
do { \
|
|
20
|
+
if (cond) { \
|
|
21
|
+
printf("PASS %s\n", name); \
|
|
22
|
+
} else { \
|
|
23
|
+
printf("FAIL %s (line %d)\n", name, __LINE__); \
|
|
24
|
+
failures++; \
|
|
25
|
+
} \
|
|
26
|
+
} while (0)
|
|
27
|
+
|
|
28
|
+
static char *take_error(void) { return jsonata_last_error_message(); }
|
|
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
|
+
|
|
55
|
+
int main(void) {
|
|
56
|
+
/* version */
|
|
57
|
+
const char *v = jsonata_version();
|
|
58
|
+
CHECK(v != NULL && strlen(v) > 0, "version non-empty");
|
|
59
|
+
|
|
60
|
+
/* simple path */
|
|
61
|
+
{
|
|
62
|
+
JsonataExpr *e = jsonata_compile("user.name");
|
|
63
|
+
CHECK(e != NULL, "compile simple path");
|
|
64
|
+
char *r = jsonata_evaluate(e, "{\"user\":{\"name\":\"Alice\"}}");
|
|
65
|
+
CHECK(r != NULL && strcmp(r, "\"Alice\"") == 0, "evaluate simple path");
|
|
66
|
+
jsonata_free_string(r);
|
|
67
|
+
jsonata_free_expr(e);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* object construction + arithmetic */
|
|
71
|
+
{
|
|
72
|
+
JsonataExpr *e = jsonata_compile("{\"n\": a + b}");
|
|
73
|
+
char *r = jsonata_evaluate(e, "{\"a\":1,\"b\":2}");
|
|
74
|
+
CHECK(r != NULL && strcmp(r, "{\"n\":3}") == 0, "object result");
|
|
75
|
+
jsonata_free_string(r);
|
|
76
|
+
jsonata_free_expr(e);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* undefined result: NULL with EMPTY error slot */
|
|
80
|
+
{
|
|
81
|
+
JsonataExpr *e = jsonata_compile("missing.path");
|
|
82
|
+
char *r = jsonata_evaluate(e, "{\"a\":1}");
|
|
83
|
+
char *err = take_error();
|
|
84
|
+
CHECK(r == NULL && err == NULL, "undefined -> NULL + empty error");
|
|
85
|
+
jsonata_free_string(err);
|
|
86
|
+
jsonata_free_expr(e);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* parse error: NULL handle + message */
|
|
90
|
+
{
|
|
91
|
+
JsonataExpr *e = jsonata_compile("a.b[");
|
|
92
|
+
char *err = take_error();
|
|
93
|
+
CHECK(e == NULL && err != NULL && strlen(err) > 0,
|
|
94
|
+
"parse error -> NULL + message");
|
|
95
|
+
jsonata_free_string(err);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* coded evaluation error: message + spec code */
|
|
99
|
+
{
|
|
100
|
+
JsonataExpr *e = jsonata_compile("$number(b)");
|
|
101
|
+
char *r = jsonata_evaluate(e, "{\"b\":[1]}");
|
|
102
|
+
char *err = take_error();
|
|
103
|
+
char *code = jsonata_last_error_code();
|
|
104
|
+
CHECK(r == NULL && err != NULL, "eval error -> NULL + message");
|
|
105
|
+
CHECK(code != NULL && strcmp(code, "D3030") == 0,
|
|
106
|
+
"eval error -> spec code D3030");
|
|
107
|
+
jsonata_free_string(err);
|
|
108
|
+
jsonata_free_string(code);
|
|
109
|
+
jsonata_free_expr(e);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* variable binding */
|
|
113
|
+
{
|
|
114
|
+
JsonataExpr *e = jsonata_compile("$sum($xs) + n");
|
|
115
|
+
int rc = jsonata_bind_var(e, "$xs", "[1,2,3]");
|
|
116
|
+
CHECK(rc == 0, "bind_var succeeds");
|
|
117
|
+
char *r = jsonata_evaluate(e, "{\"n\":10}");
|
|
118
|
+
CHECK(r != NULL && strcmp(r, "16") == 0, "evaluate with bound var");
|
|
119
|
+
jsonata_free_string(r);
|
|
120
|
+
jsonata_free_expr(e);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* invalid input JSON: NULL + uncoded message */
|
|
124
|
+
{
|
|
125
|
+
JsonataExpr *e = jsonata_compile("a");
|
|
126
|
+
char *r = jsonata_evaluate(e, "{not json");
|
|
127
|
+
char *err = take_error();
|
|
128
|
+
char *code = jsonata_last_error_code();
|
|
129
|
+
CHECK(r == NULL && err != NULL && strstr(err, "invalid input JSON") != NULL,
|
|
130
|
+
"invalid input JSON -> message");
|
|
131
|
+
CHECK(code == NULL, "invalid input JSON -> no spec code");
|
|
132
|
+
jsonata_free_string(err);
|
|
133
|
+
jsonata_free_string(code);
|
|
134
|
+
jsonata_free_expr(e);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* multibyte UTF-8 round trip */
|
|
138
|
+
{
|
|
139
|
+
JsonataExpr *e = jsonata_compile("$uppercase(name)");
|
|
140
|
+
char *r = jsonata_evaluate(e, "{\"name\":\"h\xC3\xA9llo \xE2\x9C\x93\"}");
|
|
141
|
+
CHECK(r != NULL && strcmp(r, "\"H\xC3\x89LLO \xE2\x9C\x93\"") == 0,
|
|
142
|
+
"multibyte UTF-8 round trip");
|
|
143
|
+
jsonata_free_string(r);
|
|
144
|
+
jsonata_free_expr(e);
|
|
145
|
+
}
|
|
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
|
+
|
|
180
|
+
/* NULL tolerance of free functions */
|
|
181
|
+
jsonata_free_expr(NULL);
|
|
182
|
+
jsonata_free_string(NULL);
|
|
183
|
+
CHECK(1, "free(NULL) is a no-op");
|
|
184
|
+
|
|
185
|
+
if (failures == 0) {
|
|
186
|
+
printf("SMOKE OK (jsonata-core %s)\n", v);
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
printf("SMOKE FAILED: %d failure(s)\n", failures);
|
|
190
|
+
return 1;
|
|
191
|
+
}
|