jsonatapy 2.2.1__tar.gz → 2.2.2__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 (39) hide show
  1. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.gitignore +5 -0
  2. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/CHANGELOG.md +28 -0
  3. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/Cargo.lock +17 -17
  4. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/Cargo.toml +1 -1
  5. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/PKG-INFO +2 -2
  6. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/README.md +1 -1
  7. jsonatapy-2.2.2/examples/simd_json_bench.rs +126 -0
  8. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/pyproject.toml +1 -1
  9. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/value.rs +24 -5
  10. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.gitmodules +0 -0
  11. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/.gitignore +0 -0
  12. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/architecture.md +0 -0
  13. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/conventions.md +0 -0
  14. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/core.md +0 -0
  15. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/memory_maintenance.md +0 -0
  16. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/suggested_commands.md +0 -0
  17. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/task_completion.md +0 -0
  18. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/memories/tech_stack.md +0 -0
  19. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/.serena/project.yml +0 -0
  20. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/LICENSE +0 -0
  21. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/benches/evaluator_bench.rs +0 -0
  22. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/examples/evaluator_demo.rs +0 -0
  23. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/examples/parser_demo.rs +0 -0
  24. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/python/jsonatapy/__init__.py +0 -0
  25. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/python/jsonatapy/py.typed +0 -0
  26. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/ast.rs +0 -0
  27. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/ast_transform.rs +0 -0
  28. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/compiler.rs +0 -0
  29. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/datetime.rs +0 -0
  30. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/evaluator.rs +0 -0
  31. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/functions.rs +0 -0
  32. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/lib.rs +0 -0
  33. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/parser/README.md +0 -0
  34. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/parser.rs +0 -0
  35. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/signature.rs +0 -0
  36. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/src/vm.rs +0 -0
  37. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/tests/datetime_picture_suite.rs +0 -0
  38. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/tests/integration_test.rs +0 -0
  39. {jsonatapy-2.2.1 → jsonatapy-2.2.2}/tests/parent_and_focus_binding_suite.rs +0 -0
@@ -63,6 +63,11 @@ benchmarks/javascript/package-lock.json
63
63
  # Test artifacts
64
64
  # tests/jsonata-js/ - Now a git submodule, don't ignore
65
65
 
66
+ # Claude Code local settings (personal per-developer permissions/config,
67
+ # not meant to be shared across the team)
68
+ .claude/settings.local.json
69
+ .claude/worktrees/
70
+
66
71
  # Temporary files
67
72
  *.tmp
68
73
  *.bak
@@ -19,6 +19,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  ### Security
21
21
 
22
+ ## [2.2.2] - 2026-07-09
23
+
24
+ ### Added
25
+ - Documented `JsonataData`, `evaluate_with_data`, and `evaluate_data_to_json` in `docs/api.md`
26
+ (previously absent from the API reference entirely, despite being the "3-15x faster"
27
+ pre-converted-data path highlighted in the README's Performance section). Added a cross-link
28
+ from `docs/rust-crate.md` to the full auto-generated API reference on docs.rs. (#65)
29
+
30
+ ### Changed
31
+ - Updated Rust dependencies to the latest versions compatible with existing (unpinned) semver
32
+ requirements: `simd-json` 0.17.0 → 0.17.2 plus 6 transitive patch bumps. No security advisories
33
+ found before or after (`cargo deny check`). (#65)
34
+
35
+ ### Deprecated
36
+
37
+ ### Removed
38
+
39
+ ### Fixed
40
+ - SIMD-accelerated JSON parsing (`simd-json` feature, on by default) was consistently *slower*
41
+ than the plain `serde_json` fallback for most payload sizes (up to 29% slower at 180KB), the
42
+ opposite of its intent — caused by allocating fresh internal scratch buffers on every single
43
+ parse call. Fixed by reusing a thread-local scratch buffer across calls; SIMD parsing now beats
44
+ `serde_json` consistently (up to +22% faster) instead of losing at 3 of 4 tested sizes.
45
+ Also corrected the README's "(optional feature)" wording for SIMD, which implied opt-in when
46
+ it's actually enabled by default, including in published wheels. (#65)
47
+
48
+ ### Security
49
+
22
50
  ## [2.2.1] - 2026-07-08
23
51
 
24
52
  > Same code as [2.1.7](#217---2026-07-08) below, renumbered. This project's release versions
@@ -103,9 +103,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
103
103
 
104
104
  [[package]]
105
105
  name = "cc"
106
- version = "1.2.65"
106
+ version = "1.2.66"
107
107
  source = "registry+https://github.com/rust-lang/crates.io-index"
108
- checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
108
+ checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
109
109
  dependencies = [
110
110
  "find-msvc-tools",
111
111
  "shlex",
@@ -245,9 +245,9 @@ dependencies = [
245
245
 
246
246
  [[package]]
247
247
  name = "crossbeam-deque"
248
- version = "0.8.6"
248
+ version = "0.8.7"
249
249
  source = "registry+https://github.com/rust-lang/crates.io-index"
250
- checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
250
+ checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
251
251
  dependencies = [
252
252
  "crossbeam-epoch",
253
253
  "crossbeam-utils",
@@ -264,9 +264,9 @@ dependencies = [
264
264
 
265
265
  [[package]]
266
266
  name = "crossbeam-utils"
267
- version = "0.8.21"
267
+ version = "0.8.22"
268
268
  source = "registry+https://github.com/rust-lang/crates.io-index"
269
- checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
269
+ checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
270
270
 
271
271
  [[package]]
272
272
  name = "crunchy"
@@ -483,7 +483,7 @@ dependencies = [
483
483
 
484
484
  [[package]]
485
485
  name = "jsonata-core"
486
- version = "2.2.1"
486
+ version = "2.2.2"
487
487
  dependencies = [
488
488
  "base64",
489
489
  "chrono",
@@ -522,9 +522,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
522
522
 
523
523
  [[package]]
524
524
  name = "memchr"
525
- version = "2.8.2"
525
+ version = "2.8.3"
526
526
  source = "registry+https://github.com/rust-lang/crates.io-index"
527
- checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
527
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
528
528
 
529
529
  [[package]]
530
530
  name = "num-traits"
@@ -882,9 +882,9 @@ dependencies = [
882
882
 
883
883
  [[package]]
884
884
  name = "rustversion"
885
- version = "1.0.22"
885
+ version = "1.0.23"
886
886
  source = "registry+https://github.com/rust-lang/crates.io-index"
887
- checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
887
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
888
888
 
889
889
  [[package]]
890
890
  name = "rusty-fork"
@@ -965,9 +965,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
965
965
 
966
966
  [[package]]
967
967
  name = "simd-json"
968
- version = "0.17.0"
968
+ version = "0.17.2"
969
969
  source = "registry+https://github.com/rust-lang/crates.io-index"
970
- checksum = "4255126f310d2ba20048db6321c81ab376f6a6735608bf11f0785c41f01f64e3"
970
+ checksum = "6f75934ad5ee9e78b79ce7731b822debcb3e5fa6c5aabe57410084715897d1ee"
971
971
  dependencies = [
972
972
  "halfbrown",
973
973
  "ref-cast",
@@ -1276,18 +1276,18 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
1276
1276
 
1277
1277
  [[package]]
1278
1278
  name = "zerocopy"
1279
- version = "0.8.52"
1279
+ version = "0.8.54"
1280
1280
  source = "registry+https://github.com/rust-lang/crates.io-index"
1281
- checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
1281
+ checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
1282
1282
  dependencies = [
1283
1283
  "zerocopy-derive",
1284
1284
  ]
1285
1285
 
1286
1286
  [[package]]
1287
1287
  name = "zerocopy-derive"
1288
- version = "0.8.52"
1288
+ version = "0.8.54"
1289
1289
  source = "registry+https://github.com/rust-lang/crates.io-index"
1290
- checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
1290
+ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
1291
1291
  dependencies = [
1292
1292
  "proc-macro2",
1293
1293
  "quote",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "jsonata-core"
3
- version = "2.2.1"
3
+ version = "2.2.2"
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.1
3
+ Version: 2.2.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -180,7 +180,7 @@ benchmark methodology.
180
180
  - **Pure Rust core** — no JavaScript runtime, no Node.js dependency
181
181
  - **Optional Python bindings** — PyO3/maturin, zero-copy where possible
182
182
  - **Cross-platform** — Linux, macOS (Intel & ARM), Windows; Python 3.10–3.14
183
- - **SIMD-accelerated JSON parsing** — via `simd-json` (optional feature)
183
+ - **SIMD-accelerated JSON parsing** — via `simd-json`, enabled by default (disable with `--no-default-features`)
184
184
 
185
185
  ---
186
186
 
@@ -135,7 +135,7 @@ benchmark methodology.
135
135
  - **Pure Rust core** — no JavaScript runtime, no Node.js dependency
136
136
  - **Optional Python bindings** — PyO3/maturin, zero-copy where possible
137
137
  - **Cross-platform** — Linux, macOS (Intel & ARM), Windows; Python 3.10–3.14
138
- - **SIMD-accelerated JSON parsing** — via `simd-json` (optional feature)
138
+ - **SIMD-accelerated JSON parsing** — via `simd-json`, enabled by default (disable with `--no-default-features`)
139
139
 
140
140
  ---
141
141
 
@@ -0,0 +1,126 @@
1
+ //! One-off benchmark: how much does the `simd` feature speed up JSON parsing?
2
+ //!
3
+ //! Run with the feature on (default) vs off, e.g.:
4
+ //! cargo run --release --example simd_json_bench
5
+ //! cargo run --release --example simd_json_bench --no-default-features
6
+ //!
7
+ //! Not part of the permanent benchmark suite - a throwaway comparison.
8
+
9
+ use jsonata_core::value::JValue;
10
+ use std::time::Instant;
11
+
12
+ fn ecommerce_json(n: usize) -> String {
13
+ let products: Vec<String> = (0..n)
14
+ .map(|i| {
15
+ format!(
16
+ r#"{{"id":{i},"name":"Product {i}","category":"{cat}","price":{price},"inStock":{stock},"rating":{rating},"reviews":{reviews},"tags":["tag1","tag2","tag3"],"vendor":{{"name":"Vendor {v}","rating":4.2}}}}"#,
17
+ i = i,
18
+ cat = ["Electronics", "Clothing", "Books", "Home"][i % 4],
19
+ price = 10.0 + i as f64 * 5.5,
20
+ stock = i % 3 != 0,
21
+ rating = 3.0 + (i % 3) as f64 * 0.5,
22
+ reviews = i * 2,
23
+ v = i % 10,
24
+ )
25
+ })
26
+ .collect();
27
+ format!(r#"{{"products":[{}]}}"#, products.join(","))
28
+ }
29
+
30
+ const REPEAT_TRIALS: usize = 5;
31
+
32
+ fn bench_parse(label: &str, json: &str, iterations: usize) {
33
+ // warmup (shared across all trials)
34
+ for _ in 0..(iterations / 10).max(10) {
35
+ let _ = JValue::from_json_str(json).unwrap();
36
+ }
37
+
38
+ // Take the min across REPEAT_TRIALS independent trials - single-sample
39
+ // wall-clock timing is noisy enough to be meaningless on its own (see
40
+ // this project's own benchmarks/python/benchmark.py for why).
41
+ let mut best_ms: Option<f64> = None;
42
+ let mut all_ms = Vec::with_capacity(REPEAT_TRIALS);
43
+ for _ in 0..REPEAT_TRIALS {
44
+ let start = Instant::now();
45
+ for _ in 0..iterations {
46
+ let _ = JValue::from_json_str(json).unwrap();
47
+ }
48
+ let ms = start.elapsed().as_secs_f64() * 1000.0;
49
+ all_ms.push(ms);
50
+ best_ms = Some(best_ms.map_or(ms, |b: f64| b.min(ms)));
51
+ }
52
+ let best_ms = best_ms.unwrap();
53
+ let per_iter = best_ms / iterations as f64;
54
+ println!(
55
+ "{label:<28} {bytes:>8} bytes {iterations:>6} iters min={min:>8.2}ms all={all:?} {per:>10.5} ms/iter",
56
+ label = label,
57
+ bytes = json.len(),
58
+ iterations = iterations,
59
+ min = best_ms,
60
+ all = all_ms.iter().map(|v| format!("{v:.1}")).collect::<Vec<_>>(),
61
+ per = per_iter,
62
+ );
63
+ }
64
+
65
+ #[cfg(feature = "simd")]
66
+ fn bench_parse_reused_buffer(label: &str, json: &str, iterations: usize) {
67
+ use simd_json::Buffers;
68
+
69
+ let mut buffers = Buffers::new(json.len() + 64);
70
+ let mut bytes = json.as_bytes().to_vec();
71
+
72
+ // warmup
73
+ for _ in 0..(iterations / 10).max(10) {
74
+ let _: JValue =
75
+ simd_json::serde::from_slice_with_buffers(&mut bytes, &mut buffers).unwrap();
76
+ }
77
+
78
+ let mut best_ms: Option<f64> = None;
79
+ let mut all_ms = Vec::with_capacity(REPEAT_TRIALS);
80
+ for _ in 0..REPEAT_TRIALS {
81
+ let start = Instant::now();
82
+ for _ in 0..iterations {
83
+ bytes.clear();
84
+ bytes.extend_from_slice(json.as_bytes());
85
+ let _: JValue =
86
+ simd_json::serde::from_slice_with_buffers(&mut bytes, &mut buffers).unwrap();
87
+ }
88
+ let ms = start.elapsed().as_secs_f64() * 1000.0;
89
+ all_ms.push(ms);
90
+ best_ms = Some(best_ms.map_or(ms, |b: f64| b.min(ms)));
91
+ }
92
+ let best_ms = best_ms.unwrap();
93
+ let per_iter = best_ms / iterations as f64;
94
+ println!(
95
+ "{label:<28} {bytes:>8} bytes {iterations:>6} iters min={min:>8.2}ms all={all:?} {per:>10.5} ms/iter",
96
+ label = label,
97
+ bytes = json.len(),
98
+ iterations = iterations,
99
+ min = best_ms,
100
+ all = all_ms.iter().map(|v| format!("{v:.1}")).collect::<Vec<_>>(),
101
+ per = per_iter,
102
+ );
103
+ }
104
+
105
+ fn main() {
106
+ let feature = if cfg!(feature = "simd") {
107
+ "simd-json"
108
+ } else {
109
+ "serde_json (simd off)"
110
+ };
111
+ println!("=== JSON parsing benchmark - active parser: {feature} ===\n");
112
+
113
+ bench_parse("tiny (1 product)", &ecommerce_json(1), 200_000);
114
+ bench_parse("small (10 products)", &ecommerce_json(10), 50_000);
115
+ bench_parse("medium (100 products)", &ecommerce_json(100), 5_000);
116
+ bench_parse("large (1000 products)", &ecommerce_json(1000), 500);
117
+
118
+ #[cfg(feature = "simd")]
119
+ {
120
+ println!("\n=== HYPOTHESIS TEST: simd-json with a REUSED Buffers scratch ===\n");
121
+ bench_parse_reused_buffer("tiny (1 product)", &ecommerce_json(1), 200_000);
122
+ bench_parse_reused_buffer("small (10 products)", &ecommerce_json(10), 50_000);
123
+ bench_parse_reused_buffer("medium (100 products)", &ecommerce_json(100), 5_000);
124
+ bench_parse_reused_buffer("large (1000 products)", &ecommerce_json(1000), 500);
125
+ }
126
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "jsonatapy"
7
- version = "2.2.1"
7
+ version = "2.2.2"
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"}
@@ -572,14 +572,33 @@ impl JValue {
572
572
 
573
573
  /// Parse a JSON string into a JValue (single-pass, no intermediate serde_json::Value).
574
574
  ///
575
- /// When the `simd` feature is enabled, uses simd-json for 2-4x faster parsing
576
- /// on CPUs with SIMD support (SSE4.2/AVX2/NEON). Falls back to serde_json otherwise.
575
+ /// When the `simd` feature is enabled, uses simd-json for faster parsing on CPUs
576
+ /// with SIMD support (SSE4.2/AVX2/NEON). Falls back to serde_json otherwise.
577
+ ///
578
+ /// `simd_json::serde::from_slice` allocates fresh internal scratch buffers
579
+ /// (`Buffers::new`) on every call, sized proportionally to the input - measured
580
+ /// as the dominant cost, large enough to make the "simd" path consistently
581
+ /// SLOWER than plain serde_json (worse as input grows: -26% at 189 bytes,
582
+ /// -29% at 180KB). Reusing those buffers across calls via
583
+ /// `from_slice_with_buffers` (thread-local, since JValue's Rc-based design is
584
+ /// already single-threaded per instance) eliminates that reallocation and
585
+ /// brings simd-json back to consistently faster than serde_json (+5% to +22%
586
+ /// across the same size range, measured with benches/examples/simd_json_bench.rs).
577
587
  pub fn from_json_str(s: &str) -> Result<JValue, serde_json::Error> {
578
588
  #[cfg(feature = "simd")]
579
589
  {
580
- // simd-json requires a mutable byte slice with padding
581
- let mut bytes = s.as_bytes().to_vec();
582
- if let Ok(value) = simd_json::serde::from_slice::<JValue>(&mut bytes) {
590
+ thread_local! {
591
+ static SCRATCH: std::cell::RefCell<(Vec<u8>, simd_json::Buffers)> =
592
+ std::cell::RefCell::new((Vec::new(), simd_json::Buffers::new(0)));
593
+ }
594
+
595
+ let result = SCRATCH.with(|scratch| {
596
+ let (bytes, buffers) = &mut *scratch.borrow_mut();
597
+ bytes.clear();
598
+ bytes.extend_from_slice(s.as_bytes());
599
+ simd_json::serde::from_slice_with_buffers::<JValue>(bytes, buffers)
600
+ });
601
+ if let Ok(value) = result {
583
602
  return Ok(value);
584
603
  }
585
604
  // Fall back to serde_json if simd-json fails (e.g., unsupported CPU)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes