diff --git a/README.md b/README.md index f857c054..d3d2eaa4 100644 --- a/README.md +++ b/README.md @@ -96,42 +96,207 @@ Here is a performance comparison of various JSONPath queries on the standard boo } ``` - | Method | Mean | Error | StdDev | Allocated - | :----------------------- | ---------: | ----------: | ---------: | ---------: - | `$..* First()` - | Hyperbee_JsonElement | 2.874 μs | 1.6256 μs | 0.0891 μs | 3.52 KB - | Hyperbee_JsonNode | 3.173 μs | 0.7979 μs | 0.0437 μs | 3.09 KB - | JsonEverything_JsonNode | 3.199 μs | 2.4697 μs | 0.1354 μs | 3.53 KB - | JsonCons_JsonElement | 5.976 μs | 8.4042 μs | 0.4607 μs | 8.48 KB - | Newtonsoft_JObject | 9.219 μs | 2.9245 μs | 0.1603 μs | 14.22 KB - | | | | | - | `$..*` - | JsonCons_JsonElement | 5.674 μs | 3.8650 μs | 0.2119 μs | 8.45 KB - | Hyperbee_JsonElement | 7.934 μs | 3.5907 μs | 0.1968 μs | 9.13 KB - | Hyperbee_JsonNode | 10.457 μs | 7.7120 μs | 0.4227 μs | 10.91 KB - | Newtonsoft_JObject | 10.722 μs | 4.1310 μs | 0.2264 μs | 14.86 KB - | JsonEverything_JsonNode | 23.096 μs | 10.8629 μs | 0.5954 μs | 36.81 KB - | | | | | - | `$..price` - | Hyperbee_JsonElement | 4.428 μs | 4.6731 μs | 0.2561 μs | 4.2 KB - | JsonCons_JsonElement | 5.355 μs | 1.1624 μs | 0.0637 μs | 5.65 KB - | Hyperbee_JsonNode | 7.931 μs | 0.6970 μs | 0.0382 μs | 7.48 KB - | Newtonsoft_JObject | 10.334 μs | 8.2331 μs | 0.4513 μs | 14.4 KB - | JsonEverything_JsonNode | 17.000 μs | 14.9812 μs | 0.8212 μs | 27.63 KB - | | | | | - | `$.store.book[?(@.price == 8.99)]` - | Hyperbee_JsonElement | 4.153 μs | 3.6089 μs | 0.1978 μs | 5.24 KB - | JsonCons_JsonElement | 4.873 μs | 1.0395 μs | 0.0570 μs | 5.05 KB - | Hyperbee_JsonNode | 6.980 μs | 5.1007 μs | 0.2796 μs | 8 KB - | Newtonsoft_JObject | 10.629 μs | 3.9096 μs | 0.2143 μs | 15.84 KB - | JsonEverything_JsonNode | 11.133 μs | 7.2544 μs | 0.3976 μs | 15.85 KB - | | | | | - | `$.store.book[0]` - | Hyperbee_JsonElement | 2.677 μs | 2.2733 μs | 0.1246 μs | 2.27 KB - | Hyperbee_JsonNode | 3.126 μs | 3.5345 μs | 0.1937 μs | 2.77 KB - | JsonCons_JsonElement | 3.229 μs | 0.0681 μs | 0.0037 μs | 3.21 KB - | JsonEverything_JsonNode | 4.612 μs | 2.0037 μs | 0.1098 μs | 5.96 KB - | Newtonsoft_JObject | 9.627 μs | 1.1498 μs | 0.0630 μs | 14.56 KB +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|------------------------ |----------:|----------:|----------:|-------:|-------:|----------:| +|`$..[?(@.price < 10)]` +| JsonCraft.JsonElement | 3.841 us | 0.3111 us | 0.0171 us | 0.2899 | - | 3.59 KB | +| JsonCons.JsonElement | 7.459 us | 0.6412 us | 0.0351 us | 1.0376 | 0.0076 | 12.77 KB | +| Hyperbee.JsonElement | 8.284 us | 2.5497 us | 0.1398 us | 1.6785 | 0.0153 | 20.73 KB | +| Hyperbee.JsonNode | 11.086 us | 8.0907 us | 0.4435 us | 1.8921 | - | 23.79 KB | +| Newtonsoft.JObject | 12.153 us | 2.4881 us | 0.1364 us | 2.1057 | 0.0763 | 25.86 KB | +| JsonEverything.JsonNode | 23.541 us | 1.3946 us | 0.0764 us | 3.9063 | 0.1221 | 48.15 KB | +| | | | | | | | +| `$..['bicycle','price']` +| JsonCraft.JsonElement | 3.136 us | 0.2760 us | 0.0151 us | 0.3242 | - | 4.01 KB | +| Hyperbee.JsonElement | 3.578 us | 0.4623 us | 0.0253 us | 0.4349 | 0.0038 | 5.37 KB | +| JsonCons.JsonElement | 3.948 us | 0.6099 us | 0.0334 us | 0.5798 | - | 7.18 KB | +| Hyperbee.JsonNode | 5.181 us | 1.8016 us | 0.0988 us | 0.7477 | 0.0076 | 9.23 KB | +| Newtonsoft.JObject | 7.823 us | 0.8017 us | 0.0439 us | 1.1749 | 0.0458 | 14.55 KB | +| JsonEverything.JsonNode | 16.753 us | 1.5507 us | 0.0850 us | 2.3193 | 0.0610 | 28.5 KB | +| +| ` $..*` | | | | | | | +| JsonCraft.JsonElement | 2.497 us | 0.0903 us | 0.0049 us | 0.2327 | - | 2.88 KB | +| Hyperbee.JsonElement | 3.299 us | 0.8178 us | 0.0448 us | 0.5302 | 0.0038 | 6.51 KB | +| JsonCons.JsonElement | 4.176 us | 0.5887 us | 0.0323 us | 0.6866 | - | 8.49 KB | +| Hyperbee.JsonNode | 5.330 us | 0.7684 us | 0.0421 us | 0.8392 | - | 10.3 KB | +| Newtonsoft.JObject | 7.784 us | 0.5303 us | 0.0291 us | 1.1444 | 0.0458 | 14.19 KB | +| JsonEverything.JsonNode | 21.226 us | 1.0905 us | 0.0598 us | 2.7466 | 0.0610 | 33.97 KB | +| | | | | | | | +|`$..author` +| JsonCraft.JsonElement | 2.904 us | 4.2915 us | 0.2352 us | 0.2327 | - | 2.88 KB | +| Hyperbee.JsonElement | 3.068 us | 0.3672 us | 0.0201 us | 0.4196 | 0.0038 | 5.16 KB | +| JsonCons.JsonElement | 3.381 us | 0.6294 us | 0.0345 us | 0.4501 | - | 5.55 KB | +| Hyperbee.JsonNode | 5.030 us | 0.6100 us | 0.0334 us | 0.7324 | 0.0076 | 9.02 KB | +| Newtonsoft.JObject | 7.469 us | 1.7563 us | 0.0963 us | 1.1444 | 0.0305 | 14.2 KB | +| JsonEverything.JsonNode | 15.752 us | 3.8966 us | 0.2136 us | 2.1057 | 0.0305 | 26.1 KB | +| | | | | | | | +|`$..book[?@.isbn]` +| Hyperbee.JsonElement | 3.913 us | 1.5846 us | 0.0869 us | 0.5493 | - | 6.8 KB | +| JsonCons.JsonElement | 4.359 us | 2.1655 us | 0.1187 us | 0.5875 | - | 7.21 KB | +| Hyperbee.JsonNode | 5.335 us | 0.7057 us | 0.0387 us | 0.8621 | 0.0153 | 10.62 KB | +| JsonEverything.JsonNode | 17.200 us | 1.9836 us | 0.1087 us | 2.4414 | 0.0610 | 29.98 KB | +| JsonCraft.JsonElement | NA | NA | NA | NA | NA | NA | +| Newtonsoft.JObject | NA | NA | NA | NA | NA | NA | +| | | | | | | | +|`$..book[?@.price == 8.99 && @.category == 'fiction']` +| Hyperbee.JsonElement | 5.135 us | 1.0302 us | 0.0565 us | 0.7706 | - | 9.47 KB | +| JsonCons.JsonElement | 6.105 us | 0.6309 us | 0.0346 us | 0.6943 | 0.0076 | 8.52 KB | +| Hyperbee.JsonNode | 7.002 us | 3.0008 us | 0.1645 us | 1.0910 | 0.0229 | 13.41 KB | +| JsonEverything.JsonNode | 21.845 us | 3.9271 us | 0.2153 us | 3.2043 | 0.0916 | 39.52 KB | +| JsonCraft.JsonElement | NA | NA | NA | NA | NA | NA | +| Newtonsoft.JObject | NA | NA | NA | NA | NA | NA | +| | | | | | | | +`$..book[0,1]` +| JsonCraft.JsonElement | 2.936 us | 0.6578 us | 0.0361 us | 0.2518 | - | 3.09 KB | +| Hyperbee.JsonElement | 3.157 us | 0.8302 us | 0.0455 us | 0.4196 | 0.0038 | 5.16 KB | +| JsonCons.JsonElement | 3.691 us | 0.1430 us | 0.0078 us | 0.4997 | - | 6.15 KB | +| Hyperbee.JsonNode | 4.972 us | 0.6586 us | 0.0361 us | 0.7324 | 0.0076 | 9.02 KB | +| Newtonsoft.JObject | 7.441 us | 0.5961 us | 0.0327 us | 1.1749 | 0.0458 | 14.45 KB | +| JsonEverything.JsonNode | 15.523 us | 5.4908 us | 0.3010 us | 2.1362 | 0.0610 | 26.41 KB | +| | | | | | | | +|`$..book[0]` +| JsonCraft.JsonElement | 2.825 us | 0.0934 us | 0.0051 us | 0.2441 | - | 3 KB | +| Hyperbee.JsonElement | 3.114 us | 0.4106 us | 0.0225 us | 0.4196 | 0.0038 | 5.16 KB | +| JsonCons.JsonElement | 3.451 us | 0.2921 us | 0.0160 us | 0.4578 | 0.0038 | 5.63 KB | +| Hyperbee.JsonNode | 4.691 us | 1.4048 us | 0.0770 us | 0.7324 | 0.0076 | 9.02 KB | +| Newtonsoft.JObject | 7.720 us | 0.8748 us | 0.0480 us | 1.1597 | 0.0458 | 14.33 KB | +| JsonEverything.JsonNode | 15.314 us | 3.3459 us | 0.1834 us | 2.1210 | 0.0610 | 26.02 KB | +| | | | | | | | +|`$.store..price` +| JsonCraft.JsonElement | 2.912 us | 1.3083 us | 0.0717 us | 0.2518 | - | 3.13 KB | +| Hyperbee.JsonElement | 2.993 us | 0.4534 us | 0.0249 us | 0.3891 | 0.0038 | 4.8 KB | +| JsonCons.JsonElement | 3.446 us | 0.9305 us | 0.0510 us | 0.4578 | - | 5.62 KB | +| Hyperbee.JsonNode | 4.721 us | 0.9516 us | 0.0522 us | 0.7095 | 0.0076 | 8.7 KB | +| Newtonsoft.JObject | 7.723 us | 1.1978 us | 0.0657 us | 1.1597 | 0.0305 | 14.34 KB | +| JsonEverything.JsonNode | 15.966 us | 1.1138 us | 0.0610 us | 2.1362 | 0.0610 | 26.63 KB | +| | | | | | | | +|`$.store.*` +| JsonCraft.JsonElement | 1.882 us | 0.5586 us | 0.0306 us | 0.2022 | - | 2.49 KB | +| JsonCons.JsonElement | 2.151 us | 0.1067 us | 0.0058 us | 0.2670 | - | 3.31 KB | +| Hyperbee.JsonElement | 2.167 us | 0.6457 us | 0.0354 us | 0.2327 | - | 2.88 KB | +| Hyperbee.JsonNode | 2.435 us | 0.9690 us | 0.0531 us | 0.2403 | - | 2.95 KB | +| JsonEverything.JsonNode | 3.047 us | 0.4674 us | 0.0256 us | 0.3891 | - | 4.8 KB | +| Newtonsoft.JObject | 6.576 us | 0.8290 us | 0.0454 us | 1.1749 | 0.0458 | 14.43 KB | +| | | | | | | | +|`$.store.bicycle.color` +| Hyperbee.JsonElement | 1.920 us | 0.6179 us | 0.0339 us | 0.1869 | - | 2.3 KB | +| JsonCraft.JsonElement | 2.032 us | 0.7770 us | 0.0426 us | 0.2022 | - | 2.49 KB | +| JsonCons.JsonElement | 2.216 us | 0.1473 us | 0.0081 us | 0.2670 | - | 3.27 KB | +| Hyperbee.JsonNode | 2.383 us | 1.2149 us | 0.0666 us | 0.2327 | - | 2.88 KB | +| JsonEverything.JsonNode | 3.556 us | 0.5152 us | 0.0282 us | 0.4654 | 0.0038 | 5.74 KB | +| Newtonsoft.JObject | 6.700 us | 1.6404 us | 0.0899 us | 1.1826 | 0.0381 | 14.49 KB | +| | | | | | | | +|`$.store.book[-1:]` +| JsonCraft.JsonElement | 2.004 us | 0.3158 us | 0.0173 us | 0.2098 | - | 2.58 KB | +| Hyperbee.JsonElement | 2.087 us | 0.1721 us | 0.0094 us | 0.1984 | - | 2.47 KB | +| JsonCons.JsonElement | 2.399 us | 0.3533 us | 0.0194 us | 0.2899 | - | 3.56 KB | +| Hyperbee.JsonNode | 2.467 us | 0.9814 us | 0.0538 us | 0.2403 | - | 2.97 KB | +| JsonEverything.JsonNode | 3.679 us | 0.4354 us | 0.0239 us | 0.4654 | 0.0038 | 5.72 KB | +| Newtonsoft.JObject | 6.472 us | 1.5636 us | 0.0857 us | 1.1826 | 0.0534 | 14.52 KB | +| | | | | | | | +|`$.store.book[:2]` +| JsonCraft.JsonElement | 2.010 us | 0.1463 us | 0.0080 us | 0.2098 | - | 2.58 KB | +| Hyperbee.JsonElement | 2.128 us | 0.4884 us | 0.0268 us | 0.1984 | - | 2.47 KB | +| JsonCons.JsonElement | 2.382 us | 0.0585 us | 0.0032 us | 0.2899 | - | 3.59 KB | +| Hyperbee.JsonNode | 2.450 us | 0.1728 us | 0.0095 us | 0.2403 | - | 2.97 KB | +| JsonEverything.JsonNode | 4.019 us | 2.1096 us | 0.1156 us | 0.4883 | 0.0038 | 6.02 KB | +| Newtonsoft.JObject | 6.899 us | 0.6775 us | 0.0371 us | 1.1826 | 0.0305 | 14.51 KB | +| | | | | | | | +|`$.store.book[?(@.author && @.title)]` +| JsonCraft.JsonElement | 2.567 us | 0.2239 us | 0.0123 us | 0.2670 | - | 3.3 KB | +| Hyperbee.JsonElement | 3.362 us | 0.2369 us | 0.0130 us | 0.4501 | - | 5.52 KB | +| JsonCons.JsonElement | 3.805 us | 0.8769 us | 0.0481 us | 0.4578 | 0.0038 | 5.63 KB | +| Hyperbee.JsonNode | 5.128 us | 1.1406 us | 0.0625 us | 0.7477 | 0.0076 | 9.23 KB | +| Newtonsoft.JObject | 7.514 us | 1.6892 us | 0.0926 us | 1.3199 | 0.0458 | 16.18 KB | +| JsonEverything.JsonNode | 9.261 us | 3.4741 us | 0.1904 us | 1.4801 | 0.0305 | 18.32 KB | +| | | | | | | | +|`$.store.book[?(@.category == 'fiction')]` +| JsonCraft.JsonElement | 2.734 us | 0.3242 us | 0.0178 us | 0.2747 | - | 3.38 KB | +| Hyperbee.JsonElement | 3.315 us | 0.3024 us | 0.0166 us | 0.4120 | - | 5.09 KB | +| JsonCons.JsonElement | 3.426 us | 1.0773 us | 0.0590 us | 0.4120 | - | 5.05 KB | +| Hyperbee.JsonNode | 5.003 us | 0.8363 us | 0.0458 us | 0.7248 | 0.0153 | 8.89 KB | +| Newtonsoft.JObject | 7.213 us | 1.1931 us | 0.0654 us | 1.2817 | 0.0458 | 15.74 KB | +| JsonEverything.JsonNode | 8.898 us | 1.8821 us | 0.1032 us | 1.3428 | 0.0305 | 16.49 KB | +| | | | | | | | +|`$.store.book[?(@.price < 10)].title` +| JsonCraft.JsonElement | 3.108 us | 1.8864 us | 0.1034 us | 0.2747 | - | 3.37 KB | +| Hyperbee.JsonElement | 3.353 us | 0.4814 us | 0.0264 us | 0.4158 | - | 5.1 KB | +| JsonCons.JsonElement | 4.008 us | 0.5005 us | 0.0274 us | 0.4272 | - | 5.27 KB | +| Hyperbee.JsonNode | 5.285 us | 0.7662 us | 0.0420 us | 0.7019 | - | 8.78 KB | +| Newtonsoft.JObject | 7.687 us | 1.0056 us | 0.0551 us | 1.2817 | 0.0458 | 15.89 KB | +| JsonEverything.JsonNode | 9.513 us | 6.3528 us | 0.3482 us | 1.4038 | - | 17.38 KB | +| | | | | | | | +|`$.store.book[?(@.price > 10 && @.price < 20)]` +| JsonCraft.JsonElement | 3.475 us | 0.0797 us | 0.0044 us | 0.3090 | - | 3.82 KB | +| Hyperbee.JsonElement | 4.010 us | 0.6169 us | 0.0338 us | 0.5341 | - | 6.55 KB | +| JsonCons.JsonElement | 5.102 us | 1.1449 us | 0.0628 us | 0.5112 | - | 6.28 KB | +| Hyperbee.JsonNode | 6.086 us | 1.3252 us | 0.0726 us | 0.8316 | 0.0153 | 10.27 KB | +| Newtonsoft.JObject | 8.050 us | 0.6497 us | 0.0356 us | 1.3580 | 0.0458 | 16.69 KB | +| JsonEverything.JsonNode | 11.612 us | 0.2688 us | 0.0147 us | 1.8158 | 0.0458 | 22.27 KB | +| | | | | | | | +|`$.store.book[?@.price == 8.99]` +| Hyperbee.JsonElement | 3.162 us | 0.9502 us | 0.0521 us | 0.3967 | - | 4.9 KB | +| JsonCons.JsonElement | 3.822 us | 0.0869 us | 0.0048 us | 0.4044 | - | 5.02 KB | +| Hyperbee.JsonNode | 4.889 us | 1.5106 us | 0.0828 us | 0.6943 | 0.0076 | 8.58 KB | +| JsonEverything.JsonNode | 8.310 us | 2.0101 us | 0.1102 us | 1.2512 | 0.0305 | 15.47 KB | +| JsonCraft.JsonElement | NA | NA | NA | NA | NA | NA | +| Newtonsoft.JObject | NA | NA | NA | NA | NA | NA | +| | | | | | | | +|`$.store.book['category','author']` +| JsonCraft.JsonElement | 2.084 us | 0.4663 us | 0.0256 us | 0.2403 | - | 2.95 KB | +| JsonCons.JsonElement | 2.480 us | 0.0770 us | 0.0042 us | 0.2937 | - | 3.61 KB | +| Hyperbee.JsonElement | 2.569 us | 1.1453 us | 0.0628 us | 0.2174 | - | 2.67 KB | +| JsonEverything.JsonNode | 3.309 us | 0.2755 us | 0.0151 us | 0.4387 | 0.0038 | 5.41 KB | +| Hyperbee.JsonNode | 4.142 us | 1.4670 us | 0.0804 us | 0.5188 | 0.0076 | 6.42 KB | +| Newtonsoft.JObject | 6.737 us | 0.5860 us | 0.0321 us | 1.2054 | 0.0534 | 14.85 KB | +| | | | | | | | +|`$.store.book[*].author` +| JsonCraft.JsonElement | 2.256 us | 1.4487 us | 0.0794 us | 0.2136 | - | 2.63 KB | +| Hyperbee.JsonElement | 2.469 us | 0.7492 us | 0.0411 us | 0.2518 | - | 3.12 KB | +| JsonCons.JsonElement | 2.496 us | 0.3427 us | 0.0188 us | 0.2899 | - | 3.59 KB | +| Hyperbee.JsonNode | 4.088 us | 0.0348 us | 0.0019 us | 0.5569 | 0.0076 | 6.83 KB | +| Newtonsoft.JObject | 7.169 us | 1.8980 us | 0.1040 us | 1.1902 | 0.0534 | 14.64 KB | +| JsonEverything.JsonNode | 7.511 us | 0.2592 us | 0.0142 us | 1.0071 | 0.0153 | 12.45 KB | +| | | | | | | | +|`$.store.book[*]` +| JsonCraft.JsonElement | 2.048 us | 1.8693 us | 0.1025 us | 0.2022 | - | 2.48 KB | +| Hyperbee.JsonElement | 2.179 us | 0.6664 us | 0.0365 us | 0.2213 | - | 2.71 KB | +| JsonCons.JsonElement | 2.246 us | 0.1811 us | 0.0099 us | 0.2747 | - | 3.4 KB | +| Hyperbee.JsonNode | 2.672 us | 0.2099 us | 0.0115 us | 0.2556 | - | 3.17 KB | +| JsonEverything.JsonNode | 4.315 us | 0.0253 us | 0.0014 us | 0.5341 | - | 6.61 KB | +| Newtonsoft.JObject | 6.632 us | 1.6876 us | 0.0925 us | 1.1826 | 0.0381 | 14.49 KB | +| | | | | | | | +|`$.store.book[0,1]` +| Hyperbee.JsonElement | 2.049 us | 0.0126 us | 0.0007 us | 0.1984 | - | 2.47 KB | +| JsonCraft.JsonElement | 2.056 us | 0.2169 us | 0.0119 us | 0.2136 | - | 2.64 KB | +| Hyperbee.JsonNode | 2.435 us | 0.5524 us | 0.0303 us | 0.2403 | - | 2.97 KB | +| JsonCons.JsonElement | 2.554 us | 1.3052 us | 0.0715 us | 0.3052 | - | 3.77 KB | +| JsonEverything.JsonNode | 3.987 us | 0.4523 us | 0.0248 us | 0.4883 | - | 6.07 KB | +| Newtonsoft.JObject | 6.648 us | 0.7321 us | 0.0401 us | 1.1902 | 0.0534 | 14.59 KB | +| | | | | | | | +|`$.store.book[0].title` +| Hyperbee.JsonElement | 1.897 us | 0.1620 us | 0.0089 us | 0.1831 | - | 2.27 KB | +| JsonCraft.JsonElement | 2.055 us | 0.2876 us | 0.0158 us | 0.2060 | - | 2.55 KB | +| JsonCons.JsonElement | 2.318 us | 0.3233 us | 0.0177 us | 0.2708 | - | 3.35 KB | +| Hyperbee.JsonNode | 2.575 us | 0.1096 us | 0.0060 us | 0.2937 | - | 3.63 KB | +| JsonEverything.JsonNode | 4.534 us | 1.5492 us | 0.0849 us | 0.5951 | 0.0076 | 7.38 KB | +| Newtonsoft.JObject | 6.734 us | 2.0745 us | 0.1137 us | 1.1902 | 0.0458 | 14.62 KB | +| | | | | | | | +|`$.store.book[0]` +| Hyperbee.JsonElement | 1.931 us | 0.1500 us | 0.0082 us | 0.1831 | - | 2.27 KB | +| JsonCraft.JsonElement | 1.996 us | 0.1804 us | 0.0099 us | 0.1984 | - | 2.48 KB | +| Hyperbee.JsonNode | 2.204 us | 0.1747 us | 0.0096 us | 0.2327 | - | 2.86 KB | +| JsonCons.JsonElement | 2.221 us | 0.1388 us | 0.0076 us | 0.2632 | - | 3.26 KB | +| JsonEverything.JsonNode | 3.592 us | 0.3654 us | 0.0200 us | 0.4616 | 0.0038 | 5.68 KB | +| Newtonsoft.JObject | 6.892 us | 2.7132 us | 0.1487 us | 1.1749 | 0.0381 | 14.48 KB | +| | | | | | | | +|`$` +| JsonCraft.JsonElement | 1.732 us | 0.0529 us | 0.0029 us | 0.1831 | - | 2.26 KB | +| Hyperbee.JsonElement | 1.751 us | 0.0628 us | 0.0034 us | 0.1850 | - | 2.27 KB | +| JsonEverything.JsonNode | 1.767 us | 0.0675 us | 0.0037 us | 0.1526 | - | 1.88 KB | +| Hyperbee.JsonNode | 1.790 us | 0.1222 us | 0.0067 us | 0.1450 | - | 1.78 KB | +| JsonCons.JsonElement | 1.929 us | 0.1539 us | 0.0084 us | 0.2422 | - | 2.98 KB | +| Newtonsoft.JObject | 6.587 us | 0.7309 us | 0.0401 us | 1.1368 | 0.0381 | 14.01 KB | ## Credits diff --git a/src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs b/src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs new file mode 100644 index 00000000..e99eddf4 --- /dev/null +++ b/src/Hyperbee.Json/Descriptors/ChildEnumerationOptions.cs @@ -0,0 +1,9 @@ +namespace Hyperbee.Json.Descriptors; + +[Flags] +public enum ChildEnumerationOptions +{ + None = 0, + ComplexTypesOnly = 1, + Reverse = 2, +} diff --git a/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs b/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs index 293ae663..ba9b9a1a 100644 --- a/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs +++ b/src/Hyperbee.Json/Descriptors/Element/ElementActions.cs @@ -33,18 +33,72 @@ public bool TryGetFromPointer( in JsonElement node, JsonSegment segment, out Jso public bool DeepEquals( JsonElement left, JsonElement right ) => left.DeepEquals( right ); - public IEnumerable<(JsonElement Value, string Key)> GetChildren( in JsonElement value, bool complexTypesOnly = false ) + public IEnumerable GetChildren( JsonElement value, ChildEnumerationOptions options ) { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + + // allocating is faster than using yield return and less memory intensive. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List results; + + switch ( value.ValueKind ) + { + case JsonValueKind.Array: + { + var length = value.GetArrayLength(); + results = new List( length ); + + for ( var index = 0; index < length; index++ ) + { + var child = value[index]; + + if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) + continue; + + results.Add( child ); + } + + return reverse ? results.EnumerateReverse() : results; + } + case JsonValueKind.Object: + { + results = new List( 8 ); + + foreach ( var child in value.EnumerateObject() ) + { + if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) + continue; + + results.Add( child.Value ); + } + + return reverse ? results.EnumerateReverse() : results; + } + } + + return []; + } + + public IEnumerable<(JsonElement Value, string Key)> GetChildrenWithName( in JsonElement value, ChildEnumerationOptions options ) + { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + // allocating is faster than using yield return and less memory intensive. - // using stack results in fewer overall allocations than calling reverse, - // which internally allocates, and then discards, a new array. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List<(JsonElement, string)> results; switch ( value.ValueKind ) { case JsonValueKind.Array: { var length = value.GetArrayLength(); - var results = new Stack<(JsonElement, string)>( length ); // stack will reverse items + results = new List<(JsonElement, string)>( length ); for ( var index = 0; index < length; index++ ) { @@ -53,27 +107,28 @@ public bool DeepEquals( JsonElement left, JsonElement right ) => if ( complexTypesOnly && child.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) continue; - results.Push( (child, IndexHelper.GetIndexString( index )) ); + results.Add( (child, IndexHelper.GetIndexString( index )) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } case JsonValueKind.Object: { - var results = new Stack<(JsonElement, string)>(); // stack will reverse items + results = new List<(JsonElement, string)>( 8 ); foreach ( var child in value.EnumerateObject() ) { if ( complexTypesOnly && child.Value.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object) ) continue; - results.Push( (child.Value, child.Name) ); + results.Add( (child.Value, child.Name) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } } return []; } } + diff --git a/src/Hyperbee.Json/Descriptors/INodeActions.cs b/src/Hyperbee.Json/Descriptors/INodeActions.cs index fbd01591..cec948cc 100644 --- a/src/Hyperbee.Json/Descriptors/INodeActions.cs +++ b/src/Hyperbee.Json/Descriptors/INodeActions.cs @@ -11,5 +11,6 @@ public interface INodeActions public bool DeepEquals( TNode left, TNode right ); - public IEnumerable<(TNode Value, string Key)> GetChildren( in TNode value, bool complexTypesOnly = false ); + public IEnumerable<(TNode Value, string Key)> GetChildrenWithName( in TNode value, ChildEnumerationOptions options ); + public IEnumerable GetChildren( TNode value, ChildEnumerationOptions options ); } diff --git a/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs b/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs index 7f2f5358..c270c56c 100644 --- a/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs +++ b/src/Hyperbee.Json/Descriptors/Node/NodeActions.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Text.Json.Nodes; +using Hyperbee.Json.Extensions; using Hyperbee.Json.Path; using Hyperbee.Json.Pointer; using Hyperbee.Json.Query; @@ -28,18 +29,72 @@ public bool TryGetFromPointer( in JsonNode node, JsonSegment segment, out JsonNo public bool DeepEquals( JsonNode left, JsonNode right ) => JsonNode.DeepEquals( left, right ); - public IEnumerable<(JsonNode Value, string Key)> GetChildren( in JsonNode value, bool complexTypesOnly = false ) + public IEnumerable GetChildren( JsonNode value, ChildEnumerationOptions options ) { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + // allocating is faster than using yield return and less memory intensive. - // using stack results in fewer overall allocations than calling reverse, - // which internally allocates, and then discards, a new array. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List results; + + switch ( value ) + { + case JsonArray jsonArray: + { + var length = jsonArray.Count; + results = new List( length ); + + for ( var index = 0; index < length; index++ ) + { + var child = value[index]; + + if ( complexTypesOnly && child is not (JsonArray or JsonObject) ) + continue; + + results.Add( child ); + } + + return reverse ? results.EnumerateReverse() : results; + } + case JsonObject jsonObject: + { + results = new List( 8 ); + + foreach ( var child in jsonObject ) + { + if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) ) + continue; + + results.Add( child.Value ); + } + + return reverse ? results.EnumerateReverse() : results; + } + } + + return []; + } + + public IEnumerable<(JsonNode Value, string Key)> GetChildrenWithName( in JsonNode value, ChildEnumerationOptions options ) + { + bool complexTypesOnly = options.HasFlag( ChildEnumerationOptions.ComplexTypesOnly ); + bool reverse = options.HasFlag( ChildEnumerationOptions.Reverse ); + + // allocating is faster than using yield return and less memory intensive. + // using a collection results in fewer overall allocations than calling + // LINQ reverse, which internally allocates, and then discards, a new array. + + List<(JsonNode, string)> results; switch ( value ) { case JsonArray jsonArray: { var length = jsonArray.Count; - var results = new Stack<(JsonNode, string)>( length ); // stack will reverse items + results = new List<(JsonNode, string)>( length ); for ( var index = 0; index < length; index++ ) { @@ -48,24 +103,24 @@ public bool DeepEquals( JsonNode left, JsonNode right ) => if ( complexTypesOnly && child is not (JsonArray or JsonObject) ) continue; - results.Push( (child, IndexHelper.GetIndexString( index )) ); + results.Add( (child, IndexHelper.GetIndexString( index )) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } case JsonObject jsonObject: { - var results = new Stack<(JsonNode, string)>(); // stack will reverse items + results = new List<(JsonNode, string)>( 8 ); foreach ( var child in jsonObject ) { if ( complexTypesOnly && child.Value is not (JsonArray or JsonObject) ) continue; - results.Push( (child.Value, child.Key) ); + results.Add( (child.Value, child.Key) ); } - return results; + return reverse ? results.EnumerateReverse() : results; } } diff --git a/src/Hyperbee.Json/Extensions/ListExtensions.cs b/src/Hyperbee.Json/Extensions/ListExtensions.cs new file mode 100644 index 00000000..02e3dbf6 --- /dev/null +++ b/src/Hyperbee.Json/Extensions/ListExtensions.cs @@ -0,0 +1,10 @@ +namespace Hyperbee.Json.Extensions; + +public static class ListExtensions +{ + internal static IEnumerable EnumerateReverse( this IList list ) + { + for ( var i = list.Count - 1; i >= 0; i-- ) + yield return list[i]; + } +} diff --git a/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs b/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs index 8fdd3abf..8727d2e6 100644 --- a/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs +++ b/src/Hyperbee.Json/Path/Filters/Parser/Expressions/JsonExpressionFactory.cs @@ -44,6 +44,7 @@ private static bool TryParseNode( INodeActions actions, ReadOnlySp private static void ConvertToDoubleQuotes( ref Span buffer, int length ) { var insideString = false; + for ( var i = 0; i < length; i++ ) { if ( buffer[i] == (byte) '\"' ) diff --git a/src/Hyperbee.Json/Path/JsonPath.cs b/src/Hyperbee.Json/Path/JsonPath.cs index 7e13c227..205e3a90 100644 --- a/src/Hyperbee.Json/Path/JsonPath.cs +++ b/src/Hyperbee.Json/Path/JsonPath.cs @@ -32,6 +32,7 @@ #endregion +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using Hyperbee.Json.Core; @@ -84,6 +85,43 @@ internal static IEnumerable SelectInternal( in TNode value, in TNode root return EnumerateMatches( value, root, compiledQuery, processor ); } + private static IEnumerable EnumerateAllDescendants( TNode node, NodeProcessorDelegate processor ) + { + var kind = Descriptor.ValueAccessor.GetNodeKind( node ); + + if ( kind != NodeKind.Object && kind != NodeKind.Array ) + { + yield return node; + yield break; + } + + var stack = new Stack( 8 ); + var current = node; + + do + { + // .. + foreach ( var child in Descriptor.NodeActions.GetChildren( current, ChildEnumerationOptions.Reverse | ChildEnumerationOptions.ComplexTypesOnly ) ) + stack.Push( child ); + + // * + if ( processor == null ) + { + foreach ( var child in Descriptor.NodeActions.GetChildren( current, ChildEnumerationOptions.None ) ) + yield return child; + } + else + { + foreach ( var (child, key) in Descriptor.NodeActions.GetChildrenWithName( current, ChildEnumerationOptions.None ) ) + { + processor.Invoke( current, child, key, default ); + yield return child; + } + } + + } while ( stack.TryPop( out current ) ); + } + private static IEnumerable EnumerateMatches( in TNode value, in TNode root, JsonQuery compiledQuery, NodeProcessorDelegate processor = null ) { if ( string.IsNullOrWhiteSpace( compiledQuery.Query ) ) // invalid per the RFC ABNF @@ -92,6 +130,9 @@ private static IEnumerable EnumerateMatches( in TNode value, in TNode roo if ( compiledQuery.Query == "$" || compiledQuery.Query == "@" ) // quick out for everything return [value]; + if ( compiledQuery.Query == "$..*" ) // Fast path for $..* + return EnumerateAllDescendants( value, processor ); + var segmentNext = compiledQuery.Segments.Next; // The first segment is always the root; skip it if ( compiledQuery.Normalized ) // we can fast path this @@ -107,7 +148,7 @@ private static IEnumerable EnumerateMatches( in TNode value, in TNode roo private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, NodeProcessorDelegate processor = null ) { - var stack = new NodeArgsStack(); + using var stack = new NodeArgsStack( 32 ); var accessor = Descriptor.ValueAccessor; do @@ -136,7 +177,9 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // reference to the next segment in the list var segmentCurrent = segmentNext; // get current segment - var (selector, selectorKind) = segmentCurrent.Selectors[0]; // first selector in segment + + var selectors = segmentCurrent.Selectors; + var (selector, selectorKind) = selectors[0]; segmentNext = segmentNext.Next; @@ -168,9 +211,8 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // optimization: avoid immediate push pop // // replaces stack.Push( value, childValue, selector, segmentNext ); - DeconstructValues( out parent, out value, out key, out segmentNext, out flags, - (value, childValue, selector, segmentNext, NodeFlags.Default) - ); + + (parent, value, key, segmentNext, flags) = (value, childValue, selector, segmentNext, NodeFlags.Default); goto ProcessArgs; } @@ -179,19 +221,19 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // group selector - var selectorCount = segmentCurrent.Selectors.Length; + var selectorCount = selectors.Length; for ( var i = 0; i < selectorCount; i++ ) // using 'for' for performance { if ( i > 0 ) // we already have the first selector - (selector, selectorKind) = segmentCurrent.Selectors[i]; + (selector, selectorKind) = selectors[i]; switch ( selectorKind ) { // descendant case SelectorKind.Descendant: { - var children = Descriptor.NodeActions.GetChildren( value, complexTypesOnly: true ); + var children = Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse | ChildEnumerationOptions.ComplexTypesOnly ); stack.PushMany( value, children, segmentCurrent, NodeFlags.AfterDescent ); // Union Processing After Descent: If a union operator immediately follows a @@ -203,9 +245,8 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // // this is safe because descendant only ever has one selector. // replaces stack.Push( value, childValue, selector, segmentNext ); - DeconstructValues( out parent, out value, out key, out segmentNext, out flags, // process the current value - (parent, value, null, segmentNext, NodeFlags.AfterDescent) - ); + + (parent, value, key, segmentNext, flags) = (parent, value, null, segmentNext, NodeFlags.AfterDescent); // process the current value goto ProcessArgs; } @@ -214,7 +255,7 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N { var childKind = GetSelectorKind( nodeKind ); - foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildren( value ) ) + foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { // optimization: quicker return for final // @@ -222,8 +263,8 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N // to push and pop values onto the stack that we know will not be used. if ( segmentNext.IsFinal ) { - // we could just yield here, but we can't because we want to preserve - // the order of the results as per the RFC. so we push the current + // if we didn't care about RFC order, we could just yield here. to + // order of the results as per the RFC we need to push the current // value onto the stack without prepending the childKey or childKind // to set up for an immediate return on the next iteration. stack.Push( value, childValue, childKey, segmentNext ); @@ -241,7 +282,7 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N { var childKind = GetSelectorKind( nodeKind ); - foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildren( value ) ) + foreach ( var (childValue, childKey) in Descriptor.NodeActions.GetChildrenWithName( value, ChildEnumerationOptions.Reverse ) ) { if ( !FilterRuntime.Evaluate( selector[1..], childValue, root ) ) // remove the leading '?' character continue; @@ -332,17 +373,6 @@ private static IEnumerable EnumerateMatches( TNode root, NodeArgs args, N } while ( stack.TryPop( out args ) ); } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static void DeconstructValues( out TNode parent, out TNode value, out string key, out JsonSegment segmentNext, out NodeFlags flags, - (TNode Parent, TNode Value, string Key, JsonSegment SegmentNext, NodeFlags Flags) values ) - { - parent = values.Parent; - value = values.Value; - key = values.Key; - segmentNext = values.SegmentNext; - flags = values.Flags; - } - private static bool TryGetChild( IValueAccessor accessor, in TNode value, NodeKind nodeKind, string childSelector, SelectorKind selectorKind, out TNode childValue ) { switch ( nodeKind ) @@ -388,39 +418,141 @@ private static SelectorKind GetSelectorKind( NodeKind nodeKind ) }; } - [DebuggerDisplay( "Parent = {Parent}, Value = {Value}, {Segment}" )] - private readonly record struct NodeArgs( TNode Parent, TNode Value, string Key, JsonSegment Segment, NodeFlags Flags ); + private readonly record struct NodeArgs( in TNode Parent, in TNode Value, string Key, JsonSegment Segment, NodeFlags Flags ); - [DebuggerDisplay( "{_stack}" )] - private sealed class NodeArgsStack( int capacity = 8 ) + [DebuggerDisplay( "{_array}" )] + private sealed class NodeArgsStack : IDisposable { [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] - private readonly Stack _stack = new( capacity ); + private NodeArgs[] _array; + private int _count; + private bool _disposed; + + public NodeArgsStack( int capacity = 16 ) + { + _array = ArrayPool.Shared.Rent( capacity ); + _count = 0; + _disposed = false; + } [MethodImpl( MethodImplOptions.AggressiveInlining )] public void Push( in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) { - _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + EnsureNotDisposed(); + + if ( _count == _array.Length ) + Grow(); + + _array[_count++] = new NodeArgs( parent, value, key, segment, flags ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] public void Push( in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) { - _stack.Push( new NodeArgs( parent, value, IndexHelper.GetIndexString( index ), segment, flags ) ); + Push( parent, value, IndexHelper.GetIndexString( index ), segment, flags ); } public void PushMany( in TNode parent, in IEnumerable<(TNode Value, string Key)> items, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) { + EnsureNotDisposed(); + foreach ( var (value, key) in items ) + Push( parent, value, key, segment, flags ); + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public bool TryPop( out NodeArgs args ) + { + EnsureNotDisposed(); + + if ( _count == 0 ) { - _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + args = default; + return false; } + + var i = --_count; + + args = _array[i]; + //_array[i] = default; // clear entry + + return true; } [MethodImpl( MethodImplOptions.AggressiveInlining )] - public bool TryPop( out NodeArgs args ) + private void Grow() { - return _stack.TryPop( out args ); + int newSize = _array.Length * 2; + var newArray = ArrayPool.Shared.Rent( newSize ); + Array.Copy( _array, newArray, _array.Length ); + ArrayPool.Shared.Return( _array, clearArray: true ); + _array = newArray; + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + private void EnsureCapacity( int min ) + { + if ( _array.Length >= min ) + return; + + var newSize = Math.Max( _array.Length * 2, min ); + var newArray = ArrayPool.Shared.Rent( newSize ); + Array.Copy( _array, newArray, _array.Length ); + ArrayPool.Shared.Return( _array, clearArray: true ); + _array = newArray; + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + private void EnsureNotDisposed() + { + if ( !_disposed ) + return; + + throw new ObjectDisposedException( nameof( NodeArgsStack ) ); + } + + public void Dispose() + { + if ( _disposed ) + return; + + ArrayPool.Shared.Return( _array, clearArray: true ); + _array = null; + _disposed = true; } } + + //private sealed class NodeArgsStack( int capacity = 8 ) + //{ + // [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] + // private readonly Stack _stack = new(capacity); + + // [MethodImpl( MethodImplOptions.AggressiveInlining )] + // public void Push( in TNode parent, in TNode value, string key, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + // { + // _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + // } + + // [MethodImpl( MethodImplOptions.AggressiveInlining )] + // public void Push( in TNode parent, in TNode value, int index, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + // { + // _stack.Push( new NodeArgs( parent, value, IndexHelper.GetIndexString( index ), segment, flags ) ); + // } + + // public void PushMany( in TNode parent, in IEnumerable<(TNode Value, string Key)> items, in JsonSegment segment, NodeFlags flags = NodeFlags.Default ) + // { + // foreach ( var (value, key) in items ) + // { + // _stack.Push( new NodeArgs( parent, value, key, segment, flags ) ); + // } + // } + + // [MethodImpl( MethodImplOptions.AggressiveInlining )] + // public bool TryPop( out NodeArgs args ) + // { + // return _stack.TryPop( out args ); + // } + //} } + + diff --git a/src/Hyperbee.Json/Query/JsonSegment.cs b/src/Hyperbee.Json/Query/JsonSegment.cs index 126f5c7b..c03c7035 100644 --- a/src/Hyperbee.Json/Query/JsonSegment.cs +++ b/src/Hyperbee.Json/Query/JsonSegment.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Hyperbee.Json.Query; @@ -9,6 +10,7 @@ public record SelectorDescriptor public SelectorKind SelectorKind { get; internal set; } public string Value { get; init; } + [MethodImpl( MethodImplOptions.AggressiveInlining )] public void Deconstruct( out string value, out SelectorKind selectorKind ) { value = Value; @@ -17,36 +19,61 @@ public void Deconstruct( out string value, out SelectorKind selectorKind ) } [DebuggerTypeProxy( typeof( SegmentDebugView ) )] -[DebuggerDisplay( "First = ({Selectors?[0]}), IsSingular = {IsSingular}, Count = {Selectors?.Length}" )] +[DebuggerDisplay( "First = {Selectors.Length == 0 ? null : Selectors[0].Value}, IsSingular = {IsSingular}, Count = {Selectors.Length}" )] public class JsonSegment : IEnumerable { internal static readonly JsonSegment Final = new(); // special end node - public bool IsFinal => Next == null; + private readonly SelectorDescriptor[] _selectors; - public bool IsSingular { get; } // singular is true when the selector resolves to one and only one element + public bool IsFinal + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get => Next == null; + } + + // singular is true when the selector resolves to one and only one element + public bool IsSingular + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get; + } + + public JsonSegment Next + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get; - public JsonSegment Next { get; set; } - public SelectorDescriptor[] Selectors { get; init; } + [MethodImpl( MethodImplOptions.AggressiveInlining )] + set; + } - private JsonSegment() { } + public SelectorDescriptor[] Selectors + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + get => _selectors ?? []; + } + + private JsonSegment() + { + IsSingular = false; + } public JsonSegment( JsonSegment next, string selector, SelectorKind kind ) { Next = next; - Selectors = - [ - new SelectorDescriptor { SelectorKind = kind, Value = selector } - ]; + _selectors = [new SelectorDescriptor { SelectorKind = kind, Value = selector }]; IsSingular = SetIsSingular(); } public JsonSegment( SelectorDescriptor[] selectors ) { - Selectors = selectors; + _selectors = selectors; + IsSingular = SetIsSingular(); } + [MethodImpl( MethodImplOptions.AggressiveInlining )] public JsonSegment Prepend( string selector, SelectorKind kind ) { return new JsonSegment( this, selector, kind ); @@ -54,6 +81,7 @@ public JsonSegment Prepend( string selector, SelectorKind kind ) public bool IsNormalized { + [MethodImpl( MethodImplOptions.AggressiveInlining )] get { var current = this; @@ -70,6 +98,7 @@ public bool IsNormalized } } + [MethodImpl( MethodImplOptions.AggressiveInlining )] private bool SetIsSingular() { // the segment is singular, when there is only one selector @@ -81,6 +110,7 @@ private bool SetIsSingular() return (Selectors[0].SelectorKind & SelectorKind.Singular) == SelectorKind.Singular; } + [MethodImpl( MethodImplOptions.AggressiveInlining )] public void Deconstruct( out bool singular, out SelectorDescriptor[] selectors ) { singular = IsSingular; diff --git a/test/Hyperbee.Json.Benchmark/Config.cs b/test/Hyperbee.Json.Benchmark/Config.cs index 113affcc..433ac8af 100644 --- a/test/Hyperbee.Json.Benchmark/Config.cs +++ b/test/Hyperbee.Json.Benchmark/Config.cs @@ -24,7 +24,7 @@ public Config() ); // Customize the summary style to prevent truncation - WithSummaryStyle( SummaryStyle.Default.WithMaxParameterColumnWidth( 50 ) ); + WithSummaryStyle( SummaryStyle.Default.WithMaxParameterColumnWidth( 100 ) ); // Add the custom exporter with specified visible columns AddExporter( new JsonPathMarkdownExporter diff --git a/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj b/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj index 7201256b..ab72340e 100644 --- a/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj +++ b/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj @@ -16,6 +16,7 @@ + diff --git a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs index 1213a16b..875d3767 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelectEvaluator.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; using Hyperbee.Json.Extensions; using JsonCons.JsonPath; using Newtonsoft.Json.Linq; @@ -8,18 +9,45 @@ namespace Hyperbee.Json.Benchmark; + public class JsonPathParseAndSelectEvaluator { [Params( + "$.store.book[0].title", + "$.store.book[*].author", + "$.store.book[?(@.price < 10)].title", + "$.store.bicycle.color", + "$.store.book[*]", + "$.store..price", + "$..author", + "$.store.book[?(@.price > 10 && @.price < 20)]", + "$.store.book[?(@.category == 'fiction')]", + "$.store.book[-1:]", + "$.store.book[:2]", + "$..book[0,1]", + "$..*", + "$..['bicycle','price']", + "$..[?(@.price < 10)]", + "$.store.book[?(@.author && @.title)]", + "$.store.*", + "$", "$.store.book[0]", - "$.store.book[?(@.price == 8.99)]", - "$..price", - "$..* `First()`", - "$..*" + "$..book[0]", + "$.store.book[0,1]", + "$.store.book['category','author']", + "$..book[?@.isbn]", + "$.store.book[?@.price == 8.99]", + "$..book[?@.price == 8.99 && @.category == 'fiction']" )] public string Filter; public string Document; + public JsonNode _node; + public JsonElement _element; + private JObject _jObject; + + + public Consumer _consumer = new(); [GlobalSetup] public void Setup() @@ -63,81 +91,95 @@ public void Setup() } } """; + + + _jObject = JObject.Parse( Document ); + _node = JsonNode.Parse( Document )!; + _element = JsonDocument.Parse( Document ).RootElement; } public (string, bool) GetFilter() { - const string First = " `First()`"; + const string First = " ::First()"; return Filter.EndsWith( First ) ? (Filter[..^First.Length], true) : (Filter, false); } - [Benchmark] + private void Consume( IEnumerable select, bool takeFirst ) + { + if ( takeFirst ) + _ = select.First(); + else + select.Consume( _consumer ); + } + + [Benchmark( Description = "Hyperbee.JsonElement" )] public void Hyperbee_JsonElement() { var (filter, first) = GetFilter(); var element = JsonDocument.Parse( Document ).RootElement; + var select = element.Select( filter ); - if ( first ) - _ = element.Select( filter ).First(); - else - _ = element.Select( filter ).ToArray(); + Consume( select, first ); } - [Benchmark] + [Benchmark( Description = "Hyperbee.JsonNode" )] public void Hyperbee_JsonNode() { var (filter, first) = GetFilter(); var node = JsonNode.Parse( Document )!; + var select = node.Select( filter ); - if ( first ) - _ = node.Select( filter ).First(); - else - _ = node.Select( filter ).ToArray(); + Consume( select, first ); } - [Benchmark] + [Benchmark( Description = "Newtonsoft.JObject" )] public void Newtonsoft_JObject() { var (filter, first) = GetFilter(); var jObject = JObject.Parse( Document ); + var select = jObject.SelectTokens( filter ); - if ( first ) - _ = jObject.SelectTokens( filter ).First(); - else - _ = jObject.SelectTokens( filter ).ToArray(); + Consume( select, first ); } - [Benchmark] + [Benchmark( Description = "JsonEverything.JsonNode" )] public void JsonEverything_JsonNode() { var (filter, first) = GetFilter(); var path = JsonEverything.JsonPath.Parse( filter ); var node = JsonNode.Parse( Document )!; + var select = path.Evaluate( node ).Matches!; - if ( first ) - _ = path.Evaluate( node ).Matches!.First(); - else - _ = path.Evaluate( node ).Matches!.ToArray(); + Consume( select, first ); } - [Benchmark] + [Benchmark( Description = "JsonCons.JsonElement" )] public void JsonCons_JsonElement() { var (filter, first) = GetFilter(); var path = JsonSelector.Parse( filter )!; var element = JsonDocument.Parse( Document ).RootElement; + var select = path.Select( element ); - if ( first ) - _ = path.Select( element ).First(); - else - _ = path.Select( element ); + Consume( select, first ); + } + + [Benchmark( Description = "JsonCraft.JsonElement" )] + public void JsonCraft_JsonElement() + { + var (filter, first) = GetFilter(); + + var element = JsonDocument.Parse( Document ).RootElement; + var select = JsonCraft.JsonPath.JsonExtensions.SelectElements( element, filter ); + + Consume( select, first ); } } diff --git a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs deleted file mode 100644 index 0fb5d8d3..00000000 --- a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using BenchmarkDotNet.Attributes; -using Hyperbee.Json.Extensions; -using Newtonsoft.Json.Linq; - -namespace Hyperbee.Json.Benchmark; - -public class JsonPathSelectEvaluator -{ - [Params( - "$", - "$.store.book[0]", - "$.store..price", - "$..book[0]", - "$.store.*", - "$.store.book[*].author", - "$.store.book[-1:]", - "$.store.book[0,1]", - "$.store.book['category','author']", - "$..book[?@.isbn]", - "$.store.book[?@.price == 8.99]", - "$..*", - "$..book[?@.price == 8.99 && @.category == 'fiction']" - )] - public string Filter; - - public JsonNode _node; - public JsonElement _element; - - private JObject _jObject; - - [GlobalSetup] - public void Setup() - { - const string document = - """ - { - "store": { - "book": [ - { - "category": "reference", - "author": "Nigel Rees", - "title": "Sayings of the Century", - "price": 8.95 - }, - { - "category": "fiction", - "author": "Evelyn Waugh", - "title": "Sword of Honour", - "price": 12.99 - }, - { - "category": "fiction", - "author": "Herman Melville", - "title": "Moby Dick", - "isbn": "0-553-21311-3", - "price": 8.99 - }, - { - "category": "fiction", - "author": "J. R. R. Tolkien", - "title": "The Lord of the Rings", - "isbn": "0-395-19395-8", - "price": 22.99 - } - ], - "bicycle": { - "color": "red", - "price": 19.95 - } - } - } - """; - - _jObject = JObject.Parse( document ); - - _node = JsonNode.Parse( document )!; - _element = JsonDocument.Parse( document ).RootElement; - } - - [Benchmark] - public void Hyperbee_JsonElement() - { - var _ = _element.Select( Filter ).ToArray(); - } - - [Benchmark] - public void Hyperbee_JsonNode() - { - var _ = _node.Select( Filter ).ToArray(); - } - - [Benchmark] - public void Newtonsoft_JObject() - { - var _ = _jObject.SelectTokens( Filter ).ToArray(); - } -} diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md index 66517759..afa7bc1a 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.FilterExpressionParserEvaluator-report-jsonpath.md @@ -1,10 +1,10 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) 12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 | Method | Mean | Error diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md index c8784508..008be474 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonDiffBenchmark-report-jsonpath.md @@ -1,17 +1,17 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) 12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 | Method | Mean | Error | StdDev | Allocated | :-------------------- | --------: | ---------: | --------: | ---------: - | JsonDiff_JsonNode | 473.9 ns | 83.44 ns | 4.57 ns | 1.2 KB - | JsonDiff_JsonElement | 595.7 ns | 283.98 ns | 15.57 ns | 1.66 KB + | JsonDiff_JsonNode | 572.1 ns | 164.76 ns | 9.03 ns | 1.2 KB + | JsonDiff_JsonElement | 704.5 ns | 219.53 ns | 12.03 ns | 1.66 KB | | | | | - | JsonDiff_JsonNode | 591.9 ns | 262.91 ns | 14.41 ns | 1.3 KB - | JsonDiff_JsonElement | 775.5 ns | 75.61 ns | 4.14 ns | 1.93 KB + | JsonDiff_JsonNode | 704.4 ns | 267.47 ns | 14.66 ns | 1.3 KB + | JsonDiff_JsonElement | 905.6 ns | 73.54 ns | 4.03 ns | 1.93 KB ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md index f8f9c316..585b90ed 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPatchBenchmark-report-jsonpath.md @@ -1,16 +1,16 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) 12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 | Method | Mean | Error | StdDev | Allocated | :----------------------- | --------: | ---------: | -------: | ---------: - | Hyperbee_JsonNode | 139.2 ns | 26.87 ns | 1.47 ns | 520 B - | Hyperbee_JsonElement | 145.1 ns | 26.35 ns | 1.44 ns | 520 B - | JsonEverything_JsonNode | 232.5 ns | 116.18 ns | 6.37 ns | 968 B - | AspNetCore_JsonNode | 441.0 ns | 173.80 ns | 9.53 ns | 1024 B + | Hyperbee_JsonNode | 172.9 ns | 44.86 ns | 2.46 ns | 520 B + | Hyperbee_JsonElement | 178.8 ns | 76.47 ns | 4.19 ns | 520 B + | JsonEverything_JsonNode | 289.7 ns | 108.59 ns | 5.95 ns | 968 B + | AspNetCore_JsonNode | 516.7 ns | 44.74 ns | 2.45 ns | 1024 B ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md index 82d7a9b0..12a72cb0 100644 --- a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathParseAndSelectEvaluator-report-jsonpath.md @@ -1,46 +1,219 @@ ``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) 12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores -.NET SDK 9.0.103 - [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 [AttachedDebugger] - ShortRun : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 | Method | Mean | Error | StdDev | Allocated | :----------------------- | ---------: | ---------: | ---------: | ---------: - | `$..* First()` - | Hyperbee_JsonElement | 1.867 μs | 0.6072 μs | 0.0333 μs | 3.5 KB - | Hyperbee_JsonNode | 1.916 μs | 0.2202 μs | 0.0121 μs | 3.11 KB - | JsonEverything_JsonNode | 2.097 μs | 0.7484 μs | 0.0410 μs | 3.49 KB - | JsonCons_JsonElement | 3.498 μs | 0.8161 μs | 0.0447 μs | 8.48 KB - | Newtonsoft_JObject | 5.734 μs | 1.1777 μs | 0.0646 μs | 14.22 KB + | `$..[?(@.price < 10)]` + | JsonCraft.JsonElement | 3.841 μs | 0.3111 μs | 0.0171 μs | 3.59 KB + | JsonCons.JsonElement | 7.459 μs | 0.6412 μs | 0.0351 μs | 12.77 KB + | Hyperbee.JsonElement | 8.284 μs | 2.5497 μs | 0.1398 μs | 20.73 KB + | Hyperbee.JsonNode | 11.086 μs | 8.0907 μs | 0.4435 μs | 23.79 KB + | Newtonsoft.JObject | 12.153 μs | 2.4881 μs | 0.1364 μs | 25.86 KB + | JsonEverything.JsonNode | 23.541 μs | 1.3946 μs | 0.0764 μs | 48.15 KB + | | | | | + | `$..['bicycle','price']` + | JsonCraft.JsonElement | 3.136 μs | 0.2760 μs | 0.0151 μs | 4.01 KB + | Hyperbee.JsonElement | 3.578 μs | 0.4623 μs | 0.0253 μs | 5.37 KB + | JsonCons.JsonElement | 3.948 μs | 0.6099 μs | 0.0334 μs | 7.18 KB + | Hyperbee.JsonNode | 5.181 μs | 1.8016 μs | 0.0988 μs | 9.23 KB + | Newtonsoft.JObject | 7.823 μs | 0.8017 μs | 0.0439 μs | 14.55 KB + | JsonEverything.JsonNode | 16.753 μs | 1.5507 μs | 0.0850 μs | 28.5 KB | | | | | | `$..*` - | JsonCons_JsonElement | 3.525 μs | 1.0092 μs | 0.0553 μs | 8.45 KB - | Hyperbee_JsonElement | 4.288 μs | 0.4672 μs | 0.0256 μs | 8.38 KB - | Hyperbee_JsonNode | 5.744 μs | 0.8133 μs | 0.0446 μs | 11.22 KB - | Newtonsoft_JObject | 7.111 μs | 1.1213 μs | 0.0615 μs | 14.43 KB - | JsonEverything_JsonNode | 18.571 μs | 5.4711 μs | 0.2999 μs | 34.2 KB - | | | | | - | `$..price` - | Hyperbee_JsonElement | 2.599 μs | 0.2500 μs | 0.0137 μs | 4.11 KB - | JsonCons_JsonElement | 2.962 μs | 0.1946 μs | 0.0107 μs | 5.65 KB - | Hyperbee_JsonNode | 4.107 μs | 0.8757 μs | 0.0480 μs | 8.22 KB - | Newtonsoft_JObject | 6.653 μs | 1.2770 μs | 0.0700 μs | 14.26 KB - | JsonEverything_JsonNode | 13.430 μs | 4.4075 μs | 0.2416 μs | 26.46 KB - | | | | | - | `$.store.book[?(@.price == 8.99)]` - | Hyperbee_JsonElement | 2.778 μs | 0.3989 μs | 0.0219 μs | 5.41 KB - | JsonCons_JsonElement | 3.282 μs | 0.5416 μs | 0.0297 μs | 5.05 KB - | Hyperbee_JsonNode | 4.279 μs | 0.4826 μs | 0.0265 μs | 8.95 KB - | Newtonsoft_JObject | 5.986 μs | 0.7627 μs | 0.0418 μs | 15.78 KB - | JsonEverything_JsonNode | 7.135 μs | 1.1539 μs | 0.0632 μs | 15.5 KB + | JsonCraft.JsonElement | 2.497 μs | 0.0903 μs | 0.0049 μs | 2.88 KB + | Hyperbee.JsonElement | 3.299 μs | 0.8178 μs | 0.0448 μs | 6.51 KB + | JsonCons.JsonElement | 4.176 μs | 0.5887 μs | 0.0323 μs | 8.49 KB + | Hyperbee.JsonNode | 5.330 μs | 0.7684 μs | 0.0421 μs | 10.3 KB + | Newtonsoft.JObject | 7.784 μs | 0.5303 μs | 0.0291 μs | 14.19 KB + | JsonEverything.JsonNode | 21.226 μs | 1.0905 μs | 0.0598 μs | 33.97 KB + | | | | | + | `$..author` + | JsonCraft.JsonElement | 2.904 μs | 4.2915 μs | 0.2352 μs | 2.88 KB + | Hyperbee.JsonElement | 3.068 μs | 0.3672 μs | 0.0201 μs | 5.16 KB + | JsonCons.JsonElement | 3.381 μs | 0.6294 μs | 0.0345 μs | 5.55 KB + | Hyperbee.JsonNode | 5.030 μs | 0.6100 μs | 0.0334 μs | 9.02 KB + | Newtonsoft.JObject | 7.469 μs | 1.7563 μs | 0.0963 μs | 14.2 KB + | JsonEverything.JsonNode | 15.752 μs | 3.8966 μs | 0.2136 μs | 26.1 KB + | | | | | + | `$..book[?@.isbn]` + | Hyperbee.JsonElement | 3.913 μs | 1.5846 μs | 0.0869 μs | 6.8 KB + | JsonCons.JsonElement | 4.359 μs | 2.1655 μs | 0.1187 μs | 7.21 KB + | Hyperbee.JsonNode | 5.335 μs | 0.7057 μs | 0.0387 μs | 10.62 KB + | JsonEverything.JsonNode | 17.200 μs | 1.9836 μs | 0.1087 μs | 29.98 KB + | JsonCraft.JsonElement | NA | NA | NA | NA + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$..book[?@.price == 8.99 && @.category == 'fiction']` + | Hyperbee.JsonElement | 5.135 μs | 1.0302 μs | 0.0565 μs | 9.47 KB + | JsonCons.JsonElement | 6.105 μs | 0.6309 μs | 0.0346 μs | 8.52 KB + | Hyperbee.JsonNode | 7.002 μs | 3.0008 μs | 0.1645 μs | 13.41 KB + | JsonEverything.JsonNode | 21.845 μs | 3.9271 μs | 0.2153 μs | 39.52 KB + | JsonCraft.JsonElement | NA | NA | NA | NA + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$..book[0,1]` + | JsonCraft.JsonElement | 2.936 μs | 0.6578 μs | 0.0361 μs | 3.09 KB + | Hyperbee.JsonElement | 3.157 μs | 0.8302 μs | 0.0455 μs | 5.16 KB + | JsonCons.JsonElement | 3.691 μs | 0.1430 μs | 0.0078 μs | 6.15 KB + | Hyperbee.JsonNode | 4.972 μs | 0.6586 μs | 0.0361 μs | 9.02 KB + | Newtonsoft.JObject | 7.441 μs | 0.5961 μs | 0.0327 μs | 14.45 KB + | JsonEverything.JsonNode | 15.523 μs | 5.4908 μs | 0.3010 μs | 26.41 KB + | | | | | + | `$..book[0]` + | JsonCraft.JsonElement | 2.825 μs | 0.0934 μs | 0.0051 μs | 3 KB + | Hyperbee.JsonElement | 3.114 μs | 0.4106 μs | 0.0225 μs | 5.16 KB + | JsonCons.JsonElement | 3.451 μs | 0.2921 μs | 0.0160 μs | 5.63 KB + | Hyperbee.JsonNode | 4.691 μs | 1.4048 μs | 0.0770 μs | 9.02 KB + | Newtonsoft.JObject | 7.720 μs | 0.8748 μs | 0.0480 μs | 14.33 KB + | JsonEverything.JsonNode | 15.314 μs | 3.3459 μs | 0.1834 μs | 26.02 KB + | | | | | + | `$.store..price` + | JsonCraft.JsonElement | 2.912 μs | 1.3083 μs | 0.0717 μs | 3.13 KB + | Hyperbee.JsonElement | 2.993 μs | 0.4534 μs | 0.0249 μs | 4.8 KB + | JsonCons.JsonElement | 3.446 μs | 0.9305 μs | 0.0510 μs | 5.62 KB + | Hyperbee.JsonNode | 4.721 μs | 0.9516 μs | 0.0522 μs | 8.7 KB + | Newtonsoft.JObject | 7.723 μs | 1.1978 μs | 0.0657 μs | 14.34 KB + | JsonEverything.JsonNode | 15.966 μs | 1.1138 μs | 0.0610 μs | 26.63 KB + | | | | | + | `$.store.*` + | JsonCraft.JsonElement | 1.882 μs | 0.5586 μs | 0.0306 μs | 2.49 KB + | JsonCons.JsonElement | 2.151 μs | 0.1067 μs | 0.0058 μs | 3.31 KB + | Hyperbee.JsonElement | 2.167 μs | 0.6457 μs | 0.0354 μs | 2.88 KB + | Hyperbee.JsonNode | 2.435 μs | 0.9690 μs | 0.0531 μs | 2.95 KB + | JsonEverything.JsonNode | 3.047 μs | 0.4674 μs | 0.0256 μs | 4.8 KB + | Newtonsoft.JObject | 6.576 μs | 0.8290 μs | 0.0454 μs | 14.43 KB + | | | | | + | `$.store.bicycle.color` + | Hyperbee.JsonElement | 1.920 μs | 0.6179 μs | 0.0339 μs | 2.3 KB + | JsonCraft.JsonElement | 2.032 μs | 0.7770 μs | 0.0426 μs | 2.49 KB + | JsonCons.JsonElement | 2.216 μs | 0.1473 μs | 0.0081 μs | 3.27 KB + | Hyperbee.JsonNode | 2.383 μs | 1.2149 μs | 0.0666 μs | 2.88 KB + | JsonEverything.JsonNode | 3.556 μs | 0.5152 μs | 0.0282 μs | 5.74 KB + | Newtonsoft.JObject | 6.700 μs | 1.6404 μs | 0.0899 μs | 14.49 KB + | | | | | + | `$.store.book[-1:]` + | JsonCraft.JsonElement | 2.004 μs | 0.3158 μs | 0.0173 μs | 2.58 KB + | Hyperbee.JsonElement | 2.087 μs | 0.1721 μs | 0.0094 μs | 2.47 KB + | JsonCons.JsonElement | 2.399 μs | 0.3533 μs | 0.0194 μs | 3.56 KB + | Hyperbee.JsonNode | 2.467 μs | 0.9814 μs | 0.0538 μs | 2.97 KB + | JsonEverything.JsonNode | 3.679 μs | 0.4354 μs | 0.0239 μs | 5.72 KB + | Newtonsoft.JObject | 6.472 μs | 1.5636 μs | 0.0857 μs | 14.52 KB + | | | | | + | `$.store.book[:2]` + | JsonCraft.JsonElement | 2.010 μs | 0.1463 μs | 0.0080 μs | 2.58 KB + | Hyperbee.JsonElement | 2.128 μs | 0.4884 μs | 0.0268 μs | 2.47 KB + | JsonCons.JsonElement | 2.382 μs | 0.0585 μs | 0.0032 μs | 3.59 KB + | Hyperbee.JsonNode | 2.450 μs | 0.1728 μs | 0.0095 μs | 2.97 KB + | JsonEverything.JsonNode | 4.019 μs | 2.1096 μs | 0.1156 μs | 6.02 KB + | Newtonsoft.JObject | 6.899 μs | 0.6775 μs | 0.0371 μs | 14.51 KB + | | | | | + | `$.store.book[?(@.author && @.title)]` + | JsonCraft.JsonElement | 2.567 μs | 0.2239 μs | 0.0123 μs | 3.3 KB + | Hyperbee.JsonElement | 3.362 μs | 0.2369 μs | 0.0130 μs | 5.52 KB + | JsonCons.JsonElement | 3.805 μs | 0.8769 μs | 0.0481 μs | 5.63 KB + | Hyperbee.JsonNode | 5.128 μs | 1.1406 μs | 0.0625 μs | 9.23 KB + | Newtonsoft.JObject | 7.514 μs | 1.6892 μs | 0.0926 μs | 16.18 KB + | JsonEverything.JsonNode | 9.261 μs | 3.4741 μs | 0.1904 μs | 18.32 KB + | | | | | + | `$.store.book[?(@.category == 'fiction')]` + | JsonCraft.JsonElement | 2.734 μs | 0.3242 μs | 0.0178 μs | 3.38 KB + | Hyperbee.JsonElement | 3.315 μs | 0.3024 μs | 0.0166 μs | 5.09 KB + | JsonCons.JsonElement | 3.426 μs | 1.0773 μs | 0.0590 μs | 5.05 KB + | Hyperbee.JsonNode | 5.003 μs | 0.8363 μs | 0.0458 μs | 8.89 KB + | Newtonsoft.JObject | 7.213 μs | 1.1931 μs | 0.0654 μs | 15.74 KB + | JsonEverything.JsonNode | 8.898 μs | 1.8821 μs | 0.1032 μs | 16.49 KB + | | | | | + | `$.store.book[?(@.price < 10)].title` + | JsonCraft.JsonElement | 3.108 μs | 1.8864 μs | 0.1034 μs | 3.37 KB + | Hyperbee.JsonElement | 3.353 μs | 0.4814 μs | 0.0264 μs | 5.1 KB + | JsonCons.JsonElement | 4.008 μs | 0.5005 μs | 0.0274 μs | 5.27 KB + | Hyperbee.JsonNode | 5.285 μs | 0.7662 μs | 0.0420 μs | 8.78 KB + | Newtonsoft.JObject | 7.687 μs | 1.0056 μs | 0.0551 μs | 15.89 KB + | JsonEverything.JsonNode | 9.513 μs | 6.3528 μs | 0.3482 μs | 17.38 KB + | | | | | + | `$.store.book[?(@.price > 10 && @.price < 20)]` + | JsonCraft.JsonElement | 3.475 μs | 0.0797 μs | 0.0044 μs | 3.82 KB + | Hyperbee.JsonElement | 4.010 μs | 0.6169 μs | 0.0338 μs | 6.55 KB + | JsonCons.JsonElement | 5.102 μs | 1.1449 μs | 0.0628 μs | 6.28 KB + | Hyperbee.JsonNode | 6.086 μs | 1.3252 μs | 0.0726 μs | 10.27 KB + | Newtonsoft.JObject | 8.050 μs | 0.6497 μs | 0.0356 μs | 16.69 KB + | JsonEverything.JsonNode | 11.612 μs | 0.2688 μs | 0.0147 μs | 22.27 KB + | | | | | + | `$.store.book[?@.price == 8.99]` + | Hyperbee.JsonElement | 3.162 μs | 0.9502 μs | 0.0521 μs | 4.9 KB + | JsonCons.JsonElement | 3.822 μs | 0.0869 μs | 0.0048 μs | 5.02 KB + | Hyperbee.JsonNode | 4.889 μs | 1.5106 μs | 0.0828 μs | 8.58 KB + | JsonEverything.JsonNode | 8.310 μs | 2.0101 μs | 0.1102 μs | 15.47 KB + | JsonCraft.JsonElement | NA | NA | NA | NA + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$.store.book['category','author']` + | JsonCraft.JsonElement | 2.084 μs | 0.4663 μs | 0.0256 μs | 2.95 KB + | JsonCons.JsonElement | 2.480 μs | 0.0770 μs | 0.0042 μs | 3.61 KB + | Hyperbee.JsonElement | 2.569 μs | 1.1453 μs | 0.0628 μs | 2.67 KB + | JsonEverything.JsonNode | 3.309 μs | 0.2755 μs | 0.0151 μs | 5.41 KB + | Hyperbee.JsonNode | 4.142 μs | 1.4670 μs | 0.0804 μs | 6.42 KB + | Newtonsoft.JObject | 6.737 μs | 0.5860 μs | 0.0321 μs | 14.85 KB + | | | | | + | `$.store.book[*].author` + | JsonCraft.JsonElement | 2.256 μs | 1.4487 μs | 0.0794 μs | 2.63 KB + | Hyperbee.JsonElement | 2.469 μs | 0.7492 μs | 0.0411 μs | 3.12 KB + | JsonCons.JsonElement | 2.496 μs | 0.3427 μs | 0.0188 μs | 3.59 KB + | Hyperbee.JsonNode | 4.088 μs | 0.0348 μs | 0.0019 μs | 6.83 KB + | Newtonsoft.JObject | 7.169 μs | 1.8980 μs | 0.1040 μs | 14.64 KB + | JsonEverything.JsonNode | 7.511 μs | 0.2592 μs | 0.0142 μs | 12.45 KB + | | | | | + | `$.store.book[*]` + | JsonCraft.JsonElement | 2.048 μs | 1.8693 μs | 0.1025 μs | 2.48 KB + | Hyperbee.JsonElement | 2.179 μs | 0.6664 μs | 0.0365 μs | 2.71 KB + | JsonCons.JsonElement | 2.246 μs | 0.1811 μs | 0.0099 μs | 3.4 KB + | Hyperbee.JsonNode | 2.672 μs | 0.2099 μs | 0.0115 μs | 3.17 KB + | JsonEverything.JsonNode | 4.315 μs | 0.0253 μs | 0.0014 μs | 6.61 KB + | Newtonsoft.JObject | 6.632 μs | 1.6876 μs | 0.0925 μs | 14.49 KB + | | | | | + | `$.store.book[0,1]` + | Hyperbee.JsonElement | 2.049 μs | 0.0126 μs | 0.0007 μs | 2.47 KB + | JsonCraft.JsonElement | 2.056 μs | 0.2169 μs | 0.0119 μs | 2.64 KB + | Hyperbee.JsonNode | 2.435 μs | 0.5524 μs | 0.0303 μs | 2.97 KB + | JsonCons.JsonElement | 2.554 μs | 1.3052 μs | 0.0715 μs | 3.77 KB + | JsonEverything.JsonNode | 3.987 μs | 0.4523 μs | 0.0248 μs | 6.07 KB + | Newtonsoft.JObject | 6.648 μs | 0.7321 μs | 0.0401 μs | 14.59 KB + | | | | | + | `$.store.book[0].title` + | Hyperbee.JsonElement | 1.897 μs | 0.1620 μs | 0.0089 μs | 2.27 KB + | JsonCraft.JsonElement | 2.055 μs | 0.2876 μs | 0.0158 μs | 2.55 KB + | JsonCons.JsonElement | 2.318 μs | 0.3233 μs | 0.0177 μs | 3.35 KB + | Hyperbee.JsonNode | 2.575 μs | 0.1096 μs | 0.0060 μs | 3.63 KB + | JsonEverything.JsonNode | 4.534 μs | 1.5492 μs | 0.0849 μs | 7.38 KB + | Newtonsoft.JObject | 6.734 μs | 2.0745 μs | 0.1137 μs | 14.62 KB | | | | | | `$.store.book[0]` - | Hyperbee_JsonElement | 1.623 μs | 0.3592 μs | 0.0197 μs | 2.27 KB - | Hyperbee_JsonNode | 1.925 μs | 0.1748 μs | 0.0096 μs | 2.86 KB - | JsonCons_JsonElement | 1.937 μs | 0.1595 μs | 0.0087 μs | 3.21 KB - | JsonEverything_JsonNode | 3.095 μs | 0.4032 μs | 0.0221 μs | 5.71 KB - | Newtonsoft_JObject | 5.690 μs | 1.3949 μs | 0.0765 μs | 14.51 KB + | Hyperbee.JsonElement | 1.931 μs | 0.1500 μs | 0.0082 μs | 2.27 KB + | JsonCraft.JsonElement | 1.996 μs | 0.1804 μs | 0.0099 μs | 2.48 KB + | Hyperbee.JsonNode | 2.204 μs | 0.1747 μs | 0.0096 μs | 2.86 KB + | JsonCons.JsonElement | 2.221 μs | 0.1388 μs | 0.0076 μs | 3.26 KB + | JsonEverything.JsonNode | 3.592 μs | 0.3654 μs | 0.0200 μs | 5.68 KB + | Newtonsoft.JObject | 6.892 μs | 2.7132 μs | 0.1487 μs | 14.48 KB + | | | | | + | `$` + | JsonCraft.JsonElement | 1.732 μs | 0.0529 μs | 0.0029 μs | 2.26 KB + | Hyperbee.JsonElement | 1.751 μs | 0.0628 μs | 0.0034 μs | 2.27 KB + | JsonEverything.JsonNode | 1.767 μs | 0.0675 μs | 0.0037 μs | 1.88 KB + | Hyperbee.JsonNode | 1.790 μs | 0.1222 μs | 0.0067 μs | 1.78 KB + | JsonCons.JsonElement | 1.929 μs | 0.1539 μs | 0.0084 μs | 2.98 KB + | Newtonsoft.JObject | 6.587 μs | 0.7309 μs | 0.0401 μs | 14.01 KB + +Benchmarks with issues: + JsonPathParseAndSelectEvaluator.JsonCraft.JsonElement: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathParseAndSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathParseAndSelectEvaluator.JsonCraft.JsonElement: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == 8.99 && @.category == 'fiction']] + JsonPathParseAndSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == 8.99 && @.category == 'fiction']] + JsonPathParseAndSelectEvaluator.JsonCraft.JsonElement: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] + JsonPathParseAndSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] ``` diff --git a/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md new file mode 100644 index 00000000..5c451802 --- /dev/null +++ b/test/Hyperbee.Json.Benchmark/benchmark/results/Hyperbee.Json.Benchmark.JsonPathSelectEvaluator-report-jsonpath.md @@ -0,0 +1,81 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4351) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 9.0.301 + [Host] : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 [AttachedDebugger] + ShortRun : .NET 9.0.6 (9.0.625.26613), X64 RyuJIT AVX2 + + + | Method | Mean | Error | StdDev | Allocated + | :-------------------- | -----------: | ------------: | ---------: | ---------: + | `$..*` + | Newtonsoft.JObject | 1,129.64 ns | 200.111 ns | 10.969 ns | 320 B + | Hyperbee.JsonElement | 1,435.16 ns | 270.030 ns | 14.801 ns | 4496 B + | Hyperbee.JsonNode | 1,635.74 ns | 212.133 ns | 11.628 ns | 4120 B + | | | | | + | `$..book[?@.isbn]` + | Hyperbee.JsonNode | 1,751.53 ns | 67.009 ns | 3.673 ns | 4440 B + | Hyperbee.JsonElement | 1,939.70 ns | 1,281.085 ns | 70.221 ns | 4792 B + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$..book[?@.price == (...)tegory == 'fiction'] [52]` + | Hyperbee.JsonElement | 3,053.63 ns | 138.091 ns | 7.569 ns | 7528 B + | Hyperbee.JsonNode | 3,202.07 ns | 626.290 ns | 34.329 ns | 7304 B + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$..book[0]` + | Newtonsoft.JObject | 952.67 ns | 80.154 ns | 4.394 ns | 464 B + | Hyperbee.JsonNode | 1,066.23 ns | 199.175 ns | 10.917 ns | 2808 B + | Hyperbee.JsonElement | 1,126.19 ns | 60.415 ns | 3.312 ns | 3120 B + | | | | | + | `$.store..price` + | Newtonsoft.JObject | 1,002.20 ns | 80.903 ns | 4.435 ns | 472 B + | Hyperbee.JsonNode | 1,016.07 ns | 17.480 ns | 0.958 ns | 2480 B + | Hyperbee.JsonElement | 1,019.12 ns | 269.498 ns | 14.772 ns | 2744 B + | | | | | + | `$.store.*` + | Newtonsoft.JObject | 197.98 ns | 45.002 ns | 2.467 ns | 568 B + | Hyperbee.JsonNode | 284.19 ns | 87.546 ns | 4.799 ns | 632 B + | Hyperbee.JsonElement | 320.20 ns | 82.718 ns | 4.534 ns | 776 B + | | | | | + | `$.store.book[-1:]` + | Hyperbee.JsonNode | 206.03 ns | 60.986 ns | 3.343 ns | 304 B + | Hyperbee.JsonElement | 229.31 ns | 20.912 ns | 1.146 ns | 360 B + | Newtonsoft.JObject | 237.86 ns | 48.031 ns | 2.633 ns | 656 B + | | | | | + | `$.store.book[?@.price == 8.99]` + | Hyperbee.JsonElement | 1,238.46 ns | 117.376 ns | 6.434 ns | 2848 B + | Hyperbee.JsonNode | 1,321.87 ns | 540.536 ns | 29.629 ns | 2720 B + | Newtonsoft.JObject | NA | NA | NA | NA + | | | | | + | `$.store.book['category','author']` + | Newtonsoft.JObject | 293.04 ns | 156.371 ns | 8.571 ns | 1000 B + | Hyperbee.JsonNode | 553.55 ns | 28.365 ns | 1.555 ns | 512 B + | Hyperbee.JsonElement | 680.40 ns | 61.553 ns | 3.374 ns | 568 B + | | | | | + | `$.store.book[*].author` + | Newtonsoft.JObject | 379.88 ns | 30.004 ns | 1.645 ns | 784 B + | Hyperbee.JsonNode | 541.65 ns | 59.901 ns | 3.283 ns | 928 B + | Hyperbee.JsonElement | 652.23 ns | 124.684 ns | 6.834 ns | 1024 B + | | | | | + | `$.store.book[0,1]` + | Hyperbee.JsonNode | 215.48 ns | 13.220 ns | 0.725 ns | 304 B + | Hyperbee.JsonElement | 245.08 ns | 17.875 ns | 0.980 ns | 360 B + | Newtonsoft.JObject | 279.02 ns | 168.868 ns | 9.256 ns | 736 B + | | | | | + | `$.store.book[0]` + | Hyperbee.JsonElement | 101.05 ns | 4.460 ns | 0.244 ns | 160 B + | Hyperbee.JsonNode | 101.29 ns | 11.959 ns | 0.655 ns | 192 B + | Newtonsoft.JObject | 236.41 ns | 17.471 ns | 0.958 ns | 616 B + | | | | | + | `$` + | Newtonsoft.JObject | 31.69 ns | 9.529 ns | 0.522 ns | 136 B + | Hyperbee.JsonNode | 36.08 ns | 9.450 ns | 0.518 ns | 144 B + | Hyperbee.JsonElement | 39.98 ns | 11.650 ns | 0.639 ns | 160 B + +Benchmarks with issues: + JsonPathSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.isbn]] + JsonPathSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$..book[?@.price == (...)tegory == 'fiction'] [52]] + JsonPathSelectEvaluator.Newtonsoft.JObject: ShortRun(IterationCount=3, LaunchCount=1, WarmupCount=3) [Filter=$.store.book[?@.price == 8.99]] +``` diff --git a/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs b/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs index 93c9ba33..36312d59 100644 --- a/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs +++ b/test/Hyperbee.Json.Tests/Path/Query/JsonPathBookstoreTests.cs @@ -205,7 +205,7 @@ public void FilterAllBooksCheaperThan10( string query, Type sourceType ) public void AllMembersOfJsonStructure( string query, Type sourceType ) { var source = GetDocumentAdapter( sourceType ); - var matches = source.Select( query ); + var matches = source.Select( query ).ToArray(); var expected = new[] { source.FromJsonPathPointer( "$.store" ),