Compare commits

...

80 Commits

Author SHA1 Message Date
Daniel Lemire 1bcf71bd85 release 4.6.4 2026-05-06 17:29:56 -04:00
Daniel Lemire f01641e463 overflow patch. 2026-05-06 17:28:39 -04:00
Daniel Lemire 65e59ccf9b patch 4.6.3 2026-04-20 12:07:05 -04:00
Daniel Lemire 61641f9a7a version bump. 2026-04-17 17:22:06 -04:00
Daniel Lemire 996dc25180 adding forward declaration 2026-04-17 11:03:09 -04:00
Daniel Lemire 77cf997595 moving example 2026-04-16 22:07:59 -04:00
Daniel Lemire 453ae4e955 more fixes 2026-04-16 18:48:37 -04:00
Daniel Lemire f5602fa3c7 update 2026-04-16 18:39:43 -04:00
Daniel Lemire 942f7f298b simplifying. 2026-04-16 18:39:43 -04:00
Daniel Lemire 6a4fc260a1 fixing issue 2684 2026-04-16 18:39:30 -04:00
Daniel Lemire fb83b114ef 4.6.1 2026-04-03 15:26:12 -04:00
jmestwa-coder 542cd7af71 parser.load(string_view) does not respect view length when opening files (#2659) 2026-04-03 15:25:03 -04:00
Daniel Lemire 769364abb2 simplifying the logic 2026-03-30 20:01:35 -04:00
Daniel Lemire f25d5f9fc2 4.6.0 (#2655) 2026-03-30 19:51:00 -04:00
Eyüp Can Akman 12fb30ae9c add get_int32() and get_uint32() to the On Demand API (#2638)
* add get_int32() and get_uint32() to the On Demand API

Add convenience methods that call get_int64()/get_uint64() and
range-check the result, returning NUMBER_OUT_OF_RANGE on overflow.

Added to value, document, document_reference, and their
simdjson_result wrappers.

Closes #1890

* fix document_reference streaming for get_int32/get_uint32

The document_reference getters delegated to document::get_int32/get_uint32
which call get_root_int64/get_root_uint64 with allow_trailing_content=true,
rejecting the next document in a stream as trailing content.

Call get_root_value_iterator().get_root_int64/get_root_uint64(false) directly
to allow trailing content, matching get_int64/get_uint64.

Add streaming tests for both getters.

* add get_int32/get_uint32 to basics.md

* add get_int32/get_uint32 to wrong-type error tests

* add get<uint32_t>/get<int32_t> template specializations

Wire the 32-bit getters into the generic get<T>() and get(T&)
dispatch so that doc.get<uint32_t>() compiles on C++11/14/17
without requiring the C++20 concepts path in std_deserialize.h.
2026-03-30 10:16:31 -04:00
uwezkhan c0a952687d Add overflow checks in padded_string_builder::reserve (#2653)
* Add overflow checks in padded_string_builder::reserve

* updated

---------

Co-authored-by: uwezkhan06 <uwezkhan055@gmail.com>
2026-03-30 10:16:05 -04:00
uwezkhan 307ddd4415 Avoid overflow in padded_string_builder::reserve (#2654)
Co-authored-by: uwezkhan06 <uwezkhan055@gmail.com>
2026-03-30 10:13:08 -04:00
Edoardo Bortolozzo 21657455ed build: add SIMDJSON_INSTALL option (#2649) 2026-03-29 15:54:44 -04:00
jmestwa-coder afb1c9f4b1 correct inverted clamp in set_max_capacity (#2651) 2026-03-29 15:54:28 -04:00
uwezkhan 9973f7b3a3 Fix fopen usage with string_view (#2652)
Co-authored-by: uwezkhan06 <uwezkhan055@gmail.com>
2026-03-29 15:54:11 -04:00
Daniel Lemire 18d612df15 4.5.0 2026-03-25 17:26:13 -04:00
harshanagd bddb23177e feat: Add DOM tape support for big integers (opt-in) (#2640)
When parser.number_as_string(true) is set and a number exceeds uint64
range, write the raw digits to the string buffer and emit a BIGINT ('Z')
tape tag instead of returning BIGINT_ERROR. Default behavior unchanged.

Changes:
- tape_type.h: add BIGINT = 'Z'
- dom_parser_implementation.h: add _number_as_string flag
- parser.h: add number_as_string() getter/setter
- parser-inl.h: propagate flag before parse
- tape_builder.h: visit_number checks flag on BIGINT_ERROR
- tape_writer.h: add append_bigint helper
- serialization-inl.h: handle BIGINT in serialization (raw digits)
- tape.md: document big integer tape format

Builds on PR #2139 which added BIGINT_ERROR detection.
All 119 existing tests pass — default behavior unchanged.

Closes #167

Co-authored-by: harshagd <harshagd@amazon.com>
2026-03-25 17:25:37 -04:00
Rajdeep 2c5de6c6ed Fix PCH build failure with <bit> on non-C++20 compilers (#2646)
* Added benchmarks for on-demand get_int64 and get_double

* Remove local IDE settings

* Fix PCH build failure with <bit> on non-C++20 compilers

* Update bench_dom_api.cpp
2026-03-25 17:20:43 -04:00
Daniel Lemire 844e5eac23 bump google bench 2026-03-25 09:29:33 -04:00
Daniel Lemire 45caa861cd Update README with talk information
Added details about talks at QCon San Francisco 2019 and CppCon 2025.
2026-03-24 22:00:42 -04:00
Andrey Abramov 50bf372d3f Add SereneDB to the list of projects (#2641)
Add SereneDB to the list of projects
2026-03-22 12:37:23 -04:00
Daniel Lemire b4a0fd3ff4 4.4.2 2026-03-20 11:12:35 -04:00
Levi Zim a1ff05a5e8 Fix RISC-V compilation without RVV (#2637)
#2617 introduced an RVV check that only checks if the compiler supports RVV intrinsics without checking if RVV is enabled at compile time.

This has caused compilation failure of node.js on riscv64. Fix it by appending a check for __riscv_vector.
2026-03-20 11:11:38 -04:00
Daniel Lemire 5395aacb8b v4.4.1 2026-03-17 12:39:07 -04:00
Daniel Lemire 781ea4b495 fixing issue https://github.com/nodejs/node/pull/62257 (#2631) 2026-03-17 12:38:11 -04:00
Jaël Champagne Gareau 03e8d14eee fix failing CI and rename misleading workflow name (#2630) 2026-03-15 18:40:08 -04:00
Daniel Lemire be94694290 v4.4.0 2026-03-12 20:43:36 -04:00
Daniel Lemire 6065b3b424 disabling loongson LASX runtime dispatching everywhere but GCC15+ (#2626) 2026-03-12 20:40:43 -04:00
Daniel Lemire 019bf1dfe0 adding memory-file mapping (#2625)
* adding memory-file mapping

* tuning

* init _capacity

* adding missing header

* update doc
2026-03-12 14:36:01 -04:00
Daniel Lemire 2922822622 altivec version of find_next_json_quotable_character (#2622)
* altivec version of find_next_json_quotable_character

* simplify

* saving.
2026-03-08 14:06:01 -04:00
Daniel Lemire 262ddad037 Update bug report template with support policy details
Clarified support policy for obsolete compiler systems and vendors.
2026-03-02 14:08:04 -05:00
Hendrik ba007b0cb9 add mruby-fast-json binding (#2616)
Hi there,

i took simdjson as a learning opportunity to learn more about c++, as such i have covered most of the public API Surface simdjson got.

Have fun :)
2026-02-28 14:58:01 -05:00
Sudhanshu Shukla ec675d9c61 Add RISC-V RVV support for find_next_json_quotable_character (#2617) 2026-02-28 13:11:47 -05:00
Daniel Lemire 7dcc196f66 iterating over a simdjson_result<padded_string> (#2614) 2026-02-27 11:01:39 -05:00
Daniel Lemire 7ec4572d33 trimming whitespace 2026-02-26 16:09:34 -05:00
Daniel Lemire 5891d4b64b adding AI policy 2026-02-26 15:09:40 -05:00
Daniel Lemire 3cd9875f1f simplifying the find_next_json_quotable_character fnc for neon processors (#2613) 2026-02-25 01:06:08 -05:00
Daniel Lemire 6cf1bdba43 saving. 2026-02-24 18:01:16 -05:00
Sudhanshu Shukla 817f10dd7d Add LoongArch LSX support for find_next_json_quotable_character (#2608) 2026-02-23 15:00:26 -05:00
Daniel Lemire e829566589 4.3.1 2026-02-20 16:17:16 -05:00
Daniel Lemire dbe0c1543e fix bad hint (#2610) 2026-02-20 16:16:37 -05:00
Daniel Lemire 06c774c655 4.3.0 2026-02-18 23:50:58 -05:00
Daniel Lemire 95d8c81810 When using C++11, we could violate the one definition rule (#2606)
* When using C++11, we could violate the one definition rule

* fix

* simplifying

* disabling new 'test' when using shared libs
2026-02-18 20:08:53 -05:00
Francisco Geiman Thiesen af2a43610e perf: SIMD string escaping and batch integer formatting optimizations (#2605)
* perf(serialization): SIMD-accelerated string escape position finding

Profiling revealed that string serialization was performing redundant
scanning: first calling fast_needs_escaping() (SIMD scan to check IF
escape needed), then find_next_json_quotable_character() (scalar
byte-by-byte scan to find WHERE).

Profile data from Twitter benchmark showed:
- 54.76% time in atom<std::string> (string serialization)
- 24.85% time in find_next_json_quotable_character (scalar position finding)

This optimization unifies both operations into a single SIMD pass that
directly locates the first quotable character position:

- NEON (ARM64): Uses vceqq_u8/vcltq_u8 for character detection, then
  extracts position via __builtin_ctzll on 64-bit vector lanes
- SSE2 (x86-64): Uses _mm_cmpeq_epi8/_mm_subs_epu8 for detection, then
  _mm_movemask_epi8 + __builtin_ctz for position extraction

The write_string_escaped function now uses the position finder directly,
eliminating the separate fast_needs_escaping check.

Benchmark results (ARM64, Apple Silicon via Docker with p2996 clang):
- Twitter (string-heavy): 4330 -> 5723 MB/s (+32%)
- CITM (numeric-heavy): ~neutral (expected, few strings)

* perf(serialization): batch integer formatting with 4-digit processing

Profiling with perf annotate revealed that the integer-to-string
conversion loop was a significant hotspot in numeric-heavy workloads.
The original implementation processed 2 digits per iteration:

    while (pv >= 100) {
        memcpy(write_pointer - 1, &decimal_table[(pv % 100) * 2], 2);
        write_pointer -= 2;
        pv /= 100;
    }

Profile data from CITM benchmark showed:
- 31.20% time in atom<unsigned long> (integer formatting)
- 39.07% of integer formatting time in the 2-byte store instruction
  (sturh on ARM64)
- CITM integers average 8.8 digits, meaning 4+ store operations per number

This optimization processes 4 digits per iteration, reducing both store
operations and division count by approximately half for large numbers:

    while (pv >= 10000) {
        q = pv / 10000;
        r = pv % 10000;
        r_hi = r / 100;  // High 2 digits
        r_lo = r % 100;  // Low 2 digits
        memcpy(write_pointer - 1, &decimal_table[r_lo * 2], 2);
        memcpy(write_pointer - 3, &decimal_table[r_hi * 2], 2);
        write_pointer -= 4;
        pv = q;
    }

The division by 10000 compiles to an efficient multiply-high instruction
(umulh on ARM64). Applied to both unsigned and signed integer paths.

Benchmark results (ARM64, Apple Silicon via Docker with p2996 clang):
- Twitter: ~neutral (few integers)
- CITM (numeric-heavy): 2912 -> 3086 MB/s (+6%)

* fix(build): add MSVC compatibility for bit manipulation intrinsics

MSVC does not have __builtin_ctz/__builtin_ctzll. Use _BitScanForward
and _BitScanForward64 from <intrin.h> on MSVC instead.

This fixes the build on all Windows configurations (x64, ARM64, Win32).

* refactor: clean up comments to be implementation-focused

Remove references to specific benchmarks and previous implementations
from code comments. Comments now describe what the code does rather
than historical context.
2026-02-16 11:50:27 -05:00
Daniel Lemire e362b18499 saving. 2026-02-09 00:31:31 -05:00
Daniel Lemire 6e60ed8338 updating rust 2026-02-08 23:42:43 -05:00
Daniel Lemire e2ca00c841 reenabling bench_nlohmann_parsing in twitter bench 2026-02-08 22:36:34 -05:00
Daniel Lemire b7124f7c64 inlining get_single_implementation (#2600) 2026-02-06 20:34:00 -05:00
Daniel Lemire e6c6db6493 Update field access section in basics.md
Clarified field access behavior and error handling in documentation.
2026-02-03 14:04:26 -05:00
Daniel Lemire 6fffc99dac Add Ladybird Browser to the list of projects 2026-02-02 21:08:31 -05:00
Daniel Lemire 8c5cc8c443 updating the reflection benchmarks (#2598)
* updating the reflection benchmarks

* removing exception during parsing.

* saving.
2026-02-02 11:28:08 -05:00
Daniel Lemire d8c49a8f25 fix to rust instructions 2026-01-27 20:08:06 -05:00
Daniel Lemire cda98064b8 faster amalgamation script (#2597) 2026-01-27 11:35:58 -05:00
Daniel Lemire e532d61e16 when calling get_string with a mutable string parameter, we want to only use the string buffer (#2595)
as scratch space
2026-01-22 10:23:39 -05:00
Olaf Bernstein db93de2a21 add rvv-vls backend (#2593) 2026-01-21 10:13:50 -05:00
Francisco Geiman Thiesen fc57c09cf0 Add FracturedJson formatting support for DOM serialization (#2580)
* Add FracturedJson formatting support for DOM serialization

Implements FracturedJson formatting as requested in issue #2576.
FracturedJson produces human-readable yet compact JSON output by
intelligently choosing between different layout strategies based on
content complexity, length, and structure similarity.

Key features:
- Four layout modes: inline, compact multiline, table, and expanded
- Structure analysis pass to compute metrics before formatting
- Table formatting for arrays of similar objects with column alignment
- Configurable options for line length, indentation, padding, etc.

New files:
- fractured_json.h: Public API with fractured_json_options struct
- fractured_json-inl.h: Implementation (~1000 lines)
- json_structure_analyzer.h: Structure analysis for layout decisions
- fractured_formatter.h: Formatter class using CRTP pattern

Usage:
  dom::parser parser;
  element doc = parser.parse(json_string);
  std::cout << fractured_json(doc) << std::endl;

  // Or with custom options:
  fractured_json_options opts;
  opts.indent_spaces = 2;
  std::cout << fractured_json(doc, opts) << std::endl;

  // Or format any JSON string (useful with reflection API):
  auto formatted = fractured_json_string(minified_json);

Resolves #2576

* Add comprehensive tests for FracturedJson formatter

Adds 27 test cases covering all aspects of the FracturedJson formatter:

Core functionality tests (13):
- Roundtrip parsing verification
- Inline formatting for simple arrays and objects
- Expanded formatting for complex nested structures
- Compact multiline arrays with configurable items per line
- Table formatting for uniform arrays of objects
- Empty container handling
- All scalar types (string, int, uint, double, bool, null)
- String escaping (quotes, backslashes, control characters)
- Custom indentation options
- Deep nesting (10+ levels)
- Mixed type arrays

Edge case tests (11):
- Unicode strings (Chinese, emoji, Arabic, Russian, accented chars)
- Boundary numbers (INT64_MIN/MAX, UINT64_MAX, DBL_MIN/MAX)
- Nested arrays (arrays of arrays)
- Empty string values
- Keys with special characters (spaces, quotes, colons, etc.)
- Non-uniform arrays (should not trigger table mode)
- Very long strings (500+ chars)
- Large arrays (100 elements)
- Reflection API workflow simulation
- Control characters (tab, newline, CR, null)
- Single element containers

Option tests (3):
- Disable compact multiline mode
- Disable table format mode
- Disable all padding options

* Add FracturedJson integration with builder/reflection API

Extends FracturedJson to work seamlessly with the builder API, enabling
formatted output directly from C++ structs using static reflection.

New functions:
- to_fractured_json_string(obj, opts) - serialize struct to formatted JSON
- to_fractured_json(obj, output, opts) - same with output parameter
- extract_fractured_json<fields...>(obj, opts) - format only specific fields

These functions combine the builder's reflection-based serialization with
FracturedJson formatting in a single convenient call:

  struct User { int id; std::string name; bool active; };
  User user{1, "Alice", true};

  // Minified output (existing):
  auto minified = to_json_string(user);
  // {"id":1,"name":"Alice","active":true}

  // Formatted output (new):
  auto formatted = to_fractured_json_string(user);
  // { "id": 1, "name": "Alice", "active": true }

  // Partial extraction with formatting:
  auto partial = extract_fractured_json<"id", "name">(user);
  // { "id": 1, "name": "Alice" }

New files:
- generic/builder/fractured_json_builder.h - builder integration
- tests/builder/static_reflection_fractured_json_tests.cpp - 7 tests

* Fix INT64_MIN overflow and implement table_similarity_threshold

- Fix undefined behavior when negating INT64_MIN in estimate_number_length()
  and measure_value_length() by returning 20 (the exact length of the
  string representation) directly
- Actually use table_similarity_threshold in check_array_uniformity() by
  calling compute_object_similarity() to compare objects against the first
  object in the array

* Fix -Werror=effc++ member initialization warnings

Initialize all member variables in member initialization lists to
satisfy GCC's -Werror=effc++ flag:
- element_metrics::common_keys - add {} default initializer
- structure_analyzer - add default constructor with member init list
- fractured_formatter - add column_widths_{} to constructor
- fractured_string_builder - add analyzer_{} to constructor

* Add Rule of Five to structure_analyzer class

The class has a pointer member (current_opts_) which triggers
-Werror=effc++ requiring explicit copy/move operations. Delete
copy operations (class shouldn't be copied due to cache) and
default move operations.

* Fix Windows build: wrap std::max to avoid macro conflict

Windows.h defines max/min macros that interfere with std::max/std::min.
Wrapping in parentheses as (std::max)(...) prevents macro expansion.

* Fix GCC 15 false positive -Wfree-nonheap-object warning

GCC 15 on MINGW64 gives a false positive warning in parser_moving_parser()
when the std::vector<std::string> goes out of scope. Suppress this
specific warning with a pragma for GCC builds.

* Fix metrics cache key bug by passing metrics through recursion

The cache was using element addresses as keys, but dom::element objects
are lightweight wrappers that get copied during iteration, causing
different addresses between analysis and formatting phases. This resulted
in cache misses and fallback to empty metrics.

Solution: Store child metrics in the element_metrics struct and pass
them through recursive calls, eliminating the need for address-based
caching entirely.

Changes:
- Add children vector to element_metrics for hierarchical metrics
- Remove metrics_cache_ and related get_metrics/has_metrics methods
- Update all format functions to accept and pass child metrics
- Add public analyze_array/analyze_object overloads for standalone use

* Add ignore patterns for Node.js, Rust, and generated files

Add entries for node_modules, package-lock.json, Rust target
directories, local ablation artifacts, and generated documentation
files.

* Refactor: extract analyze_scalar helper to reduce code duplication

Extract common scalar type handling (STRING, INT64, UINT64, DOUBLE,
BOOL, NULL_VALUE) into a dedicated analyze_scalar method. Each scalar
type shares the same initialization pattern for complexity, child_count,
can_inline, and recommended_layout.

Also simplify boolean formatting in format_scalar to use ternary operator.

* Fix formatting and duplicate error message in amalgamate.py

Reformat cramped is_amalgamator condition to multi-line for readability.
Fix duplicate error message text in _included_filename_root and use
correct variable name (relative_root instead of root).

* Refactor: add count_newlines helper in fractured_json tests

Extract repeated newline counting loop into a reusable static helper
function, used by inline_array_test, inline_object_test, and
expanded_test.

* Revert "Add ignore patterns for Node.js, Rust, and generated files"

This reverts commit 4760ea7cd0.

* various minor changes

---------

Co-authored-by: Daniel Lemire <daniel@lemire.me>
2026-01-20 10:32:24 -05:00
Daniel Lemire 2058b47dfe adding padded string builder (#2592)
* adding padded string builder

* minor rename

* deleting copy constructor

* typo

* [no-ci] tuning documentation.
2026-01-18 20:18:19 -05:00
Daniel Lemire b0486c7fa2 saving. 2026-01-18 11:57:55 -05:00
Daniel Lemire feb1e7feb6 work on the ondemand iterators (#2590)
* work on the ondemand iterators

* guarding two SIMDJSON_ASSUME

* simplify following @jkeiser's comment

* adding safety rails to the iterators

* silencing a warning.

* updating the amalgamation files
2026-01-17 21:15:18 -05:00
Eve Silfanus 2c7fbc1538 Build Performance Optimization (#2588) 2026-01-16 11:42:39 -05:00
Daniel Lemire 8b69401d8a moving the builder files in their own directory (#2578) 2026-01-07 18:08:11 -05:00
Daniel Lemire f504e57e7a Add runtime dispatching for loongarch (#2575)
* loongarch runtime dispatching

* remove CMake config for Loongarch.

* make lasx available

* flipping

* moving...

* fixing minor logic error

* minor fixes

* flipping

* adding hackish header

* better comment and reordering

* adding dispatch
2026-01-02 14:28:23 -05:00
Arthur Chan 135c173053 oss-fuzz: Add unit testing build to oss-fuzz build script (#2574) 2026-01-02 14:28:01 -05:00
Daniel Lemire b4ed3a99a9 fixing typos (#2573) 2025-12-30 17:38:03 -05:00
Daniel Lemire d8e1b36c88 just new small tests. (#2571) 2025-12-23 11:14:09 -05:00
Daniel Lemire c249a1b456 Merge branch 'master' of github.com:simdjson/simdjson 2025-12-22 16:08:30 -05:00
Daniel Lemire 9c4b793c90 adding ref 2025-12-22 16:08:19 -05:00
Daniel Lemire dd92a8414c Add optimization option to pull request template 2025-12-19 23:30:13 -05:00
Dirk Stolle 835bdba123 adjust logo image file names (*_simdjason_* -> *_simdjson_*) (#2569) 2025-12-19 23:21:59 -05:00
Dirk Stolle ad3cd71ca2 fix a few typos (#2568) 2025-12-19 21:56:32 -05:00
Daniel Lemire 860f7e0458 Update logo in README.md 2025-12-17 20:36:08 -05:00
Daniel Lemire 980f2ad3af 4.2.4 2025-12-17 20:33:11 -05:00
Daniel Lemire 7ad9fe63a6 fixing issue 2549 (#2567)
* fixing issue 2549

* saving.
2025-12-17 20:32:36 -05:00
Daniel Lemire 7987418b1f adding the official simdjson logo files 2025-12-13 12:14:40 -05:00
Daniel Lemire 5e871f6724 improving slightly the documentation. 2025-12-12 19:04:34 -05:00
194 changed files with 65504 additions and 16843 deletions
+4
View File
@@ -29,6 +29,9 @@ We accept the identification of an issue by a sanitizer or some checker tool (e.
We recommend that you run your tests using different optimization levels. In particular, we recommend your run tests with the simdjson library and you code compiled in debug mode. The simdjson then sets the SIMDJSON_DEVELOPMENT_CHECKS macro to 1, and this triggers additional checks on your code and on the internals of the library. If possible, we recommend that you run tests with sanitizers (e.g., see [No more leaks with sanitize flags in gcc and clang](https://lemire.me/blog/2016/04/20/no-more-leaks-with-sanitize-flags-in-gcc-and-clang/)). You can compile the library with sanitizers for debugging purposes (e.g., set SIMDJSON_SANITIZE to ON using CMake), but you should also turn on sanitizers on your own code. You may also use tools like valgrind or the commercial equivalent. We recommend that you run your tests using different optimization levels. In particular, we recommend your run tests with the simdjson library and you code compiled in debug mode. The simdjson then sets the SIMDJSON_DEVELOPMENT_CHECKS macro to 1, and this triggers additional checks on your code and on the internals of the library. If possible, we recommend that you run tests with sanitizers (e.g., see [No more leaks with sanitize flags in gcc and clang](https://lemire.me/blog/2016/04/20/no-more-leaks-with-sanitize-flags-in-gcc-and-clang/)). You can compile the library with sanitizers for debugging purposes (e.g., set SIMDJSON_SANITIZE to ON using CMake), but you should also turn on sanitizers on your own code. You may also use tools like valgrind or the commercial equivalent.
Mixing debug and release simdjson code is unsafe: you either build all your code using simdjson in
release mode or all of it in debug mode.
Before reporting a bug, please ensure that you have read our documentation. Before reporting a bug, please ensure that you have read our documentation.
**To Reproduce** **To Reproduce**
@@ -55,6 +58,7 @@ We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux syste
* We do not support unreleased or experimental compilers. If you encounter an issue with a * We do not support unreleased or experimental compilers. If you encounter an issue with a
pre-release version of a compiler, do not report it as a bug to simdjson. However, we always pre-release version of a compiler, do not report it as a bug to simdjson. However, we always
invite contributions either in the form an analysis or of a code contribution. invite contributions either in the form an analysis or of a code contribution.
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support.
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412). Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
+2 -1
View File
@@ -6,6 +6,7 @@ Description
Type of change Type of change
- [ ] Bug fix - [ ] Bug fix
- [ ] Optimization
- [ ] New feature - [ ] New feature
- [ ] Refactor / cleanup - [ ] Refactor / cleanup
- [ ] Documentation / tests - [ ] Documentation / tests
@@ -19,7 +20,7 @@ How to verify / test
Please read before contributing: Please read before contributing:
- CONTRIBUTING: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md - CONTRIBUTING: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
- HACKING: https://github.com/simdjson/simdjson/blob/master/HACKING.md - HACKING: https://github.com/simdjson/simdjson/blob/master/HACKING.md
- AI Usage Policy: https://github.com/simdjson/simdjson/blob/master/AI_USAGE_POLICY.md
If you can, we recommend running our tests with the sanitizers turned on. If you can, we recommend running our tests with the sanitizers turned on.
+5 -5
View File
@@ -19,11 +19,11 @@ jobs:
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-18 sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-18
- name: Build - name: Build
run: | run: |
CXX=clang++-18 CXXFLAGS="--target=riscv64-linux-gnu -march=rv64gcv_zvbb" \ CC=clang-18 CXX=clang++-18 CFLAGS="--target=riscv64-linux-gnu -march=rv64gcv_zvbb" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build
cmake --build build/ -j$(nproc) cmake --build build/ -j$(nproc) --config Release
- name: Test VLEN=1024 - name: Test VLEN=1024
run: | run: |
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
export QEMU_CPU="rv64,v=on,zvbb=on,vlen=1024,rvv_ta_all_1s=on,rvv_ma_all_1s=on" QEMU_CPU="rv64,v=on,zvbb=on,vlen=1024,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc) ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
+3 -8
View File
@@ -19,11 +19,6 @@ jobs:
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-17 sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-17
- name: Build - name: Build
run: | run: |
CXX=clang++-17 CXXFLAGS="--target=riscv64-linux-gnu -march=rv64gcv" \ CC=clang-17 CXX=clang++-17 CFLAGS="--target=riscv64-linux-gnu -march=rv64gcv" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build
cmake --build build/ -j$(nproc) cmake --build build/ -j$(nproc) --config Release
- name: Test VLEN=128
run: |
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu"
export QEMU_CPU="rv64,v=on,vlen=128,rvv_ta_all_1s=on,rvv_ma_all_1s=on"
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
+39
View File
@@ -0,0 +1,39 @@
name: Ubuntu rvv VLEN=128 (clang 20)
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update -q -y
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-20
- name: Build
run: |
CC=clang-20 CXX=clang++-20 CFLAGS="--target=riscv64-linux-gnu -march=rv64gcv" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build
cmake --build build/ -j$(nproc) --config Release
- name: Test VLEN=128
run: |
QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
QEMU_CPU="rv64,v=on,vlen=128,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
- name: Build VLS
run: |
CC=clang-20 CXX=clang++-20 CFLAGS="--target=riscv64-linux-gnu -march=rv64gcv_zvl128b_zba_zbb_zbc -mrvv-vector-bits=zvl" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build-vls
cmake --build build-vls/ -j$(nproc) --config Release
- name: Test VLEN=128 VLS
run: |
QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
QEMU_CPU="rv64,v=on,zba=on,zbb=on,zbc=on,vlen=128,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build-vls -j $(nproc)
+15 -5
View File
@@ -19,11 +19,21 @@ jobs:
sudo apt-get install -y cmake make g++-14-riscv64-linux-gnu qemu-user-static sudo apt-get install -y cmake make g++-14-riscv64-linux-gnu qemu-user-static
- name: Build - name: Build
run: | run: |
CXX=riscv64-linux-gnu-g++-14 CXXFLAGS=-march=rv64gcv \ CC=riscv64-linux-gnu-gcc-14 CXX=riscv64-linux-gnu-g++-14 CFLAGS=-march=rv64gcv CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build
cmake --build build/ -j$(nproc) cmake --build build/ -j$(nproc) --config Release
- name: Test VLEN=256 - name: Test VLEN=256
run: | run: |
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
export QEMU_CPU="rv64,v=on,zvbb=on,vlen=256,rvv_ta_all_1s=on,rvv_ma_all_1s=on" QEMU_CPU="rv64,v=on,zvbb=on,vlen=256,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc) ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
- name: Build VLS
run: |
CC=riscv64-linux-gnu-gcc-14 CXX=riscv64-linux-gnu-g++-14 CFLAGS="-march=rv64gcv_zvl256b -mrvv-vector-bits=zvl" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build-vls
cmake --build build-vls/ -j$(nproc) --config Release
- name: Test VLEN=256 VLS
run: |
QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
QEMU_CPU="rv64,v=on,zvbb=on,vlen=256,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build-vls -j $(nproc)
+39
View File
@@ -0,0 +1,39 @@
name: Ubuntu rvv VLEN=512 (clang 19)
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update -q -y
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-19
- name: Build
run: |
CC=clang-19 CXX=clang++-19 CFLAGS="--target=riscv64-linux-gnu -march=rv64gcv" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build
cmake --build build/ -j$(nproc) --config Release
- name: Test VLEN=512
run: |
QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
QEMU_CPU="rv64,v=on,vlen=512,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
- name: Build VLS
run: |
CC=clang-19 CXX=clang++-19 CFLAGS="--target=riscv64-linux-gnu -march=rv64gcv_zvl512b_zba_zbb_zbc -mrvv-vector-bits=zvl" CXXFLAGS="${CFLAGS}" \
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DSIMDJSON_DEVELOPER_MODE=ON -B build-vls
cmake --build build-vls/ -j$(nproc) --config Release
- name: Test VLEN=512 VLS
run: |
QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" \
QEMU_CPU="rv64,v=on,zba=on,zbb=on,zbc=on,vlen=512,rvv_ta_all_1s=on,rvv_ma_all_1s=on" \
ctest --timeout 1800 --output-on-failure --test-dir build-vls -j $(nproc)
+1 -1
View File
@@ -1,4 +1,4 @@
name: Performance check on Ubuntu 20.04 CI (GCC 9) name: Performance check on Ubuntu 24.04 CI (GCC 13)
on: on:
push: push:
+2 -2
View File
@@ -1,4 +1,4 @@
name: Ubuntu 20.04 CI (GCC 9) without exceptions name: Ubuntu 24.04 CI (GCC 13) without exceptions
on: [push, pull_request] on: [push, pull_request]
@@ -24,7 +24,7 @@ jobs:
cd .. && cd .. &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_EXCEPTIONS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. && cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_EXCEPTIONS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . && cmake --build . &&
ctest --output-on-failure -LE explicitonly -j && ctest --output-on-failure -LE explicitonly -j &&
make install && make install &&
+1 -1
View File
@@ -1,4 +1,4 @@
name: Ubuntu 20.04 CI (GCC 9) Without Threads name: Ubuntu 24.04 CI (GCC 13) Without Threads
on: [push, pull_request] on: [push, pull_request]
+2 -2
View File
@@ -1,9 +1,9 @@
name: Ubuntu 20.04 CI (GCC 9) With Memory Sanitizer name: Ubuntu 24.04 CI (GCC 13) With Memory Sanitizer
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ubuntu-build-address-sanitizier: ubuntu-build-address-sanitizer:
if: >- if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') && ! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
+56
View File
@@ -0,0 +1,56 @@
# AI Usage Policy
Contributors can use whatever tools they would like to
craft their contributions, but there must be a **human in the loop**.
**Contributors must read and review all LLM-generated code or text before they
ask other project members to review it.** The contributor is always the author
and is fully accountable for their contributions. Contributors should be
sufficiently confident that the contribution is high enough quality that asking
for a review is a good use of scarce maintainer time, and they should be **able
to answer questions about their work** during review.
We expect that new contributors will be less confident in their contributions,
and our guidance to them is to **start with small contributions** that they can
fully understand to build confidence. We aspire to be a welcoming community
that helps new contributors grow their expertise, but learning involves taking
small steps, getting feedback, and iterating. Passing maintainer feedback to an
LLM doesn't help anyone grow, and does not sustain our community.
This policy includes, but is not limited to, the following kinds of
contributions:
- Code, usually in the form of a pull request
- Issues or security vulnerabilities
- Comments and feedback on pull requests
## Extractive Contributions
The reason for our "human-in-the-loop" contribution policy is that processing
patches, PRs, RFCs, and comments is not free -- it takes a lot of
maintainer time and energy to review those contributions! Sending the
unreviewed output of an LLM to open source project maintainers *extracts* work
from them in the form of design and code review, so we call this kind of
contribution an "extractive contribution".
## Transparency
For contributions involving significant AI assistance, we encourage you to disclose
its use and explain your process. If a submission appears to rely heavily on AI
without disclosure, we may doubt that the **human-in-the-loop** requirement has
been met. Please show awareness of your use of AI.
## Copyright
Artificial intelligence systems raise many questions around copyright that have
yet to be answered. Our policy on AI tools is similar to our copyright policy:
Contributors are responsible for ensuring that they have the right to
contribute code under the terms of our license, typically meaning that either
they, their employer, or their collaborators hold the copyright. Using AI tools
to regenerate copyrighted material does not remove the copyright, and
contributors are responsible for ensuring that such material does not appear in
their contributions. Contributions found to violate this policy will be removed
just like any other offending contribution.
## Reference
- [LLVM AI Tool Use Policy](https://discourse.llvm.org/t/rfc-llvm-ai-tool-policy-human-in-the-loop/89159)
+144 -100
View File
@@ -1,9 +1,18 @@
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
# Build performance optimizations
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export compile commands for faster IDE integration")
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Enable parallel compilation on MSVC
if(MSVC)
add_compile_options(/MP)
endif()
project( project(
simdjson simdjson
# The version number is modified by tools/release.py # The version number is modified by tools/release.py
VERSION 4.2.3 VERSION 4.6.4
DESCRIPTION "Parsing gigabytes of JSON per second" DESCRIPTION "Parsing gigabytes of JSON per second"
HOMEPAGE_URL "https://simdjson.org/" HOMEPAGE_URL "https://simdjson.org/"
LANGUAGES CXX C LANGUAGES CXX C
@@ -20,8 +29,8 @@ string(
# ---- Options, variables ---- # ---- Options, variables ----
# These version numbers are modified by tools/release.py # These version numbers are modified by tools/release.py
set(SIMDJSON_LIB_VERSION "29.0.0" CACHE STRING "simdjson library version") set(SIMDJSON_LIB_VERSION "33.0.0" CACHE STRING "simdjson library version")
set(SIMDJSON_LIB_SOVERSION "29" CACHE STRING "simdjson library soversion") set(SIMDJSON_LIB_SOVERSION "33" CACHE STRING "simdjson library soversion")
option(SIMDJSON_BUILD_STATIC_LIB "Build simdjson_static library along with simdjson (only makes sense if BUILD_SHARED_LIBS=ON)" OFF) option(SIMDJSON_BUILD_STATIC_LIB "Build simdjson_static library along with simdjson (only makes sense if BUILD_SHARED_LIBS=ON)" OFF)
if(SIMDJSON_BUILD_STATIC_LIB AND NOT BUILD_SHARED_LIBS) if(SIMDJSON_BUILD_STATIC_LIB AND NOT BUILD_SHARED_LIBS)
@@ -67,9 +76,12 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
endif() endif()
if(is_top_project) if(is_top_project)
option(SIMDJSON_INSTALL "Enable target install" ON)
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF) option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
option(BUILD_SHARED_LIBS "Build simdjson as a shared library" OFF) option(BUILD_SHARED_LIBS "Build simdjson as a shared library" OFF)
option(SIMDJSON_SINGLEHEADER "Disable singleheader generation" ON) option(SIMDJSON_SINGLEHEADER "Disable singleheader generation" ON)
else()
option(SIMDJSON_INSTALL "Enable target install" ${BUILD_SHARED_LIBS})
endif() endif()
include(cmake/handle-deprecations.cmake) include(cmake/handle-deprecations.cmake)
@@ -83,10 +95,45 @@ add_library(simdjson ${SIMDJSON_SOURCES})
add_library(simdjson::simdjson ALIAS simdjson) add_library(simdjson::simdjson ALIAS simdjson)
set(SIMDJSON_LIBRARIES simdjson) set(SIMDJSON_LIBRARIES simdjson)
# Check for <bit> header compatibility
include(CheckIncludeFileCXX)
check_include_file_cxx(bit SIMDJSON_HAS_BIT_HEADER)
# Enable precompiled headers for faster builds
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
set(SIMDJSON_PRECOMPILE_HEADERS
<algorithm>
<array>
<atomic>
<cassert>
<cctype>
<cerrno>
<cstddef>
<cstdint>
<cstdlib>
<cstring>
<memory>
<string>
<utility>
<vector>
)
if(SIMDJSON_HAS_BIT_HEADER)
list(APPEND SIMDJSON_PRECOMPILE_HEADERS <bit>)
endif()
target_precompile_headers(simdjson PRIVATE ${SIMDJSON_PRECOMPILE_HEADERS})
endif()
if(SIMDJSON_BUILD_STATIC_LIB) if(SIMDJSON_BUILD_STATIC_LIB)
add_library(simdjson_static STATIC ${SIMDJSON_SOURCES}) add_library(simdjson_static STATIC ${SIMDJSON_SOURCES})
add_library(simdjson::simdjson_static ALIAS simdjson_static) add_library(simdjson::simdjson_static ALIAS simdjson_static)
list(APPEND SIMDJSON_LIBRARIES simdjson_static) list(APPEND SIMDJSON_LIBRARIES simdjson_static)
# Reuse precompiled headers for static library
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
target_precompile_headers(simdjson_static REUSE_FROM simdjson)
endif()
endif() endif()
set_target_properties( set_target_properties(
@@ -112,6 +159,14 @@ simdjson_add_props(
PRIVATE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>" PRIVATE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
) )
# Optimize linker settings for faster builds
if(MSVC)
target_link_options(simdjson PRIVATE /INCREMENTAL)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_options(simdjson PRIVATE /DEBUG:FASTLINK)
endif()
endif()
if(SIMDJSON_STATIC_REFLECTION) if(SIMDJSON_STATIC_REFLECTION)
# We would like to require C++26, but no compiler supports that! # We would like to require C++26, but no compiler supports that!
# This is a hack: # This is a hack:
@@ -140,24 +195,6 @@ if(SIMDJSON_MINUS_ZERO_AS_FLOAT)
simdjson_add_props(target_compile_definitions PRIVATE SIMDJSON_MINUS_ZERO_AS_FLOAT=1) simdjson_add_props(target_compile_definitions PRIVATE SIMDJSON_MINUS_ZERO_AS_FLOAT=1)
endif(SIMDJSON_MINUS_ZERO_AS_FLOAT) endif(SIMDJSON_MINUS_ZERO_AS_FLOAT)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(loongarch64)$")
option(SIMDJSON_PREFER_LSX "Prefer LoongArch SX" ON)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-mlasx COMPILER_SUPPORTS_LASX)
check_cxx_compiler_flag(-mlsx COMPILER_SUPPORTS_LSX)
if(COMPILER_SUPPORTS_LASX AND NOT SIMDJSON_PREFER_LSX)
simdjson_add_props(
target_compile_options PRIVATE
-mlasx
)
elseif(COMPILER_SUPPORTS_LSX)
simdjson_add_props(
target_compile_options PRIVATE
-mlsx
)
endif()
endif()
# GCC and Clang have horrendous Debug builds when using SIMD. # GCC and Clang have horrendous Debug builds when using SIMD.
# A common fix is to use '-Og' instead. # A common fix is to use '-Og' instead.
# bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412 # bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412
@@ -171,7 +208,12 @@ if(
target_compile_options PRIVATE target_compile_options PRIVATE
$<$<CONFIG:DEBUG>:-Og> $<$<CONFIG:DEBUG>:-Og>
) )
endif() # We still want to enable development checks in Debug mode
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_DEVELOPMENT_CHECKS
)
endif()
if(SIMDJSON_ENABLE_THREADS) if(SIMDJSON_ENABLE_THREADS)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
@@ -186,87 +228,89 @@ endif()
# ---- Install rules ---- # ---- Install rules ----
include(CMakePackageConfigHelpers) if(SIMDJSON_INSTALL)
include(GNUInstallDirs) include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
if(SIMDJSON_SINGLEHEADER) if(SIMDJSON_SINGLEHEADER)
install( install(
FILES singleheader/simdjson.h FILES singleheader/simdjson.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT simdjson_Development COMPONENT simdjson_Development
) )
endif()
install(
TARGETS simdjson
EXPORT simdjsonTargets
RUNTIME COMPONENT simdjson_Runtime
LIBRARY COMPONENT simdjson_Runtime
NAMELINK_COMPONENT simdjson_Development
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
configure_file(cmake/simdjson-config.cmake.in simdjson-config.cmake @ONLY)
write_basic_package_version_file(
simdjson-config-version.cmake
COMPATIBILITY SameMinorVersion
)
set(
SIMDJSON_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/simdjson"
CACHE STRING "CMake package config location relative to the install prefix"
)
mark_as_advanced(SIMDJSON_INSTALL_CMAKEDIR)
install(
FILES
"${PROJECT_BINARY_DIR}/simdjson-config.cmake"
"${PROJECT_BINARY_DIR}/simdjson-config-version.cmake"
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
install(
EXPORT simdjsonTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
if(SIMDJSON_BUILD_STATIC_LIB)
install(
TARGETS simdjson_static
EXPORT simdjson_staticTargets
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(
EXPORT simdjson_staticTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
endif()
# pkg-config
include(cmake/JoinPaths.cmake)
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
if(SIMDJSON_ENABLE_THREADS)
set(PKGCONFIG_CFLAGS "-DSIMDJSON_THREADS_ENABLED=1")
if(CMAKE_THREAD_LIBS_INIT)
set(PKGCONFIG_LIBS_PRIVATE "Libs.private: ${CMAKE_THREAD_LIBS_INIT}")
endif()
endif()
configure_file("simdjson.pc.in" "simdjson.pc" @ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
)
endif() endif()
install(
TARGETS simdjson
EXPORT simdjsonTargets
RUNTIME COMPONENT simdjson_Runtime
LIBRARY COMPONENT simdjson_Runtime
NAMELINK_COMPONENT simdjson_Development
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
configure_file(cmake/simdjson-config.cmake.in simdjson-config.cmake @ONLY)
write_basic_package_version_file(
simdjson-config-version.cmake
COMPATIBILITY SameMinorVersion
)
set(
SIMDJSON_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/simdjson"
CACHE STRING "CMake package config location relative to the install prefix"
)
mark_as_advanced(SIMDJSON_INSTALL_CMAKEDIR)
install(
FILES
"${PROJECT_BINARY_DIR}/simdjson-config.cmake"
"${PROJECT_BINARY_DIR}/simdjson-config-version.cmake"
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
install(
EXPORT simdjsonTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
if(SIMDJSON_BUILD_STATIC_LIB)
install(
TARGETS simdjson_static
EXPORT simdjson_staticTargets
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(
EXPORT simdjson_staticTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
endif()
# pkg-config
include(cmake/JoinPaths.cmake)
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
if(SIMDJSON_ENABLE_THREADS)
set(PKGCONFIG_CFLAGS "-DSIMDJSON_THREADS_ENABLED=1")
if(CMAKE_THREAD_LIBS_INIT)
set(PKGCONFIG_LIBS_PRIVATE "Libs.private: ${CMAKE_THREAD_LIBS_INIT}")
endif()
endif()
configure_file("simdjson.pc.in" "simdjson.pc" @ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
)
# #
# CPack # CPack
# #
+7
View File
@@ -101,3 +101,10 @@ Getting Started Hacking
An overview of simdjson's directory structure, with pointers to architecture and design An overview of simdjson's directory structure, with pointers to architecture and design
considerations and other helpful notes, can be found at [HACKING.md](HACKING.md). considerations and other helpful notes, can be found at [HACKING.md](HACKING.md).
AI Usage Policy
---------------
Please also review our [AI Usage Policy](AI_USAGE_POLICY.md).
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = "4.2.3" PROJECT_NUMBER = "4.6.4"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
+25
View File
@@ -110,6 +110,24 @@ workflows used by simdjson.
Directory Structure and Source Directory Structure and Source
------------------------------ ------------------------------
Before diving into the directory structure, here are key concepts used in the codebase:
- **Amalgamated File**: A file that is conditionally included in the amalgamation process. These are wrapped in `#ifndef SIMDJSON_CONDITIONAL_INCLUDE` blocks and are included based on the target implementation (e.g., ARM64, x86). They include implementation-specific files (e.g., `arm64.h`) and generic files (e.g., under `generic/`). Amalgamated files have associated dependency files (`dependencies.h`) to track includes.
- **Amalgamator File**: A file that orchestrates the inclusion of amalgamated files. Examples: `arm64.h`, `arm64/implementation.h`, `generic/amalgamated.h`. These are not themselves amalgamated but control conditional inclusions.
- **Free Dependency File**: A top-level header that is always included unconditionally. These do not have dependency files and represent the public API (e.g., main headers).
- **Implementation-Specific File**: A file tied to a specific CPU architecture or instruction set (e.g., `arm64/`, `haswell/`). These must be amalgamated.
- **Generic File**: A shared file (under `generic/` or `simdjson/generic/`) that contains common code included once per implementation.
- **Builtin File**: Special files under `simdjson/builtin/` that handle the builtin implementation, a fallback/default implementation used when no optimized implementation is available.
- **Conditional Include Block**: A section wrapped in `#ifndef SIMDJSON_CONDITIONAL_INCLUDE` for editor-only or implementation-specific content.
The script `singleheader/amalgation_helper.py` will generate an HTML report which you can use to visualize the status of each file.
simdjson's source structure, from the top level, looks like this: simdjson's source structure, from the top level, looks like this:
* **CMakeLists.txt:** The main build system. * **CMakeLists.txt:** The main build system.
@@ -133,6 +151,12 @@ simdjson's source structure, from the top level, looks like this:
* simdjson/generic/ondemand/*.h: individual On-Demand classes, generically written. * simdjson/generic/ondemand/*.h: individual On-Demand classes, generically written.
* simdjson/generic/ondemand/dependencies.h: dependencies on common, non-implementation-specific simdjson classes. This will be included before including amalgamated.h. * simdjson/generic/ondemand/dependencies.h: dependencies on common, non-implementation-specific simdjson classes. This will be included before including amalgamated.h.
* simdjson/generic/ondemand/amalgamated.h: all generic ondemand classes for an implementation. * simdjson/generic/ondemand/amalgamated.h: all generic ondemand classes for an implementation.
* simdjson/builder.h: the `simdjson::builder` namespace. Includes all public builder classes.
* simdjson/builtin/builder.h: the `simdjson::builtin::builder` namespace.
* simdjson/arm64|fallback|haswell|icelake|ppc64|westmere/builder.h: the `simdjson::<implementation>::builder` namespace. Builder compiled for the specific implementation.
* simdjson/generic/builder/*.h: individual Builder classes, generically written.
* simdjson/generic/builder/dependencies.h: dependencies on common, non-implementation-specific simdjson classes. This will be included before including amalgamated.h.
* simdjson/generic/builder/amalgamated.h: all generic builder classes for an implementation.
* **src:** The source files for non-inlined functionality (e.g. the architecture-specific parser * **src:** The source files for non-inlined functionality (e.g. the architecture-specific parser
implementations). implementations).
* simdjson.cpp: A "main source" that includes all implementation files from src/. This is * simdjson.cpp: A "main source" that includes all implementation files from src/. This is
@@ -147,6 +171,7 @@ Other important files and directories:
* **.github/workflows:** Definitions for GitHub Actions (CI). * **.github/workflows:** Definitions for GitHub Actions (CI).
* **singleheader:** Contains generated `simdjson.h` and `simdjson.cpp` that we release. The files `singleheader/simdjson.h` and `singleheader/simdjson.cpp` should never be edited by hand. * **singleheader:** Contains generated `simdjson.h` and `simdjson.cpp` that we release. The files `singleheader/simdjson.h` and `singleheader/simdjson.cpp` should never be edited by hand.
* **singleheader/amalgamate.py:** Generates `singleheader/simdjson.h` and `singleheader/simdjson.cpp` for release (python script). If you add a new implementation (e.g., rvv), you need to edit this file (IMPLEMENTATIONS). * **singleheader/amalgamate.py:** Generates `singleheader/simdjson.h` and `singleheader/simdjson.cpp` for release (python script). If you add a new implementation (e.g., rvv), you need to edit this file (IMPLEMENTATIONS).
* **singleheader/amalgation_helper.py:** Generates and `amalgamation_report.html` that helps you understand the status of each file.
* **benchmark:** This is where we do benchmarking. Benchmarking is core to every change we make; the * **benchmark:** This is where we do benchmarking. Benchmarking is core to every change we make; the
cardinal rule is don't regress performance without knowing exactly why, and what you're trading cardinal rule is don't regress performance without knowing exactly why, and what you're trading
for it. Many of our benchmarks are microbenchmarks. We are effectively doing controlled scientific experiments for the purpose of understanding what affects our performance. So we simplify as much as possible. We try to avoid irrelevant factors such as page faults, interrupts, unnecessary system calls. We recommend checking the performance as follows: for it. Many of our benchmarks are microbenchmarks. We are effectively doing controlled scientific experiments for the purpose of understanding what affects our performance. So we simplify as much as possible. We try to avoid irrelevant factors such as page faults, interrupts, unnecessary system calls. We recommend checking the performance as follows:
+27 -2
View File
@@ -6,7 +6,8 @@
simdjson : Parsing gigabytes of JSON per second simdjson : Parsing gigabytes of JSON per second
=============================================== ===============================================
<img src="images/logo.png" width="10%" style="float: right"> <img src="images/official_logo/logo_noir/SVG/logo_simdjson_noir.svg" width="40%" style="float: right">
JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh
approach. The simdjson library uses commonly available SIMD instructions and microparallel algorithms approach. The simdjson library uses commonly available SIMD instructions and microparallel algorithms
to parse JSON 4x faster than RapidJSON and 25x faster than JSON for Modern C++. to parse JSON 4x faster than RapidJSON and 25x faster than JSON for Modern C++.
@@ -63,6 +64,8 @@ Real-world usage
- [RonDB](https://github.com/logicalclocks/rondb) - [RonDB](https://github.com/logicalclocks/rondb)
- [GreptimeDB](https://github.com/GreptimeTeam/greptimedb) - [GreptimeDB](https://github.com/GreptimeTeam/greptimedb)
- [mamba](https://github.com/mamba-org/mamba) - [mamba](https://github.com/mamba-org/mamba)
- [Ladybird Browser](https://ladybird.org)
- [SereneDB](https://github.com/serenedb/serenedb)
If you are planning to use simdjson in a product, please work from one of our releases. If you are planning to use simdjson in a product, please work from one of our releases.
@@ -186,6 +189,7 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
- [JSON::SIMD](https://metacpan.org/pod/JSON::SIMD): Perl bindings; fully-featured JSON module that uses simdjson for decoding. - [JSON::SIMD](https://metacpan.org/pod/JSON::SIMD): Perl bindings; fully-featured JSON module that uses simdjson for decoding.
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings. - [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port. - [simdjson-java](https://github.com/simdjson/simdjson-java): Java port.
- [mruby-fast-json](https://github.com/Asmod4n/mruby-fast-json): mruby binding with high API coverage.
About simdjson About simdjson
-------------- --------------
@@ -208,10 +212,31 @@ We have an in-depth paper focused on the UTF-8 validation:
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/). We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/).
For the video inclined, <br /> For the video inclined, we had a talk at QCon San Francisco 2019<br />
[![simdjson at QCon San Francisco 2019](http://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br /> [![simdjson at QCon San Francisco 2019](http://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
(It was the best voted talk, we're kinda proud of it.) (It was the best voted talk, we're kinda proud of it.)
We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether youre a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk!
[![simdjson at CppCon 2025](http://img.youtube.com/vi/Mcgk3CxHYMs/0.jpg)](http://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
Citing this work
-----------------
If you use simdjson in published research, please cite the software library. A suitable BibTeX entry is:
```bibtex
@misc{simdjson,
title={{The simdjson library: Parsing Gigabytes of JSON per Second}},
author={Daniel Lemire and Geoff Langdale and John Keiser and Paul Dreik and Francisco Thiesen and others},
year={2019},
howpublished={Software library},
note={https://github.com/simdjson/simdjson}
}
```
Funding Funding
------- -------
+3 -3
View File
@@ -245,7 +245,7 @@ static u32 (*kpc_get_counter_count)(u32 classes);
/// Get counter accumulations. /// Get counter accumulations.
/// If `all_cpus` is true, the buffer count should not smaller than /// If `all_cpus` is true, the buffer count should not smaller than
/// (cpu_count * counter_count). Otherwize, the buffer count should not smaller /// (cpu_count * counter_count). Otherwise, the buffer count should not smaller
/// than (counter_count). /// than (counter_count).
/// @see kpc_get_counter_count(), kpc_cpu_count(). /// @see kpc_get_counter_count(), kpc_cpu_count().
/// @param all_cpus true for all CPUs, false for current cpu. /// @param all_cpus true for all CPUs, false for current cpu.
@@ -374,7 +374,7 @@ static int kperf_lightweight_pet_set(u32 enabled) {
// These functions do not require root privileges. // These functions do not require root privileges.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// KPEP CPU archtecture constants. // KPEP CPU architecture constants.
#define KPEP_ARCH_I386 0 #define KPEP_ARCH_I386 0
#define KPEP_ARCH_X86_64 1 #define KPEP_ARCH_X86_64 1
#define KPEP_ARCH_ARM 2 #define KPEP_ARCH_ARM 2
@@ -414,7 +414,7 @@ typedef struct kpep_db {
usize fixed_counter_count; usize fixed_counter_count;
usize config_counter_count; usize config_counter_count;
usize power_counter_count; usize power_counter_count;
u32 archtecture; ///< see `KPEP CPU archtecture constants` above. u32 architecture; ///< see `KPEP CPU architecture constants` above.
u32 fixed_counter_bits; u32 fixed_counter_bits;
u32 config_counter_bits; u32 config_counter_bits;
u32 power_counter_bits; u32 power_counter_bits;
+3 -3
View File
@@ -8,7 +8,7 @@ CPMAddPackage(
option(SIMDJSON_USE_RUST "Build the static_reflect benchmark" OFF) option(SIMDJSON_USE_RUST "Build the static_reflect benchmark" OFF)
if(SIMDJSON_USER_RUST) if(SIMDJSON_USE_RUST)
if(NOT WIN32) if(NOT WIN32)
# We want the check whether Rust is available before trying to build a crate. # We want the check whether Rust is available before trying to build a crate.
CPMAddPackage( CPMAddPackage(
@@ -39,9 +39,9 @@ if(SIMDJSON_USER_RUST)
message(STATUS "curl https://sh.rustup.rs -sSf | sh") message(STATUS "curl https://sh.rustup.rs -sSf | sh")
endif() endif()
endif() endif()
else(SIMDJSON_USER_RUST) else(SIMDJSON_USE_RUST)
message(STATUS "We will not benchmark serde-benchmark." ) message(STATUS "We will not benchmark serde-benchmark." )
endif(SIMDJSON_USER_RUST) endif(SIMDJSON_USE_RUST)
# Add the benchmark executable targets # Add the benchmark executable targets
add_subdirectory(twitter_benchmark) add_subdirectory(twitter_benchmark)
@@ -1,4 +1,5 @@
add_executable(benchmark_serialization_citm_catalog benchmark_serialization_citm_catalog.cpp) add_executable(benchmark_serialization_citm_catalog benchmark_serialization_citm_catalog.cpp)
add_executable(benchmark_parsing_citm benchmark_parsing_citm.cpp)
# Link with Rust benchmarking code if available # Link with Rust benchmarking code if available
if(TARGET serde-benchmark) if(TARGET serde-benchmark)
@@ -11,4 +12,29 @@ target_link_libraries(benchmark_serialization_citm_catalog PRIVATE simdjson::sim
target_link_libraries(benchmark_serialization_citm_catalog PRIVATE reflectcpp) target_link_libraries(benchmark_serialization_citm_catalog PRIVATE reflectcpp)
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE SIMDJSON_BENCH_CPP_REFLECT=1) target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE SIMDJSON_BENCH_CPP_REFLECT=1)
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE JSON_FILE="${BENCH_CITM_JSON}") if(TARGET yyjson)
target_link_libraries(benchmark_serialization_citm_catalog PRIVATE yyjson)
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE SIMDJSON_COMPETITION_YYJSON)
endif()
target_compile_definitions(benchmark_serialization_citm_catalog PRIVATE JSON_FILE="${BENCH_CITM_JSON}")
# Configuration for parsing benchmark
if(TARGET serde-benchmark)
target_link_libraries(benchmark_parsing_citm PRIVATE serde-benchmark)
target_compile_definitions(benchmark_parsing_citm PRIVATE SIMDJSON_RUST_VERSION="${Rust_VERSION}")
endif()
target_link_libraries(benchmark_parsing_citm PRIVATE simdjson::simdjson nlohmann_json)
if(TARGET rapidjson)
target_link_libraries(benchmark_parsing_citm PRIVATE rapidjson)
target_compile_definitions(benchmark_parsing_citm PRIVATE SIMDJSON_COMPETITION_RAPIDJSON)
endif()
if(TARGET yyjson)
target_link_libraries(benchmark_parsing_citm PRIVATE yyjson)
target_compile_definitions(benchmark_parsing_citm PRIVATE SIMDJSON_COMPETITION_YYJSON)
endif()
target_compile_definitions(benchmark_parsing_citm PRIVATE JSON_FILE="${BENCH_CITM_JSON}")
@@ -0,0 +1,563 @@
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <format>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <simdjson.h>
#include <string>
#include "citm_catalog_data.h"
#include "nlohmann_citm_catalog_data.h"
#include "../benchmark_utils/benchmark_helper.h"
#ifdef SIMDJSON_COMPETITION_RAPIDJSON
#include "rapidjson_citm_catalog_data.h"
#endif
#ifdef SIMDJSON_COMPETITION_YYJSON
#include "yyjson_citm_catalog_data.h"
#endif
#ifdef SIMDJSON_RUST_VERSION
#include "../serde-benchmark/serde_benchmark.h"
void bench_rust_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_rust_parsing",
bench([&json_str, &result]() {
serde_benchmark::CitmCatalog *catalog = serde_benchmark::citm_from_str(json_str.c_str(), json_str.size());
result = (catalog != nullptr);
if (catalog) {
serde_benchmark::free_citm(catalog);
}
if (!result) {
printf("parse error\n");
}
}));
}
#endif
template <class T> void bench_simdjson_static_reflection_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
// Pre-allocate padded buffer outside the benchmark loop
std::string mutable_json = json_str;
simdjson::pad(mutable_json);
volatile bool result = true;
pretty_print(1, input_volume, "bench_simdjson_static_reflection_parsing",
bench([&mutable_json, &result]() {
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
if(parser.iterate(mutable_json).get(doc)) {
result = false;
return;
}
T my_struct;
if(doc.get<T>().get(my_struct)) {
result = false;
}
if (!result) {
printf("parse error\n");
}
}));
}
#if SIMDJSON_STATIC_REFLECTION
template <class T> void bench_simdjson_from_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
// Pre-allocate padded buffer outside the benchmark loop
simdjson::padded_string padded = simdjson::padded_string(json_str);
volatile bool result = true;
pretty_print(1, input_volume, "bench_simdjson_from_parsing",
bench([&padded, &result]() {
T my_struct;
auto err = simdjson::from(padded).get(my_struct);
if (err) {
result = false;
printf("parse error: %s\n", simdjson::error_message(err));
return;
}
}));
}
#endif
// nlohmann::json deserialization functions
void from_json(const nlohmann::json &j, CITMPrice &p) {
j.at("amount").get_to(p.amount);
j.at("audienceSubCategoryId").get_to(p.audienceSubCategoryId);
j.at("seatCategoryId").get_to(p.seatCategoryId);
}
void from_json(const nlohmann::json &j, CITMArea &a) {
j.at("areaId").get_to(a.areaId);
j.at("blockIds").get_to(a.blockIds);
}
void from_json(const nlohmann::json &j, CITMSeatCategory &s) {
j.at("areas").get_to(s.areas);
j.at("seatCategoryId").get_to(s.seatCategoryId);
}
void from_json(const nlohmann::json &j, CITMPerformance &p) {
j.at("id").get_to(p.id);
j.at("eventId").get_to(p.eventId);
if (j.contains("logo") && !j["logo"].is_null()) {
p.logo = j["logo"].get<std::string>();
}
if (j.contains("name") && !j["name"].is_null()) {
p.name = j["name"].get<std::string>();
}
j.at("prices").get_to(p.prices);
j.at("seatCategories").get_to(p.seatCategories);
if (j.contains("seatMapImage") && !j["seatMapImage"].is_null()) {
p.seatMapImage = j["seatMapImage"].get<std::string>();
}
j.at("start").get_to(p.start);
j.at("venueCode").get_to(p.venueCode);
}
void from_json(const nlohmann::json &j, CITMEvent &e) {
j.at("id").get_to(e.id);
j.at("name").get_to(e.name);
if (j.contains("description") && !j["description"].is_null()) {
e.description = j["description"].get<std::string>();
}
if (j.contains("logo") && !j["logo"].is_null()) {
e.logo = j["logo"].get<std::string>();
}
j.at("subTopicIds").get_to(e.subTopicIds);
if (j.contains("subjectCode") && !j["subjectCode"].is_null()) {
e.subjectCode = j["subjectCode"].get<std::string>();
}
if (j.contains("subtitle") && !j["subtitle"].is_null()) {
e.subtitle = j["subtitle"].get<std::string>();
}
j.at("topicIds").get_to(e.topicIds);
}
void from_json(const nlohmann::json &j, CitmCatalog &c) {
j.at("events").get_to(c.events);
j.at("performances").get_to(c.performances);
}
CitmCatalog nlohmann_deserialize(const std::string &json_str) {
nlohmann::json j = nlohmann::json::parse(json_str);
return j.get<CitmCatalog>();
}
void bench_nlohmann_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_nlohmann_parsing",
bench([&json_str, &result]() {
try {
CitmCatalog data = nlohmann_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
#ifdef SIMDJSON_COMPETITION_RAPIDJSON
CitmCatalog rapidjson_deserialize(const std::string &json_str) {
rapidjson::Document doc;
doc.Parse(json_str.c_str());
if (doc.HasParseError()) {
throw std::runtime_error("RapidJSON parse error");
}
CitmCatalog catalog;
// Parse events
if (doc.HasMember("events") && doc["events"].IsObject()) {
for (auto& m : doc["events"].GetObject()) {
CITMEvent event;
const auto& e = m.value;
event.id = e["id"].GetUint64();
event.name = e["name"].GetString();
if (e.HasMember("description") && !e["description"].IsNull()) {
event.description = e["description"].GetString();
}
if (e.HasMember("logo") && !e["logo"].IsNull()) {
event.logo = e["logo"].GetString();
}
event.subTopicIds.clear();
for (auto& id : e["subTopicIds"].GetArray()) {
event.subTopicIds.push_back(id.GetUint64());
}
if (e.HasMember("subjectCode") && !e["subjectCode"].IsNull()) {
event.subjectCode = e["subjectCode"].GetString();
}
if (e.HasMember("subtitle") && !e["subtitle"].IsNull()) {
event.subtitle = e["subtitle"].GetString();
}
event.topicIds.clear();
for (auto& id : e["topicIds"].GetArray()) {
event.topicIds.push_back(id.GetUint64());
}
catalog.events[m.name.GetString()] = event;
}
}
// Parse performances
if (doc.HasMember("performances") && doc["performances"].IsArray()) {
for (auto& p : doc["performances"].GetArray()) {
CITMPerformance perf;
perf.id = p["id"].GetUint64();
perf.eventId = p["eventId"].GetUint64();
if (p.HasMember("logo") && !p["logo"].IsNull()) {
perf.logo = p["logo"].GetString();
}
if (p.HasMember("name") && !p["name"].IsNull()) {
perf.name = p["name"].GetString();
}
// Parse prices
for (auto& price : p["prices"].GetArray()) {
CITMPrice pr;
pr.amount = price["amount"].GetUint64();
pr.audienceSubCategoryId = price["audienceSubCategoryId"].GetUint64();
pr.seatCategoryId = price["seatCategoryId"].GetUint64();
perf.prices.push_back(pr);
}
// Parse seat categories
for (auto& sc : p["seatCategories"].GetArray()) {
CITMSeatCategory seatCat;
seatCat.seatCategoryId = sc["seatCategoryId"].GetUint64();
for (auto& area : sc["areas"].GetArray()) {
CITMArea ar;
ar.areaId = area["areaId"].GetUint64();
for (auto& block : area["blockIds"].GetArray()) {
ar.blockIds.push_back(block.GetUint64());
}
seatCat.areas.push_back(ar);
}
perf.seatCategories.push_back(seatCat);
}
if (p.HasMember("seatMapImage") && !p["seatMapImage"].IsNull()) {
perf.seatMapImage = p["seatMapImage"].GetString();
}
perf.start = p["start"].GetUint64();
perf.venueCode = p["venueCode"].GetString();
catalog.performances.push_back(perf);
}
}
return catalog;
}
void bench_rapidjson_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_rapidjson_parsing",
bench([&json_str, &result]() {
try {
CitmCatalog data = rapidjson_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
#endif
#ifdef SIMDJSON_COMPETITION_YYJSON
CitmCatalog yyjson_deserialize(const std::string &json_str) {
yyjson_doc *doc = yyjson_read(json_str.c_str(), json_str.size(), 0);
if (!doc) {
throw std::runtime_error("YYJson parse error");
}
yyjson_val *root = yyjson_doc_get_root(doc);
CitmCatalog catalog;
// Parse events
yyjson_val *events = yyjson_obj_get(root, "events");
if (events) {
size_t idx, max;
yyjson_val *key, *val;
yyjson_obj_foreach(events, idx, max, key, val) {
CITMEvent event;
event.id = yyjson_get_uint(yyjson_obj_get(val, "id"));
const char* name = yyjson_get_str(yyjson_obj_get(val, "name"));
if (name) event.name = name;
yyjson_val *desc = yyjson_obj_get(val, "description");
if (desc && !yyjson_is_null(desc)) {
const char* str = yyjson_get_str(desc);
if (str) event.description = str;
}
yyjson_val *logo = yyjson_obj_get(val, "logo");
if (logo && !yyjson_is_null(logo)) {
const char* str = yyjson_get_str(logo);
if (str) event.logo = str;
}
yyjson_val *subTopics = yyjson_obj_get(val, "subTopicIds");
if (subTopics) {
size_t sidx, smax;
yyjson_val *sval;
yyjson_arr_foreach(subTopics, sidx, smax, sval) {
event.subTopicIds.push_back(yyjson_get_uint(sval));
}
}
yyjson_val *subjectCode = yyjson_obj_get(val, "subjectCode");
if (subjectCode && !yyjson_is_null(subjectCode)) {
const char* str = yyjson_get_str(subjectCode);
if (str) event.subjectCode = str;
}
yyjson_val *subtitle = yyjson_obj_get(val, "subtitle");
if (subtitle && !yyjson_is_null(subtitle)) {
const char* str = yyjson_get_str(subtitle);
if (str) event.subtitle = str;
}
yyjson_val *topics = yyjson_obj_get(val, "topicIds");
if (topics) {
size_t tidx, tmax;
yyjson_val *tval;
yyjson_arr_foreach(topics, tidx, tmax, tval) {
event.topicIds.push_back(yyjson_get_uint(tval));
}
}
const char* keyStr = yyjson_get_str(key);
if (keyStr) {
catalog.events[keyStr] = event;
}
}
}
// Parse performances
yyjson_val *performances = yyjson_obj_get(root, "performances");
if (performances) {
size_t idx, max;
yyjson_val *val;
yyjson_arr_foreach(performances, idx, max, val) {
CITMPerformance perf;
perf.id = yyjson_get_uint(yyjson_obj_get(val, "id"));
perf.eventId = yyjson_get_uint(yyjson_obj_get(val, "eventId"));
yyjson_val *logo = yyjson_obj_get(val, "logo");
if (logo && !yyjson_is_null(logo)) {
const char* str = yyjson_get_str(logo);
if (str) perf.logo = str;
}
yyjson_val *name = yyjson_obj_get(val, "name");
if (name && !yyjson_is_null(name)) {
const char* str = yyjson_get_str(name);
if (str) perf.name = str;
}
// Parse prices
yyjson_val *prices = yyjson_obj_get(val, "prices");
if (prices) {
size_t pidx, pmax;
yyjson_val *pval;
yyjson_arr_foreach(prices, pidx, pmax, pval) {
CITMPrice price;
price.amount = yyjson_get_uint(yyjson_obj_get(pval, "amount"));
price.audienceSubCategoryId = yyjson_get_uint(yyjson_obj_get(pval, "audienceSubCategoryId"));
price.seatCategoryId = yyjson_get_uint(yyjson_obj_get(pval, "seatCategoryId"));
perf.prices.push_back(price);
}
}
// Parse seat categories
yyjson_val *seatCats = yyjson_obj_get(val, "seatCategories");
if (seatCats) {
size_t scidx, scmax;
yyjson_val *scval;
yyjson_arr_foreach(seatCats, scidx, scmax, scval) {
CITMSeatCategory seatCat;
seatCat.seatCategoryId = yyjson_get_uint(yyjson_obj_get(scval, "seatCategoryId"));
yyjson_val *areas = yyjson_obj_get(scval, "areas");
if (areas) {
size_t aidx, amax;
yyjson_val *aval;
yyjson_arr_foreach(areas, aidx, amax, aval) {
CITMArea area;
area.areaId = yyjson_get_uint(yyjson_obj_get(aval, "areaId"));
yyjson_val *blocks = yyjson_obj_get(aval, "blockIds");
if (blocks) {
size_t bidx, bmax;
yyjson_val *bval;
yyjson_arr_foreach(blocks, bidx, bmax, bval) {
area.blockIds.push_back(yyjson_get_uint(bval));
}
}
seatCat.areas.push_back(area);
}
}
perf.seatCategories.push_back(seatCat);
}
}
yyjson_val *seatMapImage = yyjson_obj_get(val, "seatMapImage");
if (seatMapImage && !yyjson_is_null(seatMapImage)) {
const char* str = yyjson_get_str(seatMapImage);
if (str) perf.seatMapImage = str;
}
perf.start = yyjson_get_uint(yyjson_obj_get(val, "start"));
const char* venueCode = yyjson_get_str(yyjson_obj_get(val, "venueCode"));
if (venueCode) perf.venueCode = venueCode;
catalog.performances.push_back(perf);
}
}
yyjson_doc_free(doc);
return catalog;
}
void bench_yyjson_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_yyjson_parsing",
bench([&json_str, &result]() {
try {
CitmCatalog data = yyjson_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
#endif
std::string read_file(std::string filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 4096;
auto stream = std::ifstream(filename);
stream.exceptions(std::ios_base::badbit);
if (!stream) {
std::cerr << "Error: Failed to open file " << filename << std::endl;
exit(EXIT_FAILURE);
}
std::string out;
auto buf = std::string(read_size, '\0');
while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount()));
}
out.append(buf, 0, size_t(stream.gcount()));
return out;
}
// Function to check if benchmark name matches any of the comma-separated filters
bool matches_filter(const std::string& benchmark_name, const std::string& filter) {
if (filter.empty()) return true;
// Split filter by comma
size_t start = 0;
size_t end = filter.find(',');
while (end != std::string::npos) {
std::string token = filter.substr(start, end - start);
if (benchmark_name.find(token) != std::string::npos) {
return true;
}
start = end + 1;
end = filter.find(',', start);
}
// Check last token
std::string token = filter.substr(start);
return benchmark_name.find(token) != std::string::npos;
}
int main(int argc, char *argv[]) {
// Get the JSON file path from preprocessor or use default
std::string filename;
#ifdef JSON_FILE
filename = JSON_FILE;
#else
filename = "jsonexamples/citm_catalog.json";
#endif
std::string json_str = read_file(filename);
// Parse command-line arguments for filter
std::string filter;
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-f" && i + 1 < argc) {
filter = argv[i + 1];
printf("# Filter: %s\n", filter.c_str());
i++;
}
}
// If no filter provided, run all benchmarks
if (filter.empty()) {
printf("# Running all benchmarks (use -f <filter> to run specific ones)\n");
}
// Benchmarking the parsing
if (matches_filter("nlohmann", filter)) {
bench_nlohmann_parsing(json_str);
}
#ifdef SIMDJSON_COMPETITION_RAPIDJSON
if (matches_filter("rapidjson", filter)) {
bench_rapidjson_parsing(json_str);
}
#endif
#ifdef SIMDJSON_COMPETITION_YYJSON
if (matches_filter("yyjson", filter)) {
bench_yyjson_parsing(json_str);
}
#endif
if (matches_filter("simdjson_static_reflection", filter)) {
bench_simdjson_static_reflection_parsing<CitmCatalog>(json_str);
}
#if SIMDJSON_STATIC_REFLECTION
if (matches_filter("simdjson_from", filter)) {
bench_simdjson_from_parsing<CitmCatalog>(json_str);
}
#endif
#ifdef SIMDJSON_RUST_VERSION
if (matches_filter("rust", filter)) {
printf("# Note: Rust/Serde parsing test\n");
bench_rust_parsing(json_str);
}
#endif
return EXIT_SUCCESS;
}
@@ -11,6 +11,10 @@
#include "nlohmann_citm_catalog_data.h" #include "nlohmann_citm_catalog_data.h"
#include "../benchmark_utils/benchmark_helper.h" #include "../benchmark_utils/benchmark_helper.h"
#ifdef SIMDJSON_COMPETITION_YYJSON
#include "yyjson_citm_catalog_data.h"
#endif
#if SIMDJSON_BENCH_CPP_REFLECT #if SIMDJSON_BENCH_CPP_REFLECT
#include <rfl.hpp> #include <rfl.hpp>
#include <rfl/json.hpp> #include <rfl/json.hpp>
@@ -35,20 +39,15 @@ void bench_reflect_cpp(CitmCatalog &data) {
#include "../serde-benchmark/serde_benchmark.h" #include "../serde-benchmark/serde_benchmark.h"
void bench_rust(serde_benchmark::CitmCatalog *data) { void bench_rust(serde_benchmark::CitmCatalog *data) {
const char * output = serde_benchmark::str_from_citm(data); serde_benchmark::set_citm_data(data);
size_t output_volume = strlen(output); size_t output_volume = serde_benchmark::serialize_citm_to_string();
printf("# output volume: %zu bytes\n", output_volume); printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0; volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_rust", pretty_print(1, output_volume, "bench_rust",
bench([&data, &measured_volume, &output_volume]() { bench([&measured_volume, &output_volume]() {
const char * output = serde_benchmark::str_from_citm(data); measured_volume = serde_benchmark::serialize_citm_to_string();
measured_volume = strlen(output);
if (measured_volume != output_volume) {
printf("mismatch\n");
}
serde_benchmark::free_str(const_cast<char*>(output));
})); }));
serde_benchmark::free_str(const_cast<char*>(output));
} }
#endif // SIMDJSON_RUST_VERSION #endif // SIMDJSON_RUST_VERSION
@@ -68,7 +67,55 @@ void bench_nlohmann(CitmCatalog &data) {
})); }));
} }
#ifdef SIMDJSON_COMPETITION_YYJSON
void bench_yyjson(CitmCatalog &data) {
std::string output = yyjson_serialize_citm(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_yyjson",
bench([&data, &measured_volume, &output_volume]() {
std::string output = yyjson_serialize_citm(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
#endif
// Fair allocation variant: allocates fresh buffer each iteration (matches other libraries)
void bench_simdjson_static_reflection(CitmCatalog &data) { void bench_simdjson_static_reflection(CitmCatalog &data) {
// First run to determine expected size
simdjson::builder::string_builder sb_init;
simdjson::builder::append(sb_init, data);
std::string_view p_init;
if(sb_init.view().get(p_init)) {
std::cerr << "Error!" << std::endl;
}
size_t output_volume = p_init.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_static_reflection",
bench([&data, &measured_volume, &output_volume]() {
// Fresh allocation each iteration - fair comparison
simdjson::builder::string_builder sb;
simdjson::builder::append(sb, data);
std::string_view p;
if(sb.view().get(p)) {
std::cerr << "Error!" << std::endl;
}
measured_volume = sb.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
// Optimized variant: reuses buffer across iterations (shows API potential)
void bench_simdjson_static_reflection_reuse(CitmCatalog &data) {
simdjson::builder::string_builder sb; simdjson::builder::string_builder sb;
simdjson::builder::append(sb, data); simdjson::builder::append(sb, data);
std::string_view p; std::string_view p;
@@ -80,7 +127,7 @@ void bench_simdjson_static_reflection(CitmCatalog &data) {
printf("# output volume: %zu bytes\n", output_volume); printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0; volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_static_reflection", pretty_print(sizeof(data), output_volume, "bench_simdjson_reuse_buffer",
bench([&data, &measured_volume, &output_volume, &sb]() { bench([&data, &measured_volume, &output_volume, &sb]() {
sb.clear(); sb.clear();
simdjson::builder::append(sb, data); simdjson::builder::append(sb, data);
@@ -95,25 +142,97 @@ void bench_simdjson_static_reflection(CitmCatalog &data) {
})); }));
} }
std::string read_file(const std::string &file_path, size_t read_size = 65536) { #if SIMDJSON_STATIC_REFLECTION
// Fair allocation variant: allocates fresh string each iteration
void bench_simdjson_to(CitmCatalog &data) {
// First run to determine size
std::string output_init;
if (simdjson::error_code err = simdjson::builder::to_json(data, output_init); err) {
std::cerr << "Error in to_json initialization!" << simdjson::error_message(err) << std::endl;
return;
}
size_t output_volume = output_init.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_to",
bench([&data, &measured_volume, &output_volume]() {
// Fresh allocation each iteration - fair comparison
std::string output;
if (simdjson::error_code err = simdjson::builder::to_json(data, output); err) {
std::cerr << "Error in to_json!" << simdjson::error_message(err) << std::endl;
return;
}
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
// Optimized variant: reuses pre-allocated string
void bench_simdjson_to_reuse(CitmCatalog &data) {
std::string output;
if (simdjson::error_code err = simdjson::builder::to_json(data, output); err) {
std::cerr << "Error in to_json initialization!" << simdjson::error_message(err) << std::endl;
return;
}
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
// Pre-allocate string with sufficient capacity to avoid reallocation
output.reserve(output_volume * 2);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_to_reuse",
bench([&data, &measured_volume, &output_volume, &output]() {
// Reuse the pre-allocated string - avoids allocation
if (simdjson::error_code err = simdjson::builder::to_json(data, output); err) {
std::cerr << "Error in to_json!" << simdjson::error_message(err) << std::endl;
return;
}
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
#endif
simdjson::padded_string read_file(const std::string &file_path, size_t read_size = 65536) {
std::ifstream stream(file_path, std::ios::binary); std::ifstream stream(file_path, std::ios::binary);
if(!stream) { if(!stream) {
std::cerr << "Could not open file '" << file_path << "'" << std::endl; std::cerr << "Could not open file '" << file_path << "'" << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
stream.exceptions(std::ios_base::badbit); stream.exceptions(std::ios_base::badbit);
std::string out; simdjson::padded_string_builder builder;
std::string buf(read_size, '\0'); std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) { while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount())); builder.append(buf.data(), size_t(stream.gcount()));
} }
out.append(buf, 0, size_t(stream.gcount())); builder.append(buf.data(), size_t(stream.gcount()));
return out; return builder.convert();
} }
// Function to check if benchmark name contains filter substring // Function to check if benchmark name matches any of the comma-separated filters
bool matches_filter(const std::string& benchmark_name, const std::string& filter) { bool matches_filter(const std::string& benchmark_name, const std::string& filter) {
return filter.empty() || benchmark_name.find(filter) != std::string::npos; if (filter.empty()) return true;
// Split filter by comma
size_t start = 0;
size_t end = filter.find(',');
while (end != std::string::npos) {
std::string token = filter.substr(start, end - start);
if (benchmark_name.find(token) != std::string::npos) {
return true;
}
start = end + 1;
end = filter.find(',', start);
}
// Check last token
std::string token = filter.substr(start);
return benchmark_name.find(token) != std::string::npos;
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@@ -131,12 +250,12 @@ int main(int argc, char* argv[]) {
} }
} }
// Testing correctness of round-trip (serialization + deserialization) // Testing correctness of round-trip (serialization + deserialization)
std::string json_str = read_file(JSON_FILE); simdjson::padded_string json_str = read_file(JSON_FILE);
// Loading up the data into a structure. // Loading up the data into a structure.
simdjson::ondemand::parser parser; simdjson::ondemand::parser parser;
simdjson::ondemand::document doc; simdjson::ondemand::document doc;
if(parser.iterate(simdjson::pad(json_str)).get(doc)) { if(parser.iterate(json_str).get(doc)) {
std::cerr << "Error loading the document!" << std::endl; std::cerr << "Error loading the document!" << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@@ -147,18 +266,37 @@ int main(int argc, char* argv[]) {
} }
// Benchmarking the serialization // Benchmarking the serialization
// Note: simdjson benchmarks include both "fair" (fresh allocation) and "reuse" (buffer reuse) variants
// The "fair" variants allocate fresh memory each iteration, matching other libraries' behavior
// The "reuse" variants demonstrate the API's potential when buffer reuse is possible
if (matches_filter("nlohmann", filter)) { if (matches_filter("nlohmann", filter)) {
bench_nlohmann(my_struct); bench_nlohmann(my_struct);
} }
#ifdef SIMDJSON_COMPETITION_YYJSON
if (matches_filter("yyjson", filter)) {
bench_yyjson(my_struct);
}
#endif
if (matches_filter("simdjson_static_reflection", filter)) { if (matches_filter("simdjson_static_reflection", filter)) {
bench_simdjson_static_reflection(my_struct); bench_simdjson_static_reflection(my_struct);
} }
if (matches_filter("simdjson_reuse", filter)) {
bench_simdjson_static_reflection_reuse(my_struct);
}
#if SIMDJSON_STATIC_REFLECTION
if (matches_filter("simdjson_to", filter)) {
bench_simdjson_to(my_struct);
}
if (matches_filter("simdjson_to_reuse", filter)) {
bench_simdjson_to_reuse(my_struct);
}
#endif
#ifdef SIMDJSON_RUST_VERSION #ifdef SIMDJSON_RUST_VERSION
if (matches_filter("rust", filter)) { if (matches_filter("rust", filter)) {
printf("# WARNING: The Rust benchmark may not be directly comparable since it does not use an equivalent data structure.\n");
// Create a Rust-compatible CitmCatalog structure from the JSON string // Create a Rust-compatible CitmCatalog structure from the JSON string
serde_benchmark::CitmCatalog* rust_data = serde_benchmark::CitmCatalog* rust_data =
serde_benchmark::citm_from_str(json_str.c_str(), json_str.size()); serde_benchmark::citm_from_str(json_str.data(), json_str.size());
if (rust_data == nullptr) { if (rust_data == nullptr) {
printf("# Failed to initialize Rust data structure\n"); printf("# Failed to initialize Rust data structure\n");
@@ -4,79 +4,65 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
#include <optional>
#include <cstdint>
struct Area { // Price structure - field names must match JSON keys for reflection
int64_t id; struct CITMPrice {
std::string name; uint64_t amount;
int64_t parent; uint64_t audienceSubCategoryId;
std::vector<int64_t> childAreas; uint64_t seatCategoryId;
bool operator==(const Area &other) const = default; bool operator==(const CITMPrice&) const = default;
}; };
struct AudienceSubCategory { struct CITMArea {
int64_t id; uint64_t areaId;
std::string name; std::vector<uint64_t> blockIds;
int64_t parent; bool operator==(const CITMArea&) const = default;
bool operator==(const AudienceSubCategory &other) const = default;
}; };
struct Event { struct CITMSeatCategory {
int64_t id; std::vector<CITMArea> areas;
std::string name; uint64_t seatCategoryId;
std::string description; bool operator==(const CITMSeatCategory&) const = default;
int64_t subTopic;
int64_t topic;
std::vector<int64_t> audience;
bool operator==(const Event &other) const = default;
}; };
struct Performance { struct CITMPerformance {
int64_t id; uint64_t id;
std::string name; uint64_t eventId;
int64_t event; std::optional<std::string> logo;
std::string start; std::optional<std::string> name;
int64_t venueCode; std::vector<CITMPrice> prices;
bool operator==(const Performance &other) const = default; std::vector<CITMSeatCategory> seatCategories;
std::optional<std::string> seatMapImage;
uint64_t start;
std::string venueCode;
bool operator==(const CITMPerformance&) const = default;
}; };
struct SeatCategory { struct CITMEvent {
int64_t id; uint64_t id;
std::string name; std::string name;
std::vector<int64_t> areas; std::optional<std::string> description;
bool operator==(const SeatCategory &other) const = default; std::optional<std::string> logo;
}; std::vector<uint64_t> subTopicIds;
std::optional<std::string> subjectCode;
struct SubTopic { std::optional<std::string> subtitle;
int64_t id; std::vector<uint64_t> topicIds;
std::string name; bool operator==(const CITMEvent&) const = default;
int64_t parent;
bool operator==(const SubTopic &other) const = default;
};
struct Topic {
int64_t id;
std::string name;
bool operator==(const Topic &other) const = default;
};
struct Venue {
int64_t id;
std::string name;
int64_t address;
bool operator==(const Venue &other) const = default;
}; };
struct CitmCatalog { struct CitmCatalog {
std::map<std::string, Area> areas; std::map<std::string, CITMEvent> events;
std::map<std::string, AudienceSubCategory> audienceSubCategory; std::vector<CITMPerformance> performances;
std::map<std::string, Event> events; bool operator==(const CitmCatalog&) const = default;
std::map<std::string, Performance> performances;
std::map<std::string, SeatCategory> seatCategory;
std::map<std::string, SubTopic> subTopic;
std::map<std::string, Topic> topic;
std::map<std::string, Venue> venue;
bool operator==(const CitmCatalog &other) const = default;
}; };
#endif // Type aliases
using Event = CITMEvent;
using Performance = CITMPerformance;
using Price = CITMPrice;
using SeatArea = CITMArea;
using SeatCategoryInfo = CITMSeatCategory;
#endif
@@ -8,164 +8,72 @@
using json = nlohmann::json; using json = nlohmann::json;
// ---- Area ---- // ---- CITMPrice ----
inline void to_json(json &j, const Area &a) { inline void to_json(json &j, const CITMPrice &p) {
j = json{ j = json{
{"id", a.id}, {"amount", p.amount},
{"name", a.name}, {"audienceSubCategoryId", p.audienceSubCategoryId},
{"parent", a.parent}, {"seatCategoryId", p.seatCategoryId}
{"childAreas", a.childAreas}
}; };
} }
inline void from_json(const json &j, Area &a) {
j.at("id").get_to(a.id);
j.at("name").get_to(a.name);
j.at("parent").get_to(a.parent);
j.at("childAreas").get_to(a.childAreas);
}
// ---- AudienceSubCategory ---- // ---- CITMArea ----
inline void to_json(json &j, const AudienceSubCategory &asc) { inline void to_json(json &j, const CITMArea &a) {
j = json{ j = json{
{"id", asc.id}, {"areaId", a.areaId},
{"name", asc.name}, {"blockIds", a.blockIds}
{"parent", asc.parent}
}; };
} }
inline void from_json(const json &j, AudienceSubCategory &asc) {
j.at("id").get_to(asc.id);
j.at("name").get_to(asc.name);
j.at("parent").get_to(asc.parent);
}
// ---- Event ---- // ---- CITMSeatCategory ----
inline void to_json(json &j, const Event &e) { inline void to_json(json &j, const CITMSeatCategory &s) {
j = json{ j = json{
{"id", e.id}, {"areas", s.areas},
{"name", e.name}, {"seatCategoryId", s.seatCategoryId}
{"description", e.description},
{"subTopic", e.subTopic},
{"topic", e.topic},
{"audience", e.audience}
}; };
} }
inline void from_json(const json &j, Event &e) {
j.at("id").get_to(e.id);
j.at("name").get_to(e.name);
j.at("description").get_to(e.description);
j.at("subTopic").get_to(e.subTopic);
j.at("topic").get_to(e.topic);
j.at("audience").get_to(e.audience);
}
// ---- Performance ---- // ---- CITMPerformance ----
inline void to_json(json &j, const Performance &p) { inline void to_json(json &j, const CITMPerformance &p) {
j = json{ j = json{
{"id", p.id}, {"id", p.id},
{"eventId", p.eventId},
{"logo", p.logo},
{"name", p.name}, {"name", p.name},
{"event", p.event}, {"prices", p.prices},
{"seatCategories", p.seatCategories},
{"seatMapImage", p.seatMapImage},
{"start", p.start}, {"start", p.start},
{"venueCode", p.venueCode} {"venueCode", p.venueCode}
}; };
} }
inline void from_json(const json &j, Performance &p) {
j.at("id").get_to(p.id);
j.at("name").get_to(p.name);
j.at("event").get_to(p.event);
j.at("start").get_to(p.start);
j.at("venueCode").get_to(p.venueCode);
}
// ---- SeatCategory ---- // ---- CITMEvent ----
inline void to_json(json &j, const SeatCategory &sc) { inline void to_json(json &j, const CITMEvent &e) {
j = json{ j = json{
{"id", sc.id}, {"id", e.id},
{"name", sc.name}, {"name", e.name},
{"areas", sc.areas} {"description", e.description},
{"logo", e.logo},
{"subTopicIds", e.subTopicIds},
{"subjectCode", e.subjectCode},
{"subtitle", e.subtitle},
{"topicIds", e.topicIds}
}; };
} }
inline void from_json(const json &j, SeatCategory &sc) {
j.at("id").get_to(sc.id);
j.at("name").get_to(sc.name);
j.at("areas").get_to(sc.areas);
}
// ---- SubTopic ----
inline void to_json(json &j, const SubTopic &st) {
j = json{
{"id", st.id},
{"name", st.name},
{"parent", st.parent}
};
}
inline void from_json(const json &j, SubTopic &st) {
j.at("id").get_to(st.id);
j.at("name").get_to(st.name);
j.at("parent").get_to(st.parent);
}
// ---- Topic ----
inline void to_json(json &j, const Topic &t) {
j = json{
{"id", t.id},
{"name", t.name}
};
}
inline void from_json(const json &j, Topic &t) {
j.at("id").get_to(t.id);
j.at("name").get_to(t.name);
}
// ---- Venue ----
inline void to_json(json &j, const Venue &v) {
j = json{
{"id", v.id},
{"name", v.name},
{"address", v.address}
};
}
inline void from_json(const json &j, Venue &v) {
j.at("id").get_to(v.id);
j.at("name").get_to(v.name);
j.at("address").get_to(v.address);
}
// ---- CitmCatalog ---- // ---- CitmCatalog ----
inline void to_json(json &j, const CitmCatalog &c) { inline void to_json(json &j, const CitmCatalog &c) {
j = json{ j = json{
{"areas", c.areas},
{"audienceSubCategory", c.audienceSubCategory},
{"events", c.events}, {"events", c.events},
{"performances", c.performances}, {"performances", c.performances}
{"seatCategory", c.seatCategory},
{"subTopic", c.subTopic},
{"topic", c.topic},
{"venue", c.venue}
}; };
} }
inline void from_json(const json &j, CitmCatalog &c) {
j.at("areas").get_to(c.areas);
j.at("audienceSubCategory").get_to(c.audienceSubCategory);
j.at("events").get_to(c.events);
j.at("performances").get_to(c.performances);
j.at("seatCategory").get_to(c.seatCategory);
j.at("subTopic").get_to(c.subTopic);
j.at("topic").get_to(c.topic);
j.at("venue").get_to(c.venue);
}
// Optional convenience functions for benchmarking // Serialization function
inline std::string nlohmann_serialize(const CitmCatalog &catalog) { inline std::string nlohmann_serialize(const CitmCatalog &catalog) {
json j = catalog; json j = catalog;
return j.dump(); return j.dump();
} }
inline bool nlohmann_deserialize(const std::string &json_in, CitmCatalog &catalog) {
try {
catalog = json::parse(json_in);
return false; // success
} catch(...) {
return true; // failure
}
}
#endif // NLOHMANN_CITM_CATALOG_DATA_H #endif // NLOHMANN_CITM_CATALOG_DATA_H
@@ -0,0 +1,189 @@
#ifndef RAPIDJSON_CITM_CATALOG_DATA_H
#define RAPIDJSON_CITM_CATALOG_DATA_H
#include "citm_catalog_data.h"
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/error/en.h>
using namespace rapidjson;
// RapidJSON deserialization for CITM Catalog data
CitmCatalog rapidjson_deserialize_citm(const std::string& json_str) {
Document doc;
doc.Parse(json_str.c_str());
if (doc.HasParseError()) {
throw std::runtime_error("RapidJSON parse error");
}
CitmCatalog catalog;
// Parse events
if (doc.HasMember("events") && doc["events"].IsObject()) {
const Value& events = doc["events"];
for (auto it = events.MemberBegin(); it != events.MemberEnd(); ++it) {
Event event;
const Value& ev = it->value;
if (ev.HasMember("description") && ev["description"].IsString())
event.description = ev["description"].GetString();
if (ev.HasMember("id") && ev["id"].IsUint64())
event.id = ev["id"].GetUint64();
if (ev.HasMember("logo") && ev["logo"].IsString())
event.logo = ev["logo"].GetString();
if (ev.HasMember("name") && ev["name"].IsString())
event.name = ev["name"].GetString();
if (ev.HasMember("subjectCode") && ev["subjectCode"].IsString())
event.subjectCode = ev["subjectCode"].GetString();
if (ev.HasMember("subtitle") && ev["subtitle"].IsString())
event.subtitle = ev["subtitle"].GetString();
if (ev.HasMember("topicIds") && ev["topicIds"].IsArray()) {
const Value& topics = ev["topicIds"];
for (SizeType j = 0; j < topics.Size(); j++) {
if (topics[j].IsUint64())
event.topicIds.push_back(topics[j].GetUint64());
}
}
if (ev.HasMember("subTopicIds") && ev["subTopicIds"].IsArray()) {
const Value& subtopics = ev["subTopicIds"];
for (SizeType j = 0; j < subtopics.Size(); j++) {
if (subtopics[j].IsUint64())
event.subTopicIds.push_back(subtopics[j].GetUint64());
}
}
catalog.events[it->name.GetString()] = event;
}
}
// Parse performances
if (doc.HasMember("performances") && doc["performances"].IsArray()) {
const Value& performances = doc["performances"];
for (SizeType i = 0; i < performances.Size(); i++) {
Performance perf;
const Value& p = performances[i];
if (p.HasMember("id") && p["id"].IsUint64())
perf.id = p["id"].GetUint64();
if (p.HasMember("eventId") && p["eventId"].IsUint64())
perf.eventId = p["eventId"].GetUint64();
if (p.HasMember("start") && p["start"].IsUint64())
perf.start = p["start"].GetUint64();
if (p.HasMember("venueCode") && p["venueCode"].IsString())
perf.venueCode = p["venueCode"].GetString();
if (p.HasMember("name") && p["name"].IsString())
perf.name = p["name"].GetString();
catalog.performances.push_back(perf);
}
}
// Parse other string maps
auto parseStringMap = [&doc](const char* key, std::map<std::string, std::string>& target) {
if (doc.HasMember(key) && doc[key].IsObject()) {
const Value& obj = doc[key];
for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) {
if (it->value.IsString()) {
target[it->name.GetString()] = it->value.GetString();
}
}
}
};
return catalog;
}
// RapidJSON serialization for CITM Catalog data
std::string rapidjson_serialize_citm(const CitmCatalog& catalog) {
Document doc;
doc.SetObject();
Document::AllocatorType& allocator = doc.GetAllocator();
// Serialize events
Value events_obj(kObjectType);
for (const auto& [key, event] : catalog.events) {
Value event_obj(kObjectType);
if (event.description) {
Value desc;
desc.SetString(event.description->c_str(), allocator);
event_obj.AddMember("description", desc, allocator);
}
event_obj.AddMember("id", event.id, allocator);
if (event.logo) {
Value logo;
logo.SetString(event.logo->c_str(), allocator);
event_obj.AddMember("logo", logo, allocator);
}
Value name;
name.SetString(event.name.c_str(), allocator);
event_obj.AddMember("name", name, allocator);
if (event.subjectCode) {
Value subject;
subject.SetString(event.subjectCode->c_str(), allocator);
event_obj.AddMember("subjectCode", subject, allocator);
}
if (event.subtitle) {
Value subtitle;
subtitle.SetString(event.subtitle->c_str(), allocator);
event_obj.AddMember("subtitle", subtitle, allocator);
}
Value topicIds(kArrayType);
for (uint64_t id : event.topicIds) {
topicIds.PushBack(id, allocator);
}
event_obj.AddMember("topicIds", topicIds, allocator);
Value subTopicIds(kArrayType);
for (uint64_t id : event.subTopicIds) {
subTopicIds.PushBack(id, allocator);
}
event_obj.AddMember("subTopicIds", subTopicIds, allocator);
Value key_val;
key_val.SetString(key.c_str(), allocator);
events_obj.AddMember(key_val, event_obj, allocator);
}
doc.AddMember("events", events_obj, allocator);
// Serialize performances
Value performances_array(kArrayType);
for (const auto& perf : catalog.performances) {
Value perf_obj(kObjectType);
perf_obj.AddMember("id", perf.id, allocator);
perf_obj.AddMember("eventId", perf.eventId, allocator);
perf_obj.AddMember("start", perf.start, allocator);
Value venue;
venue.SetString(perf.venueCode.c_str(), allocator);
perf_obj.AddMember("venueCode", venue, allocator);
if (perf.name) {
Value name;
name.SetString(perf.name->c_str(), allocator);
perf_obj.AddMember("name", name, allocator);
}
performances_array.PushBack(perf_obj, allocator);
}
doc.AddMember("performances", performances_array, allocator);
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
doc.Accept(writer);
return buffer.GetString();
}
#endif // RAPIDJSON_CITM_CATALOG_DATA_H
@@ -0,0 +1,231 @@
#ifndef YYJSON_CITM_CATALOG_DATA_H
#define YYJSON_CITM_CATALOG_DATA_H
#include "citm_catalog_data.h"
#include <yyjson.h>
#include <string>
#include <stdexcept>
// yyjson deserialization for CITM Catalog data
// Matches C++ CitmCatalog struct (only events + performances)
CitmCatalog yyjson_deserialize_citm(const std::string &json_str) {
CitmCatalog catalog;
yyjson_doc *doc = yyjson_read(json_str.c_str(), json_str.size(), 0);
if (!doc) {
throw std::runtime_error("yyjson parse error");
}
yyjson_val *root = yyjson_doc_get_root(doc);
if (!root) {
yyjson_doc_free(doc);
return catalog;
}
// Parse events
yyjson_val *events_val = yyjson_obj_get(root, "events");
if (events_val && yyjson_is_obj(events_val)) {
size_t idx, max;
yyjson_val *key, *val;
yyjson_obj_foreach(events_val, idx, max, key, val) {
CITMEvent event;
yyjson_val *v;
v = yyjson_obj_get(val, "description");
if (v && yyjson_is_str(v)) event.description = yyjson_get_str(v);
v = yyjson_obj_get(val, "id");
if (v && yyjson_is_uint(v)) event.id = yyjson_get_uint(v);
v = yyjson_obj_get(val, "logo");
if (v && yyjson_is_str(v)) event.logo = yyjson_get_str(v);
v = yyjson_obj_get(val, "name");
if (v && yyjson_is_str(v)) event.name = yyjson_get_str(v);
v = yyjson_obj_get(val, "subjectCode");
if (v && yyjson_is_str(v)) event.subjectCode = yyjson_get_str(v);
v = yyjson_obj_get(val, "subtitle");
if (v && yyjson_is_str(v)) event.subtitle = yyjson_get_str(v);
// Parse topicIds array
v = yyjson_obj_get(val, "topicIds");
if (v && yyjson_is_arr(v)) {
size_t arr_idx, arr_max;
yyjson_val *arr_val;
yyjson_arr_foreach(v, arr_idx, arr_max, arr_val) {
if (yyjson_is_uint(arr_val))
event.topicIds.push_back(yyjson_get_uint(arr_val));
}
}
// Parse subTopicIds array
v = yyjson_obj_get(val, "subTopicIds");
if (v && yyjson_is_arr(v)) {
size_t arr_idx, arr_max;
yyjson_val *arr_val;
yyjson_arr_foreach(v, arr_idx, arr_max, arr_val) {
if (yyjson_is_uint(arr_val))
event.subTopicIds.push_back(yyjson_get_uint(arr_val));
}
}
if (yyjson_is_str(key))
catalog.events[yyjson_get_str(key)] = event;
}
}
// Parse performances (simplified - full parsing would need prices/seatCategories)
yyjson_val *performances_val = yyjson_obj_get(root, "performances");
if (performances_val && yyjson_is_arr(performances_val)) {
size_t idx, max;
yyjson_val *perf_val;
yyjson_arr_foreach(performances_val, idx, max, perf_val) {
CITMPerformance perf;
yyjson_val *v;
v = yyjson_obj_get(perf_val, "id");
if (v && yyjson_is_uint(v)) perf.id = yyjson_get_uint(v);
v = yyjson_obj_get(perf_val, "eventId");
if (v && yyjson_is_uint(v)) perf.eventId = yyjson_get_uint(v);
v = yyjson_obj_get(perf_val, "start");
if (v && yyjson_is_uint(v)) perf.start = yyjson_get_uint(v);
v = yyjson_obj_get(perf_val, "venueCode");
if (v && yyjson_is_str(v)) perf.venueCode = yyjson_get_str(v);
v = yyjson_obj_get(perf_val, "name");
if (v && yyjson_is_str(v)) perf.name = yyjson_get_str(v);
v = yyjson_obj_get(perf_val, "logo");
if (v && yyjson_is_str(v)) perf.logo = yyjson_get_str(v);
v = yyjson_obj_get(perf_val, "seatMapImage");
if (v && yyjson_is_str(v)) perf.seatMapImage = yyjson_get_str(v);
// Note: prices and seatCategories parsing omitted for brevity
// The serialization benchmark uses data loaded by simdjson
catalog.performances.push_back(perf);
}
}
yyjson_doc_free(doc);
return catalog;
}
// Helper to add optional string field
static inline void yyjson_add_optional_str(yyjson_mut_doc *doc, yyjson_mut_val *obj,
const char *key, const std::optional<std::string> &val) {
if (val.has_value()) {
yyjson_mut_obj_add_str(doc, obj, key, val->c_str());
} else {
yyjson_mut_obj_add_null(doc, obj, key);
}
}
// yyjson serialization for CITM Catalog data
// Matches C++ CitmCatalog struct exactly (only events + performances)
std::string yyjson_serialize_citm(const CitmCatalog &catalog) {
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
yyjson_mut_val *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
// Create events object
yyjson_mut_val *events_obj = yyjson_mut_obj(doc);
for (const auto& [key, event] : catalog.events) {
yyjson_mut_val *event_obj = yyjson_mut_obj(doc);
yyjson_add_optional_str(doc, event_obj, "description", event.description);
yyjson_mut_obj_add_uint(doc, event_obj, "id", event.id);
yyjson_add_optional_str(doc, event_obj, "logo", event.logo);
// name is not optional in CITMEvent
yyjson_mut_obj_add_str(doc, event_obj, "name", event.name.c_str());
// Add subTopicIds array
yyjson_mut_val *subtopic_ids = yyjson_mut_arr(doc);
for (uint64_t id : event.subTopicIds) {
yyjson_mut_arr_add_uint(doc, subtopic_ids, id);
}
yyjson_mut_obj_add_val(doc, event_obj, "subTopicIds", subtopic_ids);
yyjson_add_optional_str(doc, event_obj, "subjectCode", event.subjectCode);
yyjson_add_optional_str(doc, event_obj, "subtitle", event.subtitle);
// Add topicIds array
yyjson_mut_val *topic_ids = yyjson_mut_arr(doc);
for (uint64_t id : event.topicIds) {
yyjson_mut_arr_add_uint(doc, topic_ids, id);
}
yyjson_mut_obj_add_val(doc, event_obj, "topicIds", topic_ids);
yyjson_mut_obj_add_val(doc, events_obj, key.c_str(), event_obj);
}
yyjson_mut_obj_add_val(doc, root, "events", events_obj);
// Create performances array
yyjson_mut_val *performances_array = yyjson_mut_arr(doc);
for (const auto& perf : catalog.performances) {
yyjson_mut_val *perf_obj = yyjson_mut_obj(doc);
yyjson_mut_obj_add_uint(doc, perf_obj, "eventId", perf.eventId);
yyjson_mut_obj_add_uint(doc, perf_obj, "id", perf.id);
yyjson_add_optional_str(doc, perf_obj, "logo", perf.logo);
yyjson_add_optional_str(doc, perf_obj, "name", perf.name);
// Add prices array
yyjson_mut_val *prices_array = yyjson_mut_arr(doc);
for (const auto& price : perf.prices) {
yyjson_mut_val *price_obj = yyjson_mut_obj(doc);
yyjson_mut_obj_add_uint(doc, price_obj, "amount", price.amount);
yyjson_mut_obj_add_uint(doc, price_obj, "audienceSubCategoryId", price.audienceSubCategoryId);
yyjson_mut_obj_add_uint(doc, price_obj, "seatCategoryId", price.seatCategoryId);
yyjson_mut_arr_append(prices_array, price_obj);
}
yyjson_mut_obj_add_val(doc, perf_obj, "prices", prices_array);
// Add seatCategories array
yyjson_mut_val *seat_cats_array = yyjson_mut_arr(doc);
for (const auto& seatCat : perf.seatCategories) {
yyjson_mut_val *seat_cat_obj = yyjson_mut_obj(doc);
// Add areas array
yyjson_mut_val *areas_array = yyjson_mut_arr(doc);
for (const auto& area : seatCat.areas) {
yyjson_mut_val *area_obj = yyjson_mut_obj(doc);
yyjson_mut_obj_add_uint(doc, area_obj, "areaId", area.areaId);
yyjson_mut_val *block_ids = yyjson_mut_arr(doc);
for (uint64_t blockId : area.blockIds) {
yyjson_mut_arr_add_uint(doc, block_ids, blockId);
}
yyjson_mut_obj_add_val(doc, area_obj, "blockIds", block_ids);
yyjson_mut_arr_append(areas_array, area_obj);
}
yyjson_mut_obj_add_val(doc, seat_cat_obj, "areas", areas_array);
yyjson_mut_obj_add_uint(doc, seat_cat_obj, "seatCategoryId", seatCat.seatCategoryId);
yyjson_mut_arr_append(seat_cats_array, seat_cat_obj);
}
yyjson_mut_obj_add_val(doc, perf_obj, "seatCategories", seat_cats_array);
yyjson_add_optional_str(doc, perf_obj, "seatMapImage", perf.seatMapImage);
yyjson_mut_obj_add_uint(doc, perf_obj, "start", perf.start);
yyjson_mut_obj_add_str(doc, perf_obj, "venueCode", perf.venueCode.c_str());
yyjson_mut_arr_append(performances_array, perf_obj);
}
yyjson_mut_obj_add_val(doc, root, "performances", performances_array);
// Write to string
char *json_output = yyjson_mut_write(doc, 0, NULL);
std::string result(json_output);
free(json_output);
yyjson_mut_doc_free(doc);
return result;
}
#endif // YYJSON_CITM_CATALOG_DATA_H
+237 -368
View File
@@ -5,405 +5,165 @@ extern crate libc;
use libc::{c_char, size_t}; use libc::{c_char, size_t};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::{collections::HashMap, ffi::CString, ptr, slice}; use std::{collections::HashMap, ffi::CString, ptr, slice};
use serde::de::{self, Deserializer};
/******************************************************/
/******************************************************/
/**
* Warning: the C++ code may not generate the same JSON.
*/
/******************************************************/
/******************************************************/
// This has no equivalent in C++: //==============================================================================
#[derive(Serialize, Deserialize)] // Twitter Benchmark Structures
pub struct Metadata { // These match the C++ TwitterData structures exactly
result_type: String, //==============================================================================
iso_language_code: String,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct User { pub struct User {
id: i64, id: u64,
id_str: String,
name: String, name: String,
screen_name: String, screen_name: String,
location: String, location: String,
description: String, description: String,
// C++ does not have those:
// url: Option<String>,
//protected: bool,
//listed_count: i64,
//created_at: String,
//favourites_count: i64,
//utc_offset: Option<i64>,
//time_zone: Option<String>,
//geo_enabled: bool,
verified: bool, verified: bool,
followers_count: i64, followers_count: u64,
friends_count: i64, friends_count: u64,
statuses_count: i64, statuses_count: u64,
// C++ does not have those:
//lang: String,
//profile_background_color: String,
//profile_background_image_url: String,
//profile_background_image_url_https: String,
//profile_background_tile: bool,
//profile_image_url: String,
//profile_image_url_https: String,
//profile_banner_url: Option<String>,
//profile_link_color: String,
//profile_sidebar_border_color: String,
//profile_sidebar_fill_color: String,
//profile_text_color: String,
//profile_use_background_image: bool,
//default_profile: bool,
//default_profile_image: bool,
//following: bool,
//follow_request_sent: bool,
//notifications: bool,
}
#[derive(Serialize, Deserialize)]
pub struct Hashtag {
text: String,
// C++ has those but D. Lemire does not know what they are, they don't appear in the JSON:
// int64_t indices_start;
// int64_t indices_end;
}
#[derive(Serialize, Deserialize)]
pub struct Url {
url: String,
expanded_url: String,
display_url: String,
// C++ has those but D. Lemire does not know what they are, they don't appear in the JSON:
// int64_t indices_start;
// int64_t indices_end;
}
#[derive(Serialize, Deserialize)]
pub struct UserMention {
id: i64,
name: String,
screen_name: String,
// Not in the C++ equivalent:
//id_str: String,
//indices: Vec<i64>,
// C++ has those but D. Lemire does not know what they are, they don't appear in the JSON:
// int64_t indices_start;
// int64_t indices_end;
}
#[derive(Serialize, Deserialize)]
pub struct Entities {
hashtags: Vec<Hashtag>,
urls: Vec<Url>,
user_mentions: Vec<UserMention>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Status { pub struct Status {
created_at: String, created_at: String,
id: i64, id: u64,
text: String, text: String,
user: User, user: User,
entities: Entities, retweet_count: u64,
retweet_count: i64, favorite_count: u64,
favorite_count: i64,
favorited: bool,
retweeted: bool,
// None of these are in the C++ equivalent:
/*
metadata: Metadata,
id_str: String,
source: String,
truncated: bool,
in_reply_to_status_id: Option<i64>,
in_reply_to_status_id_str: Option<String>,
in_reply_to_user_id: Option<i64>,
in_reply_to_user_id_str: Option<String>,
in_reply_to_screen_name: Option<String>,
geo: Option<String>,
coordinates: Option<String>,
place: Option<String>,
contributors: Option<String>,
lang: String,
*/
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct TwitterData { pub struct TwitterData {
statuses: Vec<Status>, statuses: Vec<Status>,
} }
static mut TWITTER_DATA: *mut TwitterData = std::ptr::null_mut();
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn twitter_from_str(raw_input: *const c_char, raw_input_length: size_t) -> *mut TwitterData { pub unsafe extern "C" fn twitter_from_str(raw_input: *const c_char, raw_input_length: size_t) -> *mut TwitterData {
let input = std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length)); let input = std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length));
match serde_json::from_str(&input) { match serde_json::from_str(&input) {
Ok(result) => Box::into_raw(Box::new(result)), Ok(result) => Box::into_raw(Box::new(result)),
Err(_) => std::ptr::null_mut(), Err(_) => std::ptr::null_mut(),
} }
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn str_from_twitter(raw: *mut TwitterData) -> *const c_char { pub unsafe extern "C" fn set_twitter_data(raw: *mut TwitterData) {
let twitter_thing = { &*raw }; TWITTER_DATA = raw;
let serialized = serde_json::to_string(&twitter_thing).unwrap();
return std::ffi::CString::new(serialized.as_str()).unwrap().into_raw()
} }
#[no_mangle]
pub unsafe extern "C" fn serialize_twitter_to_string() -> usize {
if TWITTER_DATA.is_null() {
return 0;
}
let data = &*TWITTER_DATA;
serde_json::to_string(data).unwrap().len()
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn free_twitter(raw: *mut TwitterData) { pub unsafe extern "C" fn free_twitter(raw: *mut TwitterData) {
if raw.is_null() { if raw.is_null() {
return; return;
} }
drop(Box::from_raw(raw))
drop(Box::from_raw(raw))
} }
#[no_mangle] #[no_mangle]
pub unsafe extern fn free_string(ptr: *const c_char) { pub unsafe extern fn free_string(ptr: *const c_char) {
let _ = std::ffi::CString::from_raw(ptr as *mut _); let _ = std::ffi::CString::from_raw(ptr as *mut _);
} }
// Functions associated with the CitmCatalog benchmark //==============================================================================
// CITM Catalog Benchmark Structures
// These match the C++ CitmCatalog structures EXACTLY for fair comparison
//==============================================================================
#[derive(Serialize, Deserialize)] /// Matches C++ CITMPrice struct exactly
pub struct Area { #[derive(Serialize, Deserialize, Debug, Clone)]
pub id: i64, pub struct CITMPrice {
pub name: Option<String>, // Changed to Option pub amount: u64,
pub parent: i64, #[serde(rename = "audienceSubCategoryId")]
#[serde(rename = "childAreas")] pub audience_sub_category_id: u64,
pub child_areas: Vec<i64>, #[serde(rename = "seatCategoryId")]
pub seat_category_id: u64,
} }
#[derive(Serialize, Deserialize)] /// Matches C++ CITMArea struct exactly
pub struct AudienceSubCategory { #[derive(Serialize, Deserialize, Debug, Clone)]
pub id: i64, pub struct CITMArea {
pub name: Option<String>, // Changed to Option #[serde(rename = "areaId")]
pub parent: i64, pub area_id: u64,
#[serde(rename = "blockIds")]
pub block_ids: Vec<u64>,
} }
#[derive(Serialize, Deserialize, Debug)] /// Matches C++ CITMSeatCategory struct exactly
pub struct Event { #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(default)] pub struct CITMSeatCategory {
pub description: Option<String>, pub areas: Vec<CITMArea>,
pub id: i64, #[serde(rename = "seatCategoryId")]
pub seat_category_id: u64,
}
/// Matches C++ CITMPerformance struct exactly
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CITMPerformance {
pub id: u64,
#[serde(rename = "eventId")]
pub event_id: u64,
#[serde(default)] #[serde(default)]
pub logo: Option<String>, pub logo: Option<String>,
#[serde(default)] #[serde(default)]
pub name: Option<String>, pub name: Option<String>,
pub prices: Vec<CITMPrice>,
#[serde(rename = "seatCategories")]
pub seat_categories: Vec<CITMSeatCategory>,
#[serde(default)] #[serde(default)]
pub subTopicIds: Vec<i64>, #[serde(rename = "seatMapImage")]
pub seat_map_image: Option<String>,
pub start: u64,
#[serde(rename = "venueCode")]
pub venue_code: String,
}
/// Matches C++ CITMEvent struct exactly
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CITMEvent {
pub id: u64,
#[serde(default)] #[serde(default)]
pub subjectCode: Option<String>, pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub logo: Option<String>,
#[serde(default)]
#[serde(rename = "subTopicIds")]
pub sub_topic_ids: Vec<u64>,
#[serde(default)]
#[serde(rename = "subjectCode")]
pub subject_code: Option<String>,
#[serde(default)] #[serde(default)]
pub subtitle: Option<String>, pub subtitle: Option<String>,
#[serde(default)] #[serde(default)]
pub topicIds: Vec<i64>, #[serde(rename = "topicIds")]
// Add a catch-all for any other fields pub topic_ids: Vec<u64>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Performance {
#[serde(default)]
pub id: i64,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub event: i64,
// This is the key fix - accept any JSON value type for timestamps
// This allows both string dates and integer timestamps (line 3511)
#[serde(default)]
pub start: serde_json::Value,
#[serde(rename = "venueCode")]
pub venue_code: String,
// Add a catch-all for any other fields
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize)]
pub struct SeatCategory {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub areas: Vec<i64>,
}
#[derive(Serialize, Deserialize)]
pub struct SubTopic {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub parent: i64,
}
#[derive(Serialize, Deserialize)]
pub struct Topic {
pub id: i64,
pub name: Option<String>, // Changed to Option
}
#[derive(Serialize, Deserialize)]
pub struct Venue {
pub id: i64,
pub name: Option<String>, // Changed to Option
pub address: i64,
}
// Custom deserializers
fn deserialize_string_to_area<'de, D>(deserializer: D) -> Result<HashMap<String, Area>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), Area {
id: id_num,
name: Some(name),
parent: 0,
child_areas: Vec::new(),
});
}
Ok(result)
}
fn deserialize_string_to_audience_subcategory<'de, D>(deserializer: D) -> Result<HashMap<String, AudienceSubCategory>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), AudienceSubCategory {
id: id_num,
name: Some(name),
parent: 0,
});
}
Ok(result)
}
fn deserialize_string_to_seat_category<'de, D>(deserializer: D) -> Result<HashMap<String, SeatCategory>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), SeatCategory {
id: id_num,
name: Some(name),
areas: Vec::new(),
});
}
Ok(result)
}
fn deserialize_string_to_subtopic<'de, D>(deserializer: D) -> Result<HashMap<String, SubTopic>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), SubTopic {
id: id_num,
name: Some(name),
parent: 0,
});
}
Ok(result)
}
fn deserialize_string_to_topic<'de, D>(deserializer: D) -> Result<HashMap<String, Topic>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
let id_num = id.parse::<i64>().unwrap_or(0);
result.insert(id.clone(), Topic {
id: id_num,
name: Some(name),
});
}
Ok(result)
}
fn deserialize_string_to_venue<'de, D>(deserializer: D) -> Result<HashMap<String, Venue>, D::Error>
where D: Deserializer<'de> {
let string_map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
let mut result = HashMap::new();
for (id, name) in string_map {
result.insert(id.clone(), Venue {
id: 0,
name: Some(name),
address: 0,
});
}
Ok(result)
} }
/// Matches C++ CitmCatalog struct exactly - ONLY events and performances
/// This is the key fix: we serialize only what C++ serializes
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct CitmCatalog { pub struct CitmCatalog {
#[serde(rename = "areaNames")] pub events: HashMap<String, CITMEvent>,
pub area_names: HashMap<String, String>, pub performances: Vec<CITMPerformance>,
#[serde(rename = "audienceSubCategoryNames")]
pub audience_subcategory_names: HashMap<String, String>,
#[serde(default)]
#[serde(rename = "blockNames")]
pub block_names: HashMap<String, String>,
pub events: HashMap<String, Event>,
#[serde(default)]
pub performances: Vec<Performance>,
#[serde(rename = "seatCategoryNames")]
pub seat_category_names: HashMap<String, String>,
#[serde(rename = "subTopicNames")]
pub subtopic_names: HashMap<String, String>,
#[serde(default)]
#[serde(rename = "subjectNames")]
pub subject_names: HashMap<String, String>,
#[serde(rename = "topicNames")]
pub topic_names: HashMap<String, String>,
#[serde(rename = "topicSubTopics")]
pub topic_subtopics: HashMap<String, Vec<i64>>,
#[serde(rename = "venueNames")]
pub venue_names: HashMap<String, String>,
// Catch-all for other fields
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
} }
static mut CITM_DATA: *mut CitmCatalog = std::ptr::null_mut();
/// Creates a CitmCatalog from a JSON string (UTF-8 encoded). /// Creates a CitmCatalog from a JSON string (UTF-8 encoded).
/// Only extracts events and performances to match C++ behavior.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn citm_from_str( pub unsafe extern "C" fn citm_from_str(
raw_input: *const c_char, raw_input: *const c_char,
@@ -414,7 +174,6 @@ pub unsafe extern "C" fn citm_from_str(
return ptr::null_mut(); return ptr::null_mut();
} }
// Convert the raw pointer + length into a Rust slice
let bytes = slice::from_raw_parts(raw_input as *const u8, raw_input_length); let bytes = slice::from_raw_parts(raw_input as *const u8, raw_input_length);
let input_str = match std::str::from_utf8(bytes) { let input_str = match std::str::from_utf8(bytes) {
Ok(s) => s, Ok(s) => s,
@@ -424,12 +183,23 @@ pub unsafe extern "C" fn citm_from_str(
} }
}; };
// Try deserializing the input string into CitmCatalog // Parse the full JSON to extract only events and performances
match serde_json::from_str::<CitmCatalog>(input_str) { match serde_json::from_str::<serde_json::Value>(input_str) {
Ok(catalog) => Box::into_raw(Box::new(catalog)), Ok(full_json) => {
// Extract only the fields we need (matching C++ behavior)
let events: HashMap<String, CITMEvent> = full_json.get("events")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
let performances: Vec<CITMPerformance> = full_json.get("performances")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
let catalog = CitmCatalog { events, performances };
Box::into_raw(Box::new(catalog))
},
Err(e) => { Err(e) => {
eprintln!("Error deserializing JSON: {}", e); eprintln!("Error deserializing JSON: {}", e);
eprintln!("JSON snippet (first 200 chars): {:.200}...", input_str);
ptr::null_mut() ptr::null_mut()
} }
} }
@@ -437,30 +207,17 @@ pub unsafe extern "C" fn citm_from_str(
/// Serializes a CitmCatalog into a JSON string (UTF-8). /// Serializes a CitmCatalog into a JSON string (UTF-8).
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn str_from_citm(raw_catalog: *mut CitmCatalog) -> *mut c_char { pub unsafe extern "C" fn set_citm_data(raw: *mut CitmCatalog) {
if raw_catalog.is_null() { CITM_DATA = raw;
eprintln!("Error: Catalog pointer is null"); }
return ptr::null_mut();
}
// Fix: Actually serialize the catalog #[no_mangle]
let catalog = &*raw_catalog; pub unsafe extern "C" fn serialize_citm_to_string() -> usize {
if CITM_DATA.is_null() {
match serde_json::to_string(catalog) { return 0;
Ok(serialized) => {
match CString::new(serialized) {
Ok(cstr) => cstr.into_raw(),
Err(e) => {
eprintln!("Error creating CString: {}", e);
ptr::null_mut()
}
}
},
Err(e) => {
eprintln!("Error serializing catalog to JSON: {}", e);
ptr::null_mut()
}
} }
let data = &*CITM_DATA;
return serde_json::to_string(data).unwrap().len();
} }
/// Frees the CitmCatalog pointer. /// Frees the CitmCatalog pointer.
@@ -475,8 +232,120 @@ pub unsafe extern "C" fn free_citm(raw_catalog: *mut CitmCatalog) {
pub extern "C" fn free_str(ptr: *mut c_char) { pub extern "C" fn free_str(ptr: *mut c_char) {
if !ptr.is_null() { if !ptr.is_null() {
unsafe { unsafe {
// Convert back into a CString, which automatically frees the memory
let _ = CString::from_raw(ptr); let _ = CString::from_raw(ptr);
} }
} }
} }
//==============================================================================
// FFI Overhead Measurement Functions
// These allow measuring the actual FFI overhead vs pure Rust serialization
//==============================================================================
/// Result structure for FFI overhead measurement
#[repr(C)]
pub struct FfiOverheadResult {
/// Time in nanoseconds for pure serde_json::to_string() (no FFI overhead)
pub pure_serde_ns: u64,
/// Time in nanoseconds for serde + CString conversion
pub serde_plus_cstring_ns: u64,
/// Number of iterations performed
pub iterations: u64,
/// Output size in bytes (for verification)
pub output_size: u64,
}
/// Prevents compiler from optimizing away the value
/// Works on stable Rust (unlike std::hint::black_box which is unstable)
#[inline(never)]
fn black_box<T>(dummy: T) -> T {
unsafe {
let ret = std::ptr::read_volatile(&dummy);
std::mem::forget(dummy);
ret
}
}
/// Measures FFI overhead for Twitter serialization.
/// Performs `iterations` serializations entirely in Rust and returns timing data.
/// This allows comparing against per-call FFI overhead.
#[no_mangle]
pub unsafe extern "C" fn measure_twitter_ffi_overhead(
raw: *mut TwitterData,
iterations: u64
) -> FfiOverheadResult {
use std::time::Instant;
let twitter_data = &*raw;
let output_size: u64;
// Warm-up run
let warmup = serde_json::to_string(&twitter_data).unwrap();
output_size = warmup.len() as u64;
// Measure pure serde_json::to_string() - no CString conversion
let start_pure = Instant::now();
for _ in 0..iterations {
let serialized = serde_json::to_string(&twitter_data).unwrap();
// Prevent optimization from eliminating the work
black_box(&serialized);
}
let pure_serde_ns = start_pure.elapsed().as_nanos() as u64;
// Measure serde + CString conversion (but not FFI return)
let start_cstring = Instant::now();
for _ in 0..iterations {
let serialized = serde_json::to_string(&twitter_data).unwrap();
let cstring = CString::new(serialized).unwrap();
// Prevent optimization from eliminating the work
black_box(&cstring);
}
let serde_plus_cstring_ns = start_cstring.elapsed().as_nanos() as u64;
FfiOverheadResult {
pure_serde_ns,
serde_plus_cstring_ns,
iterations,
output_size,
}
}
/// Measures FFI overhead for CITM serialization.
#[no_mangle]
pub unsafe extern "C" fn measure_citm_ffi_overhead(
raw: *mut CitmCatalog,
iterations: u64
) -> FfiOverheadResult {
use std::time::Instant;
let catalog = &*raw;
let output_size: u64;
// Warm-up run
let warmup = serde_json::to_string(&catalog).unwrap();
output_size = warmup.len() as u64;
// Measure pure serde_json::to_string() - no CString conversion
let start_pure = Instant::now();
for _ in 0..iterations {
let serialized = serde_json::to_string(&catalog).unwrap();
black_box(&serialized);
}
let pure_serde_ns = start_pure.elapsed().as_nanos() as u64;
// Measure serde + CString conversion
let start_cstring = Instant::now();
for _ in 0..iterations {
let serialized = serde_json::to_string(&catalog).unwrap();
let cstring = CString::new(serialized).unwrap();
black_box(&cstring);
}
let serde_plus_cstring_ns = start_cstring.elapsed().as_nanos() as u64;
FfiOverheadResult {
pure_serde_ns,
serde_plus_cstring_ns,
iterations,
output_size,
}
}
@@ -4,6 +4,7 @@
/* Generated with cbindgen:0.28.0 */ /* Generated with cbindgen:0.28.0 */
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
/* Note: FfiOverheadResult and measurement functions added manually */
#include <cstdarg> #include <cstdarg>
#include <cstdint> #include <cstdint>
@@ -17,11 +18,25 @@ struct CitmCatalog;
struct TwitterData; struct TwitterData;
/// Result structure for FFI overhead measurement
struct FfiOverheadResult {
/// Time in nanoseconds for pure serde_json::to_string() (no FFI overhead)
uint64_t pure_serde_ns;
/// Time in nanoseconds for serde + CString conversion
uint64_t serde_plus_cstring_ns;
/// Number of iterations performed
uint64_t iterations;
/// Output size in bytes (for verification)
uint64_t output_size;
};
extern "C" { extern "C" {
TwitterData *twitter_from_str(const char *raw_input, size_t raw_input_length); TwitterData *twitter_from_str(const char *raw_input, size_t raw_input_length);
const char *str_from_twitter(TwitterData *raw); void set_twitter_data(TwitterData *raw);
size_t serialize_twitter_to_string();
void free_twitter(TwitterData *raw); void free_twitter(TwitterData *raw);
@@ -30,14 +45,22 @@ void free_string(const char *ptr);
/// Creates a CitmCatalog from a JSON string (UTF-8 encoded). /// Creates a CitmCatalog from a JSON string (UTF-8 encoded).
CitmCatalog *citm_from_str(const char *raw_input, uintptr_t raw_input_length); CitmCatalog *citm_from_str(const char *raw_input, uintptr_t raw_input_length);
/// Serializes a CitmCatalog into a JSON string (UTF-8). void set_citm_data(CitmCatalog *raw);
char *str_from_citm(CitmCatalog *raw_catalog);
size_t serialize_citm_to_string();
/// Frees the CitmCatalog pointer. /// Frees the CitmCatalog pointer.
void free_citm(CitmCatalog *raw_catalog); void free_citm(CitmCatalog *raw_catalog);
void free_str(char *ptr); void free_str(char *ptr);
/// Measures FFI overhead for Twitter serialization.
/// Performs `iterations` serializations entirely in Rust and returns timing data.
FfiOverheadResult measure_twitter_ffi_overhead(TwitterData *raw, uint64_t iterations);
/// Measures FFI overhead for CITM serialization.
FfiOverheadResult measure_citm_ffi_overhead(CitmCatalog *raw, uint64_t iterations);
} // extern "C" } // extern "C"
} // namespace serde_benchmark } // namespace serde_benchmark
@@ -0,0 +1,31 @@
use std::fs;
// Include the lib.rs content directly
include!("../lib.rs");
fn main() {
// Read the Twitter JSON file
let json_str = fs::read_to_string("/Users/random_person/Desktop/simdjson/build/jsonexamples/twitter.json")
.expect("Failed to read file");
// Parse it
let data: TwitterData = serde_json::from_str(&json_str)
.expect("Failed to parse JSON");
// Serialize it back
let output = serde_json::to_string(&data)
.expect("Failed to serialize");
// Write to file for comparison
fs::write("rust_output.json", &output)
.expect("Failed to write output");
println!("Output size: {} bytes", output.len());
println!("Written to rust_output.json");
// Also write pretty version for easier inspection
let pretty = serde_json::to_string_pretty(&data)
.expect("Failed to serialize pretty");
fs::write("rust_output_pretty.json", &pretty)
.expect("Failed to write pretty output");
}
@@ -0,0 +1,36 @@
use std::fs;
// Import from the parent lib.rs
include!("../lib.rs");
fn main() {
// Read the Twitter JSON file
let json_str = fs::read_to_string("/Users/random_person/Desktop/simdjson/build/jsonexamples/twitter.json")
.expect("Failed to read file");
// Parse it
let data: TwitterData = serde_json::from_str(&json_str)
.expect("Failed to parse JSON");
// Serialize it back (compact)
let output = serde_json::to_vec(&data)
.expect("Failed to serialize");
let output_str = String::from_utf8(output.clone()).unwrap();
// Write to file for comparison
fs::write("rust_output_test.json", &output)
.expect("Failed to write output");
println!("Output size: {} bytes", output.len());
// Count statuses
println!("Number of statuses: {}", data.statuses.len());
// Check what fields are in the first status
if let Some(first) = data.statuses.first() {
// Let's serialize just the first status to see what fields are included
let first_json = serde_json::to_string_pretty(first).unwrap();
println!("First status (pretty):\n{}", first_json);
}
}
@@ -1,14 +1,32 @@
# Add executable targets # Add executable targets
add_executable(benchmark_serialization_twitter benchmark_serialization_twitter.cpp) add_executable(benchmark_serialization_twitter benchmark_serialization_twitter.cpp)
add_executable(benchmark_parsing_twitter benchmark_parsing_twitter.cpp)
if(TARGET serde-benchmark) if(TARGET serde-benchmark)
message(STATUS "serde-benchmark target was created. Linking benchmarks and serde-benchmark.") message(STATUS "serde-benchmark target was created. Linking benchmarks and serde-benchmark.")
target_link_libraries(benchmark_serialization_twitter PRIVATE serde-benchmark) target_link_libraries(benchmark_serialization_twitter PRIVATE serde-benchmark)
target_link_libraries(benchmark_parsing_twitter PRIVATE serde-benchmark)
target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_RUST_VERSION="${Rust_VERSION}") target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_RUST_VERSION="${Rust_VERSION}")
target_compile_definitions(benchmark_parsing_twitter PRIVATE SIMDJSON_RUST_VERSION="${Rust_VERSION}")
endif() endif()
target_link_libraries(benchmark_serialization_twitter PRIVATE simdjson::simdjson nlohmann_json) target_link_libraries(benchmark_serialization_twitter PRIVATE simdjson::simdjson nlohmann_json)
target_link_libraries(benchmark_serialization_twitter PRIVATE reflectcpp) target_link_libraries(benchmark_serialization_twitter PRIVATE reflectcpp)
target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_BENCH_CPP_REFLECT=1) target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_BENCH_CPP_REFLECT=1)
target_compile_definitions(benchmark_serialization_twitter PRIVATE JSON_FILE="${EXAMPLE_JSON}") target_link_libraries(benchmark_parsing_twitter PRIVATE simdjson::simdjson nlohmann_json)
if(TARGET rapidjson)
target_link_libraries(benchmark_parsing_twitter PRIVATE rapidjson)
target_compile_definitions(benchmark_parsing_twitter PRIVATE SIMDJSON_COMPETITION_RAPIDJSON)
endif()
if(TARGET yyjson)
target_link_libraries(benchmark_parsing_twitter PRIVATE yyjson)
target_compile_definitions(benchmark_parsing_twitter PRIVATE SIMDJSON_COMPETITION_YYJSON)
target_link_libraries(benchmark_serialization_twitter PRIVATE yyjson)
target_compile_definitions(benchmark_serialization_twitter PRIVATE SIMDJSON_COMPETITION_YYJSON)
endif()
target_compile_definitions(benchmark_serialization_twitter PRIVATE JSON_FILE="${EXAMPLE_JSON}")
target_compile_definitions(benchmark_parsing_twitter PRIVATE JSON_FILE="${EXAMPLE_JSON}")
@@ -0,0 +1,232 @@
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <format>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <simdjson.h>
#include <string>
#include "twitter_data.h"
#include "nlohmann_twitter_data.h"
#include "../benchmark_utils/benchmark_helper.h"
#ifdef SIMDJSON_COMPETITION_RAPIDJSON
#include "rapidjson_twitter_data.h"
#endif
#ifdef SIMDJSON_COMPETITION_YYJSON
#include "yyjson_twitter_data.h"
#endif
#ifdef SIMDJSON_RUST_VERSION
#include "../serde-benchmark/serde_benchmark.h"
void bench_rust_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_rust_parsing",
bench([&json_str, &result]() {
serde_benchmark::TwitterData *td = serde_benchmark::twitter_from_str(json_str.c_str(), json_str.size());
result = (td != nullptr);
if (td) {
serde_benchmark::free_twitter(td);
}
if (!result) {
printf("parse error\n");
}
}));
}
#endif
// OPTIMIZED VERSION: Reuses parser across iterations
template <class T>
void bench_simdjson_static_reflection_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
// Pre-allocate padded buffer outside the benchmark loop
simdjson::padded_string padded = simdjson::padded_string(json_str);
// CRITICAL: Create parser OUTSIDE the loop for reuse
simdjson::ondemand::parser parser;
volatile bool result = true;
pretty_print(1, input_volume, "bench_simdjson_static_reflection_parsing",
bench([&padded, &result, &parser]() {
// Reuse the same parser instance
simdjson::ondemand::document doc;
if(parser.iterate(padded).get(doc)) {
result = false;
return;
}
T my_struct;
if(doc.get<T>().get(my_struct)) {
result = false;
}
if (!result) {
printf("parse error\n");
}
}));
}
#if SIMDJSON_STATIC_REFLECTION
template <class T>
void bench_simdjson_from_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
// Pre-allocate padded buffer outside the benchmark loop
simdjson::padded_string padded = simdjson::padded_string(json_str);
volatile bool result = true;
pretty_print(1, input_volume, "bench_simdjson_from_parsing",
bench([&padded, &result]() {
T my_struct;
auto err = simdjson::from(padded).get(my_struct);
if (err) {
result = false;
printf("parse error: %s\n", simdjson::error_message(err));
return;
}
}));
}
#endif
void bench_nlohmann_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_nlohmann_parsing",
bench([&json_str, &result]() {
try {
TwitterData data = nlohmann_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
#ifdef SIMDJSON_COMPETITION_RAPIDJSON
void bench_rapidjson_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_rapidjson_parsing",
bench([&json_str, &result]() {
try {
TwitterData data = rapidjson_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
#endif
#ifdef SIMDJSON_COMPETITION_YYJSON
void bench_yyjson_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_yyjson_parsing",
bench([&json_str, &result]() {
try {
TwitterData data = yyjson_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
#endif
std::string read_file(std::string filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 4096;
auto stream = std::ifstream(filename.c_str());
stream.exceptions(std::ios_base::badbit);
std::string out;
std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount()));
}
out.append(buf, 0, size_t(stream.gcount()));
return out;
}
// Function to check if benchmark name matches any of the comma-separated filters
bool matches_filter(const std::string& benchmark_name, const std::string& filter) {
if (filter.empty()) return true;
// Split filter by comma
size_t start = 0;
size_t end = filter.find(',');
while (end != std::string::npos) {
std::string token = filter.substr(start, end - start);
if (benchmark_name.find(token) != std::string::npos) {
return true;
}
start = end + 1;
end = filter.find(',', start);
}
// Check last token
std::string token = filter.substr(start);
return benchmark_name.find(token) != std::string::npos;
}
int main(int argc, char* argv[]) {
std::string filter;
// Parse command-line arguments
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--filter") == 0) {
if (i + 1 < argc) {
filter = argv[++i];
} else {
std::cerr << "Error: -f/--filter requires an argument" << std::endl;
return EXIT_FAILURE;
}
}
}
// Load the JSON data
std::string json_str = read_file(JSON_FILE);
// Benchmarking the parsing
if (matches_filter("nlohmann", filter)) {
bench_nlohmann_parsing(json_str);
}
#ifdef SIMDJSON_COMPETITION_RAPIDJSON
if (matches_filter("rapidjson", filter)) {
bench_rapidjson_parsing(json_str);
}
#endif
#ifdef SIMDJSON_COMPETITION_YYJSON
if (matches_filter("yyjson", filter)) {
bench_yyjson_parsing(json_str);
}
#endif
if (matches_filter("simdjson_static_reflection", filter)) {
bench_simdjson_static_reflection_parsing<TwitterData>(json_str);
}
#if SIMDJSON_STATIC_REFLECTION
if (matches_filter("simdjson_from", filter)) {
bench_simdjson_from_parsing<TwitterData>(json_str);
}
#endif
#ifdef SIMDJSON_RUST_VERSION
if (matches_filter("rust", filter)) {
printf("# Note: Rust/Serde parsing test\n");
bench_rust_parsing(json_str);
}
#endif
return EXIT_SUCCESS;
}
@@ -1,4 +1,5 @@
#include <cassert> #include <cassert>
#include <chrono>
#include <cstdlib> #include <cstdlib>
#include <ctime> #include <ctime>
#include <format> #include <format>
@@ -10,6 +11,9 @@
#include "twitter_data.h" #include "twitter_data.h"
#include "nlohmann_twitter_data.h" #include "nlohmann_twitter_data.h"
#include "../benchmark_utils/benchmark_helper.h" #include "../benchmark_utils/benchmark_helper.h"
#ifdef SIMDJSON_COMPETITION_YYJSON
#include "yyjson_twitter_data.h"
#endif
#if SIMDJSON_BENCH_CPP_REFLECT #if SIMDJSON_BENCH_CPP_REFLECT
#include <rfl.hpp> #include <rfl.hpp>
#include <rfl/json.hpp> #include <rfl/json.hpp>
@@ -35,19 +39,49 @@ void bench_reflect_cpp(TwitterData &data) {
void bench_rust(serde_benchmark::TwitterData *data) { void bench_rust(serde_benchmark::TwitterData *data) {
const char * output = serde_benchmark::str_from_twitter(data); serde_benchmark::set_twitter_data(data);
size_t output_volume = strlen(output); size_t output_volume = serde_benchmark::serialize_twitter_to_string();
printf("# output volume: %zu bytes\n", output_volume); printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0; volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_rust", pretty_print(1, output_volume, "bench_rust",
bench([&data, &measured_volume, &output_volume]() { bench([&measured_volume, &output_volume]() {
const char * output = serde_benchmark::str_from_twitter(data); measured_volume = serde_benchmark::serialize_twitter_to_string();
serde_benchmark::free_string(output);
})); }));
} }
#endif #endif
// Fair allocation variant: allocates fresh buffer each iteration (matches other libraries)
template <class T> void bench_simdjson_static_reflection(T &data) { template <class T> void bench_simdjson_static_reflection(T &data) {
// First run to determine expected size
simdjson::builder::string_builder sb_init;
simdjson::builder::append(sb_init, data);
std::string_view p_init;
if(sb_init.view().get(p_init)) {
std::cerr << "Error!" << std::endl;
}
size_t output_volume = p_init.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_static_reflection",
bench([&data, &measured_volume, &output_volume]() {
// Fresh allocation each iteration - fair comparison
simdjson::builder::string_builder sb;
simdjson::builder::append(sb, data);
std::string_view p;
if(sb.view().get(p)) {
std::cerr << "Error!" << std::endl;
}
measured_volume = sb.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
// Optimized variant: reuses buffer across iterations (shows API potential)
template <class T> void bench_simdjson_static_reflection_reuse(T &data) {
simdjson::builder::string_builder sb; simdjson::builder::string_builder sb;
simdjson::builder::append(sb, data); simdjson::builder::append(sb, data);
std::string_view p; std::string_view p;
@@ -59,7 +93,7 @@ template <class T> void bench_simdjson_static_reflection(T &data) {
printf("# output volume: %zu bytes\n", output_volume); printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0; volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_static_reflection", pretty_print(sizeof(data), output_volume, "bench_simdjson_reuse_buffer",
bench([&data, &measured_volume, &output_volume, &sb]() { bench([&data, &measured_volume, &output_volume, &sb]() {
sb.clear(); sb.clear();
simdjson::builder::append(sb, data); simdjson::builder::append(sb, data);
@@ -74,6 +108,63 @@ template <class T> void bench_simdjson_static_reflection(T &data) {
})); }));
} }
#if SIMDJSON_STATIC_REFLECTION
// Fair allocation variant: allocates fresh string each iteration
template <class T> void bench_simdjson_to(T &data) {
// First run to determine size
std::string output_init;
if (simdjson::error_code err = simdjson::builder::to_json(data, output_init); err) {
std::cerr << "Error in to_json initialization!" << simdjson::error_message(err) << std::endl;
return;
}
size_t output_volume = output_init.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_to",
bench([&data, &measured_volume, &output_volume]() {
// Fresh allocation each iteration - fair comparison
std::string output;
if (simdjson::error_code err = simdjson::builder::to_json(data, output); err) {
std::cerr << "Error in to_json!" << simdjson::error_message(err) << std::endl;
return;
}
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
// Optimized variant: reuses pre-allocated string
template <class T> void bench_simdjson_to_reuse(T &data) {
std::string output;
if (simdjson::error_code err = simdjson::builder::to_json(data, output); err) {
std::cerr << "Error in to_json initialization!" << simdjson::error_message(err) << std::endl;
return;
}
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
// Pre-allocate string with sufficient capacity to avoid reallocation
output.reserve(output_volume * 2);
volatile size_t measured_volume = 0;
pretty_print(sizeof(data), output_volume, "bench_simdjson_to_reuse",
bench([&data, &measured_volume, &output_volume, &output]() {
// Reuse the pre-allocated string - avoids allocation
if (simdjson::error_code err = simdjson::builder::to_json(data, output); err) {
std::cerr << "Error in to_json!" << simdjson::error_message(err) << std::endl;
return;
}
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
#endif
void bench_nlohmann(TwitterData &data) { void bench_nlohmann(TwitterData &data) {
std::string output = nlohmann_serialize(data); std::string output = nlohmann_serialize(data);
size_t output_volume = output.size(); size_t output_volume = output.size();
@@ -90,28 +181,61 @@ void bench_nlohmann(TwitterData &data) {
})); }));
} }
#ifdef SIMDJSON_COMPETITION_YYJSON
void bench_yyjson(TwitterData &data) {
std::string output = yyjson_serialize(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_yyjson",
bench([&data, &measured_volume, &output_volume]() {
std::string output = yyjson_serialize(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
#endif
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
((std::string *)userp)->append((char *)contents, size * nmemb); ((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb; return size * nmemb;
} }
std::string read_file(std::string filename) { simdjson::padded_string read_file(std::string filename) {
printf("# Reading file %s\n", filename.c_str()); printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 4096; constexpr size_t read_size = 4096;
auto stream = std::ifstream(filename.c_str()); auto stream = std::ifstream(filename.c_str());
stream.exceptions(std::ios_base::badbit); stream.exceptions(std::ios_base::badbit);
std::string out; simdjson::padded_string_builder builder;
std::string buf(read_size, '\0'); std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) { while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount())); builder.append(buf.data(), size_t(stream.gcount()));
} }
out.append(buf, 0, size_t(stream.gcount())); builder.append(buf.data(), size_t(stream.gcount()));
return out; return builder.convert();
} }
// Function to check if benchmark name contains filter substring // Function to check if benchmark name matches any of the comma-separated filters
bool matches_filter(const std::string& benchmark_name, const std::string& filter) { bool matches_filter(const std::string& benchmark_name, const std::string& filter) {
return filter.empty() || benchmark_name.find(filter) != std::string::npos; if (filter.empty()) return true;
// Split filter by comma
size_t start = 0;
size_t end = filter.find(',');
while (end != std::string::npos) {
std::string token = filter.substr(start, end - start);
if (benchmark_name.find(token) != std::string::npos) {
return true;
}
start = end + 1;
end = filter.find(',', start);
}
// Check last token
std::string token = filter.substr(start);
return benchmark_name.find(token) != std::string::npos;
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@@ -129,12 +253,12 @@ int main(int argc, char* argv[]) {
} }
} }
// Testing correctness of round-trip (serialization + deserialization) // Testing correctness of round-trip (serialization + deserialization)
std::string json_str = read_file(JSON_FILE); simdjson::padded_string json_str = read_file(JSON_FILE);
// Loading up the data into a structure. // Loading up the data into a structure.
simdjson::ondemand::parser parser; simdjson::ondemand::parser parser;
simdjson::ondemand::document doc; simdjson::ondemand::document doc;
if(parser.iterate(simdjson::pad(json_str)).get(doc)) { if(parser.iterate(json_str).get(doc)) {
std::cerr << "Error loading the document!" << std::endl; std::cerr << "Error loading the document!" << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@@ -145,18 +269,41 @@ int main(int argc, char* argv[]) {
} }
// Benchmarking the serialization // Benchmarking the serialization
// Note: simdjson benchmarks include both "fair" (fresh allocation) and "reuse" (buffer reuse) variants
// The "fair" variants allocate fresh memory each iteration, matching other libraries' behavior
// The "reuse" variants demonstrate the API's potential when buffer reuse is possible
if (matches_filter("nlohmann", filter)) { if (matches_filter("nlohmann", filter)) {
bench_nlohmann(my_struct); bench_nlohmann(my_struct);
} }
#ifdef SIMDJSON_COMPETITION_YYJSON
if (matches_filter("yyjson", filter)) {
bench_yyjson(my_struct);
}
#endif
if (matches_filter("simdjson_static_reflection", filter)) { if (matches_filter("simdjson_static_reflection", filter)) {
bench_simdjson_static_reflection(my_struct); bench_simdjson_static_reflection(my_struct);
} }
if (matches_filter("simdjson_reuse", filter)) {
bench_simdjson_static_reflection_reuse(my_struct);
}
#if SIMDJSON_STATIC_REFLECTION
if (matches_filter("simdjson_to", filter)) {
bench_simdjson_to(my_struct);
}
if (matches_filter("simdjson_to_reuse", filter)) {
bench_simdjson_to_reuse(my_struct);
}
#endif
#ifdef SIMDJSON_RUST_VERSION #ifdef SIMDJSON_RUST_VERSION
if (matches_filter("rust", filter)) { if (matches_filter("rust", filter)) {
printf("# WARNING: The Rust benchmark may not be directly comparable since it does not use an equivalent data structure.\n"); serde_benchmark::TwitterData * td = serde_benchmark::twitter_from_str(json_str.data(), json_str.size());
serde_benchmark::TwitterData * td = serde_benchmark::twitter_from_str(json_str.c_str(), json_str.size()); if (td == nullptr) {
bench_rust(td); printf("# Failed to parse Twitter data for Rust benchmark\n");
serde_benchmark::free_twitter(td); } else {
bench_rust(td);
serde_benchmark::free_twitter(td);
}
} }
#endif #endif
#if SIMDJSON_BENCH_CPP_REFLECT #if SIMDJSON_BENCH_CPP_REFLECT
@@ -4,6 +4,7 @@
#include "twitter_data.h" #include "twitter_data.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
// Serialization functions for nlohmann
void to_json(nlohmann::json &j, const User &u) { void to_json(nlohmann::json &j, const User &u) {
j = nlohmann::json{{"id", u.id}, j = nlohmann::json{{"id", u.id},
{"name", u.name}, {"name", u.name},
@@ -16,101 +17,54 @@ void to_json(nlohmann::json &j, const User &u) {
{"statuses_count", u.statuses_count}}; {"statuses_count", u.statuses_count}};
} }
void to_json(nlohmann::json &j, const Hashtag &h) {
j = nlohmann::json{{"text", h.text},
{"indices_start", h.indices_start},
{"indices_end", h.indices_end}};
}
void to_json(nlohmann::json &j, const Url &u) {
j = nlohmann::json{{"url", u.url},
{"expanded_url", u.expanded_url},
{"display_url", u.display_url},
{"indices_start", u.indices_start},
{"indices_end", u.indices_end}};
}
void to_json(nlohmann::json &j, const UserMention &um) {
j = nlohmann::json{{"id", um.id},
{"name", um.name},
{"screen_name", um.screen_name},
{"indices_start", um.indices_start},
{"indices_end", um.indices_end}};
}
void to_json(nlohmann::json &j, const Entities &e) {
j = nlohmann::json{{"hashtags", e.hashtags},
{"urls", e.urls},
{"user_mentions", e.user_mentions}};
}
void to_json(nlohmann::json &j, const Status &s) { void to_json(nlohmann::json &j, const Status &s) {
j = nlohmann::json{{"created_at", s.created_at}, j = nlohmann::json{{"created_at", s.created_at},
{"id", s.id}, {"id", s.id},
{"text", s.text}, {"text", s.text},
{"user", s.user}, {"user", s.user},
{"entities", s.entities},
{"retweet_count", s.retweet_count}, {"retweet_count", s.retweet_count},
{"favorite_count", s.favorite_count}, {"favorite_count", s.favorite_count}};
{"favorited", s.favorited},
{"retweeted", s.retweeted}};
}
std::string nlohmann_serialize(const std::vector<Hashtag>& v) {
nlohmann::json a = nlohmann::json::array();
for(const Hashtag & h : v) {
a.push_back(nlohmann::json{{"text", h.text},
{"indices_start", h.indices_start},
{"indices_end", h.indices_end}});
}
return a.dump();
}
std::string nlohmann_serialize(const std::vector<Url>& v) {
nlohmann::json a = nlohmann::json::array();
for(const Url & u : v) {
a.push_back(nlohmann::json{{"url", u.url},
{"expanded_url", u.expanded_url},
{"display_url", u.display_url},
{"indices_start", u.indices_start},
{"indices_end", u.indices_end}});
}
return a.dump();
}
std::string nlohmann_serialize(const std::vector<UserMention>& v) {
nlohmann::json a = nlohmann::json::array();
for(const UserMention & um : v) {
a.push_back(nlohmann::json{{"id", um.id},
{"name", um.name},
{"screen_name", um.screen_name},
{"indices_start", um.indices_start},
{"indices_end", um.indices_end}});
}
return a.dump();
}
std::string nlohmann_serialize(const std::vector<Status>& v) {
nlohmann::json a = nlohmann::json::array();
for(const Status & s : v) {
a.push_back(nlohmann::json{{"created_at", s.created_at},
{"id", s.id},
{"text", s.text},
{"user", s.user},
{"entities", s.entities},
{"retweet_count", s.retweet_count},
{"favorite_count", s.favorite_count},
{"favorited", s.favorited},
{"retweeted", s.retweeted}});
}
return a.dump();
} }
void to_json(nlohmann::json &j, const TwitterData &t) { void to_json(nlohmann::json &j, const TwitterData &t) {
j = nlohmann::json{{"statuses", t.statuses}}; j = nlohmann::json{{"statuses", t.statuses}};
} }
// Deserialization functions for nlohmann
void from_json(const nlohmann::json &j, User &u) {
j.at("id").get_to(u.id);
j.at("name").get_to(u.name);
j.at("screen_name").get_to(u.screen_name);
j.at("location").get_to(u.location);
j.at("description").get_to(u.description);
j.at("verified").get_to(u.verified);
j.at("followers_count").get_to(u.followers_count);
j.at("friends_count").get_to(u.friends_count);
j.at("statuses_count").get_to(u.statuses_count);
}
void from_json(const nlohmann::json &j, Status &s) {
j.at("created_at").get_to(s.created_at);
j.at("id").get_to(s.id);
j.at("text").get_to(s.text);
j.at("user").get_to(s.user);
j.at("retweet_count").get_to(s.retweet_count);
j.at("favorite_count").get_to(s.favorite_count);
}
void from_json(const nlohmann::json &j, TwitterData &t) {
j.at("statuses").get_to(t.statuses);
}
// Helper functions for benchmarking
std::string nlohmann_serialize(const TwitterData &data) { std::string nlohmann_serialize(const TwitterData &data) {
return nlohmann_serialize(data.statuses); nlohmann::json j = data;
return j.dump();
}
TwitterData nlohmann_deserialize(const std::string &json_str) {
nlohmann::json j = nlohmann::json::parse(json_str);
return j.get<TwitterData>();
} }
#endif // NLOHMANN_TWITTER_DATA_H #endif // NLOHMANN_TWITTER_DATA_H
@@ -0,0 +1,142 @@
#ifndef RAPIDJSON_TWITTER_DATA_H
#define RAPIDJSON_TWITTER_DATA_H
#include "twitter_data.h"
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/error/en.h>
using namespace rapidjson;
// RapidJSON deserialization for simplified Twitter data
TwitterData rapidjson_deserialize(const std::string& json_str) {
Document doc;
doc.Parse(json_str.c_str());
if (doc.HasParseError()) {
throw std::runtime_error("RapidJSON parse error");
}
TwitterData data;
if (!doc.HasMember("statuses") || !doc["statuses"].IsArray()) {
return data;
}
const Value& statuses = doc["statuses"];
data.statuses.reserve(statuses.Size());
for (SizeType i = 0; i < statuses.Size(); i++) {
const Value& status_json = statuses[i];
Status status;
// Parse status fields
if (status_json.HasMember("created_at") && status_json["created_at"].IsString())
status.created_at = status_json["created_at"].GetString();
if (status_json.HasMember("id") && status_json["id"].IsUint64())
status.id = status_json["id"].GetUint64();
if (status_json.HasMember("text") && status_json["text"].IsString())
status.text = status_json["text"].GetString();
if (status_json.HasMember("retweet_count") && status_json["retweet_count"].IsUint64())
status.retweet_count = status_json["retweet_count"].GetUint64();
if (status_json.HasMember("favorite_count") && status_json["favorite_count"].IsUint64())
status.favorite_count = status_json["favorite_count"].GetUint64();
// Parse user
if (status_json.HasMember("user") && status_json["user"].IsObject()) {
const Value& user_json = status_json["user"];
User user;
if (user_json.HasMember("id") && user_json["id"].IsUint64())
user.id = user_json["id"].GetUint64();
if (user_json.HasMember("name") && user_json["name"].IsString())
user.name = user_json["name"].GetString();
if (user_json.HasMember("screen_name") && user_json["screen_name"].IsString())
user.screen_name = user_json["screen_name"].GetString();
if (user_json.HasMember("location") && user_json["location"].IsString())
user.location = user_json["location"].GetString();
if (user_json.HasMember("description") && user_json["description"].IsString())
user.description = user_json["description"].GetString();
if (user_json.HasMember("verified") && user_json["verified"].IsBool())
user.verified = user_json["verified"].GetBool();
if (user_json.HasMember("followers_count") && user_json["followers_count"].IsUint64())
user.followers_count = user_json["followers_count"].GetUint64();
if (user_json.HasMember("friends_count") && user_json["friends_count"].IsUint64())
user.friends_count = user_json["friends_count"].GetUint64();
if (user_json.HasMember("statuses_count") && user_json["statuses_count"].IsUint64())
user.statuses_count = user_json["statuses_count"].GetUint64();
status.user = user;
}
data.statuses.push_back(status);
}
return data;
}
// RapidJSON serialization for simplified Twitter data
std::string rapidjson_serialize(const TwitterData& data) {
Document doc;
doc.SetObject();
Document::AllocatorType& allocator = doc.GetAllocator();
Value statuses_array(kArrayType);
for (const auto& status : data.statuses) {
Value status_obj(kObjectType);
Value created_at;
created_at.SetString(status.created_at.c_str(), allocator);
status_obj.AddMember("created_at", created_at, allocator);
status_obj.AddMember("id", status.id, allocator);
Value text;
text.SetString(status.text.c_str(), allocator);
status_obj.AddMember("text", text, allocator);
// Add user
Value user_obj(kObjectType);
user_obj.AddMember("id", status.user.id, allocator);
Value name;
name.SetString(status.user.name.c_str(), allocator);
user_obj.AddMember("name", name, allocator);
Value screen_name;
screen_name.SetString(status.user.screen_name.c_str(), allocator);
user_obj.AddMember("screen_name", screen_name, allocator);
Value location;
location.SetString(status.user.location.c_str(), allocator);
user_obj.AddMember("location", location, allocator);
Value description;
description.SetString(status.user.description.c_str(), allocator);
user_obj.AddMember("description", description, allocator);
user_obj.AddMember("verified", status.user.verified, allocator);
user_obj.AddMember("followers_count", status.user.followers_count, allocator);
user_obj.AddMember("friends_count", status.user.friends_count, allocator);
user_obj.AddMember("statuses_count", status.user.statuses_count, allocator);
status_obj.AddMember("user", user_obj, allocator);
status_obj.AddMember("retweet_count", status.retweet_count, allocator);
status_obj.AddMember("favorite_count", status.favorite_count, allocator);
statuses_array.PushBack(status_obj, allocator);
}
doc.AddMember("statuses", statuses_array, allocator);
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
doc.Accept(writer);
return buffer.GetString();
}
#endif // RAPIDJSON_TWITTER_DATA_H
@@ -4,68 +4,31 @@
#include <string> #include <string>
#include <vector> #include <vector>
// Simplified Twitter structures for benchmarking
struct User { struct User {
int64_t id; uint64_t id;
std::string id_str;
std::string name; std::string name;
std::string screen_name; std::string screen_name;
std::string location; std::string location;
std::string description; std::string description;
bool verified; bool verified;
int64_t followers_count; uint64_t followers_count;
int64_t friends_count; uint64_t friends_count;
int64_t statuses_count; uint64_t statuses_count;
bool operator<=>(const User &other) const = default;
};
struct Hashtag {
std::string text;
int64_t indices_start;
int64_t indices_end;
bool operator<=>(const Hashtag &other) const = default;
};
struct Url {
std::string url;
std::string expanded_url;
std::string display_url;
int64_t indices_start;
int64_t indices_end;
bool operator<=>(const Url &other) const = default;
};
struct UserMention {
int64_t id;
std::string name;
std::string screen_name;
int64_t indices_start;
int64_t indices_end;
bool operator<=>(const UserMention &other) const = default;
};
struct Entities {
std::vector<Hashtag> hashtags;
std::vector<Url> urls;
std::vector<UserMention> user_mentions;
bool operator==(const Entities &other) const = default;
}; };
struct Status { struct Status {
std::string created_at; std::string created_at;
int64_t id; uint64_t id;
std::string text; std::string text;
User user; User user;
Entities entities; uint64_t retweet_count;
int64_t retweet_count; uint64_t favorite_count;
int64_t favorite_count;
bool favorited;
bool retweeted;
bool operator==(const Status &other) const = default;
}; };
struct TwitterData { struct TwitterData {
std::vector<Status> statuses; std::vector<Status> statuses;
bool operator==(const TwitterData &other) const = default;
}; };
#endif #endif // TWITTER_DATA_H
@@ -0,0 +1,145 @@
#ifndef YYJSON_TWITTER_DATA_H
#define YYJSON_TWITTER_DATA_H
#include "twitter_data.h"
#include <yyjson.h>
#include <string>
#include <stdexcept>
// yyjson deserialization for simplified Twitter data
TwitterData yyjson_deserialize(const std::string &json_str) {
TwitterData data;
yyjson_doc *doc = yyjson_read(json_str.c_str(), json_str.size(), 0);
if (!doc) {
throw std::runtime_error("yyjson parse error");
}
yyjson_val *root = yyjson_doc_get_root(doc);
if (!root) {
yyjson_doc_free(doc);
return data;
}
// Get statuses array
yyjson_val *statuses_val = yyjson_obj_get(root, "statuses");
if (!statuses_val || !yyjson_is_arr(statuses_val)) {
yyjson_doc_free(doc);
return data;
}
size_t idx, max;
yyjson_val *status_val;
yyjson_arr_foreach(statuses_val, idx, max, status_val) {
Status status;
// Parse status fields
yyjson_val *val;
val = yyjson_obj_get(status_val, "created_at");
if (val && yyjson_is_str(val)) status.created_at = yyjson_get_str(val);
val = yyjson_obj_get(status_val, "id");
if (val && yyjson_is_uint(val)) status.id = yyjson_get_uint(val);
val = yyjson_obj_get(status_val, "text");
if (val && yyjson_is_str(val)) status.text = yyjson_get_str(val);
val = yyjson_obj_get(status_val, "retweet_count");
if (val && yyjson_is_uint(val)) status.retweet_count = yyjson_get_uint(val);
val = yyjson_obj_get(status_val, "favorite_count");
if (val && yyjson_is_uint(val)) status.favorite_count = yyjson_get_uint(val);
// Parse user
yyjson_val *user_val = yyjson_obj_get(status_val, "user");
if (user_val && yyjson_is_obj(user_val)) {
User user;
val = yyjson_obj_get(user_val, "id");
if (val && yyjson_is_uint(val)) user.id = yyjson_get_uint(val);
val = yyjson_obj_get(user_val, "name");
if (val && yyjson_is_str(val)) user.name = yyjson_get_str(val);
val = yyjson_obj_get(user_val, "screen_name");
if (val && yyjson_is_str(val)) user.screen_name = yyjson_get_str(val);
val = yyjson_obj_get(user_val, "location");
if (val && yyjson_is_str(val)) user.location = yyjson_get_str(val);
val = yyjson_obj_get(user_val, "description");
if (val && yyjson_is_str(val)) user.description = yyjson_get_str(val);
val = yyjson_obj_get(user_val, "verified");
if (val && yyjson_is_bool(val)) user.verified = yyjson_get_bool(val);
val = yyjson_obj_get(user_val, "followers_count");
if (val && yyjson_is_uint(val)) user.followers_count = yyjson_get_uint(val);
val = yyjson_obj_get(user_val, "friends_count");
if (val && yyjson_is_uint(val)) user.friends_count = yyjson_get_uint(val);
val = yyjson_obj_get(user_val, "statuses_count");
if (val && yyjson_is_uint(val)) user.statuses_count = yyjson_get_uint(val);
status.user = user;
}
data.statuses.push_back(status);
}
yyjson_doc_free(doc);
return data;
}
// yyjson serialization for simplified Twitter data
std::string yyjson_serialize(const TwitterData &data) {
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
yyjson_mut_val *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
// Create statuses array
yyjson_mut_val *statuses_array = yyjson_mut_arr(doc);
for (const auto& status : data.statuses) {
yyjson_mut_val *status_obj = yyjson_mut_obj(doc);
// Add status fields
yyjson_mut_obj_add_str(doc, status_obj, "created_at", status.created_at.c_str());
yyjson_mut_obj_add_uint(doc, status_obj, "id", status.id);
yyjson_mut_obj_add_str(doc, status_obj, "text", status.text.c_str());
// User object
yyjson_mut_val *user_obj = yyjson_mut_obj(doc);
yyjson_mut_obj_add_uint(doc, user_obj, "id", status.user.id);
yyjson_mut_obj_add_str(doc, user_obj, "name", status.user.name.c_str());
yyjson_mut_obj_add_str(doc, user_obj, "screen_name", status.user.screen_name.c_str());
yyjson_mut_obj_add_str(doc, user_obj, "location", status.user.location.c_str());
yyjson_mut_obj_add_str(doc, user_obj, "description", status.user.description.c_str());
yyjson_mut_obj_add_bool(doc, user_obj, "verified", status.user.verified);
yyjson_mut_obj_add_uint(doc, user_obj, "followers_count", status.user.followers_count);
yyjson_mut_obj_add_uint(doc, user_obj, "friends_count", status.user.friends_count);
yyjson_mut_obj_add_uint(doc, user_obj, "statuses_count", status.user.statuses_count);
yyjson_mut_obj_add_val(doc, status_obj, "user", user_obj);
// Other fields
yyjson_mut_obj_add_uint(doc, status_obj, "retweet_count", status.retweet_count);
yyjson_mut_obj_add_uint(doc, status_obj, "favorite_count", status.favorite_count);
yyjson_mut_arr_append(statuses_array, status_obj);
}
// Add statuses array to root
yyjson_mut_obj_add_val(doc, root, "statuses", statuses_array);
// Write to string
char *json_output = yyjson_mut_write(doc, 0, NULL);
std::string result(json_output);
free(json_output);
yyjson_mut_doc_free(doc);
return result;
}
#endif // YYJSON_TWITTER_DATA_H
+1 -1
View File
@@ -12,7 +12,7 @@ cmake_dependent_option(SIMDJSON_GOOGLE_BENCHMARKS "compile the Google Benchmark
if(SIMDJSON_GOOGLE_BENCHMARKS) if(SIMDJSON_GOOGLE_BENCHMARKS)
CPMAddPackage( CPMAddPackage(
NAME google_benchmarks NAME google_benchmarks
URL https://github.com/google/benchmark/archive/refs/tags/v1.9.4.zip URL https://github.com/google/benchmark/archive/refs/tags/v1.9.5.zip
OPTIONS OPTIONS
"BENCHMARK_ENABLE_TESTING OFF" "BENCHMARK_ENABLE_TESTING OFF"
"BENCHMARK_ENABLE_INSTALL OFF" "BENCHMARK_ENABLE_INSTALL OFF"
+60 -32
View File
@@ -28,6 +28,8 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
- [UTF-8 validation (alone)](#utf-8-validation-alone) - [UTF-8 validation (alone)](#utf-8-validation-alone)
- [JSON Pointer](#json-pointer) - [JSON Pointer](#json-pointer)
- [JSONPath](#jsonpath) - [JSONPath](#jsonpath)
* [Using `for_each_at_path_with_wildcard` for JSONPath Queries (On-Demand)](#using-for_each_at_path_with_wildcard-for-jsonpath-queries-on-demand)
+ [Example Usage](#example-usage)
- [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection) - [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection)
- [Error handling](#error-handling) - [Error handling](#error-handling)
* [Error handling examples without exceptions](#error-handling-examples-without-exceptions) * [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
@@ -169,8 +171,10 @@ For efficiency reasons, simdjson requires a string with a few bytes (`simdjson::
at the end, these bytes may be read but their content does not affect the parsing. In practice, at the end, these bytes may be read but their content does not affect the parsing. In practice,
it means that the JSON inputs should be stored in a memory region with `simdjson::SIMDJSON_PADDING` it means that the JSON inputs should be stored in a memory region with `simdjson::SIMDJSON_PADDING`
extra bytes at the end. You do not have to set these bytes to specific values though you may extra bytes at the end. You do not have to set these bytes to specific values though you may
want to if you want to avoid runtime warnings with some sanitizers. Advanced users may want to want to if you want to avoid runtime warnings with some sanitizers. We expect the user
read the section Free Padding in [our performance notes](performance.md). of the library to load the data (from disk or from the network) into a padded buffer. To make
this easy, we provide the `padded_string::load` function which loads files from disk in a padded buffer.
[You can similarly fetch a file from a URL to a padded string](https://github.com/simdjson/curltostring) using our `simdjson::padded_string_builder`. Advanced users may want to read the section Free Padding in [our performance notes](performance.md).
The simdjson library offers a tree-like [API](https://en.wikipedia.org/wiki/API), which you can The simdjson library offers a tree-like [API](https://en.wikipedia.org/wiki/API), which you can
access by creating a `ondemand::parser` and calling the `iterate()` method. The iterate method access by creating a `ondemand::parser` and calling the `iterate()` method. The iterate method
@@ -269,6 +273,21 @@ Further, they may use the AreFileApisANSI function to determine whether
the filename is interpreted using the ANSI or the system default OEM the filename is interpreted using the ANSI or the system default OEM
codepage, and they may call SetFileApisToOEM accordingly. codepage, and they may call SetFileApisToOEM accordingly.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
```
Documents are iterators Documents are iterators
----------------------- -----------------------
@@ -354,7 +373,13 @@ the macro `SIMDJSON_DEVELOPMENT_CHECKS` to 1 prior to including
the `simdjson.h` header to enable these additional checks: just make sure you remove the the `simdjson.h` header to enable these additional checks: just make sure you remove the
definition once your code has been tested. When `SIMDJSON_DEVELOPMENT_CHECKS` is set to 1, the definition once your code has been tested. When `SIMDJSON_DEVELOPMENT_CHECKS` is set to 1, the
simdjson library runs additional (expensive) tests on your code to help ensure that you are simdjson library runs additional (expensive) tests on your code to help ensure that you are
using the library in a safe manner. using the library in a safe manner. We add asserts which may halt your program, helping
you find the bad programming pattern.
When `SIMDJSON_DEVELOPMENT_CHECKS`, some of our data structures contain extra data for
tracking explicitly potential programming mistakes. Thus you should not relying on the
size (`sizeof`) of our data structures to be constant: they may change depending on the
compiler settings.
Once your code has been tested, you can then run it in Once your code has been tested, you can then run it in
Release mode: under Visual Studio, it means having the `_DEBUG` macro undefined, and, for other Release mode: under Visual Studio, it means having the `_DEBUG` macro undefined, and, for other
@@ -366,6 +391,10 @@ builds to disable additional runtime testing and get the best performance. We
disable these checks on a best-effort basis but the C++ standard does not provide disable these checks on a best-effort basis but the C++ standard does not provide
a direct way to check for a release build. a direct way to check for a release build.
Warnign: Mixing debug and release simdjson code is unsafe: you either build all your code
using simdjson in release mode or all of it in debug mode.
Using the parsed JSON Using the parsed JSON
--------------------- ---------------------
@@ -409,7 +438,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
* **Extracting Values:** You can cast a JSON element to a native type: * **Extracting Values:** You can cast a JSON element to a native type:
`double(element)`. This works for `std::string_view`, double, uint64_t, int64_t, bool, `double(element)`. This works for `std::string_view`, double, uint64_t, int64_t, bool,
ondemand::object and ondemand::array. We also have explicit methods such as `get_string()`, `get_double()`, ondemand::object and ondemand::array. We also have explicit methods such as `get_string()`, `get_double()`,
`get_uint64()`, `get_int64()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method, `get_uint64()`, `get_int64()`, `get_uint32()`, `get_int32()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method,
the number, string or boolean will be parsed, or the initial `{` or `[` will be verified for `ondemand::object` and `ondemand::array`. An exception may be thrown if the number, string or boolean will be parsed, or the initial `{` or `[` will be verified for `ondemand::object` and `ondemand::array`. An exception may be thrown if
the cast is not possible: the error code is `simdjson::INCORRECT_TYPE` (see [Error handling](#error-handling)). Importantly, when getting an ondemand::object or ondemand::array instance, its content is the cast is not possible: the error code is `simdjson::INCORRECT_TYPE` (see [Error handling](#error-handling)). Importantly, when getting an ondemand::object or ondemand::array instance, its content is
not validated: you are only guaranteed that the corresponding initial character (`{` or `[`) is present. Thus, not validated: you are only guaranteed that the corresponding initial character (`{` or `[`) is present. Thus,
@@ -419,8 +448,8 @@ support for users who avoid exceptions. See [the simdjson error handling documen
pass `true` (`get_string(true)`) as a parameter to get replacement characters where errors pass `true` (`get_string(true)`) as a parameter to get replacement characters where errors
occur. If you somehow need to access non-UTF-8 strings in a lossless manner occur. If you somehow need to access non-UTF-8 strings in a lossless manner
(e.g., if you strings contain unpaired surrogates), you may use the `get_wobbly_string()` function to get a string in the [WTF-8 format](https://simonsapin.github.io/wtf-8). (e.g., if you strings contain unpaired surrogates), you may use the `get_wobbly_string()` function to get a string in the [WTF-8 format](https://simonsapin.github.io/wtf-8).
When calling `get_uint64()` and `get_int64()`, if the number does not fit in a corresponding When calling `get_uint64()`, `get_int64()`, `get_uint32()` or `get_int32()`, if the number does not fit in the
64-bit integer type, it is also considered an error. When parsing numbers or other scalar values, the library checks corresponding integer type, it is also considered an error (`NUMBER_OUT_OF_RANGE`). When parsing numbers or other scalar values, the library checks
that the value is followed by an expected character, thus you *may* get a number parsing error when accessing the digits that the value is followed by an expected character, thus you *may* get a number parsing error when accessing the digits
as an integer in the following strings: `{"number":12332a`, `{"number":12332\0`, `{"number":12332` (the digits appear at the end). We always abide by the [RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html) JSON specification so that, for example, numbers prefixed by the `+` sign are in error. as an integer in the following strings: `{"number":12332a`, `{"number":12332\0`, `{"number":12332` (the digits appear at the end). We always abide by the [RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html) JSON specification so that, for example, numbers prefixed by the `+` sign are in error.
@@ -437,8 +466,8 @@ support for users who avoid exceptions. See [the simdjson error handling documen
If you know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`. If you know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`.
You may also use explicit iterators: `for(auto i = array.begin(); i != array.end(); i++) {}`. You can check that an array is empty with the condition `auto i = array.begin(); if (i == array.end()) {...}`. You may also use explicit iterators: `for(auto i = array.begin(); i != array.end(); i++) {}`. You can check that an array is empty with the condition `auto i = array.begin(); if (i == array.end()) {...}`. You should derefence (`*i`) an iterator at most once before incrementing it (`i++`), when compiling in debug mode with development checks, we add asserts to help you identify such a mistake.
* **Object Iteration:** You can iterate through an object's fields, as well: `for (auto field : object) { ... }`. You may also use explicit iterators : `for(auto i = object.begin(); i != object.end(); i++) { auto field = *i; .... }`. You can check that an object is empty with the condition `auto i = object.begin(); if (i == object.end()) {...}`. * **Object Iteration:** You can iterate through an object's fields, as well: `for (auto field : object) { ... }`.
- `field.unescaped_key()` will get you the unescaped key string as a `std::string_view` instance. E.g., the JSON string `"\u00e1"` becomes the Unicode string `á`. Optionally, you pass `true` as a parameter to the `unescaped_key` method if you want invalid escape sequences to be replaced by a default replacement character (e.g., `\ud800\ud801\ud811`): otherwise bad escape sequences lead to an immediate error. - `field.unescaped_key()` will get you the unescaped key string as a `std::string_view` instance. E.g., the JSON string `"\u00e1"` becomes the Unicode string `á`. Optionally, you pass `true` as a parameter to the `unescaped_key` method if you want invalid escape sequences to be replaced by a default replacement character (e.g., `\ud800\ud801\ud811`): otherwise bad escape sequences lead to an immediate error.
- `field.escaped_key()` will get you the key string as as a `std::string_view` instance, but unlike `unescaped_key()`, the key is not processed, so no unescaping is done. E.g., the JSON string `"\u00e1"` becomes the Unicode string `\u00e1`. We expect that `escaped_key()` is faster than `field.unescaped_key()`. - `field.escaped_key()` will get you the key string as as a `std::string_view` instance, but unlike `unescaped_key()`, the key is not processed, so no unescaping is done. E.g., the JSON string `"\u00e1"` becomes the Unicode string `\u00e1`. We expect that `escaped_key()` is faster than `field.unescaped_key()`.
- `field.value()` will get you the value, which you can then use all these other methods on. - `field.value()` will get you the value, which you can then use all these other methods on.
@@ -451,13 +480,17 @@ support for users who avoid exceptions. See [the simdjson error handling documen
When you are iterating through an object, you are advancing through its keys and values. You should not also access the object or other objects. E.g. within a loop over `myobject`, you should not be accessing `myobject`. The following is an anti-pattern: `for(auto value: myobject) {myobject["mykey"]}`. When you are iterating through an object, you are advancing through its keys and values. You should not also access the object or other objects. E.g. within a loop over `myobject`, you should not be accessing `myobject`. The following is an anti-pattern: `for(auto value: myobject) {myobject["mykey"]}`.
We discourage using the object iterators explicitly: `for(auto i = object.begin(); i != object.end(); i++) { auto field = *i; .... }`. In addition to the usual requirement to check against `end()` prior to dereferencing, you must also always dereference the pointer (`*it`) exactly once before you increment it (`it++`). You must also only deference the iterator once (never more than once). When compiling in
debug mode with development checks, we add asserts to help check whether you correctly
dereferenced the pointer before incrementing it.
You should never reset an object as you are iterating through it. The following is an anti-pattern: `for(auto value: myobject) {myobject.reset()}`. You should never reset an object as you are iterating through it. The following is an anti-pattern: `for(auto value: myobject) {myobject.reset()}`.
* **Array Index:** Because it is forward-only, you cannot look up an array element by index. Instead, * **Array Index:** Because it is forward-only, you cannot look up an array element by index. Instead,
you should iterate through the array and keep an index yourself. Exceptionally, if need a single value you should iterate through the array and keep an index yourself. Exceptionally, if need a single value
out of the array, you may use an array access (e.g., `array[1]`). You should never reset an array as you are iterating through it. The following is an anti-pattern: `for(auto value: myarray) {myarray.reset()}`. out of the array, you may use an array access (e.g., `array[1]`). You should never reset an array as you are iterating through it. The following is an anti-pattern: `for(auto value: myarray) {myarray.reset()}`.
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`. This will * **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`. This will
scan through the object looking for the field with the matching string, doing a character-by-character scan through the object looking for the field with the matching string, doing a character-by-character
comparison. It may generate the error `simdjson::NO_SUCH_FIELD` if there is no such key in the object, it may throw an exception (see [Error handling](#error-handling)). For efficiency reason, you should avoid looking up the same field repeatedly: e.g., do comparison. It may generate the error `simdjson::NO_SUCH_FIELD` if there is no such key in the object, it may throw an exception (see [Error handling](#error-handling)). The returned value is only valid so long as you do not access another field: normally, you should therefore grab the value right after accessing a key (i.e., convert it to number, string, object, array...). For efficiency reason, you should avoid looking up the same field repeatedly: e.g., do
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumedonly once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumedonly once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
object twice: `std::string_view(object["foo"]` followed by `std::string_view(object["foo"]` then your code object twice: `std::string_view(object["foo"]` followed by `std::string_view(object["foo"]` then your code
is in error. Furthermore, you can only consume one field at a time, on the same object. The is in error. Furthermore, you can only consume one field at a time, on the same object. The
@@ -1759,15 +1792,15 @@ int64_t x = obj.at_path("$.c.foo.a[1]"); // 20
x = obj.at_path("$.d.foo2.a.2"); // 30 x = obj.at_path("$.d.foo2.a.2"); // 30
``` ```
## Using `at_path_with_wildcard` for JSONPath Queries (On-Demand) ## Using `for_each_at_path_with_wildcard` for JSONPath Queries (On-Demand)
The `at_path_with_wildcard` function in simdjson extends the JSONPath querying capabilities by supporting wildcard expressions (`*`) in JSON paths. This allows users to retrieve multiple elements from a JSON document in a single query. For example, you can use `$.address.*` to fetch all fields within the `address` object or `$.phoneNumbers[*].numbers[*]` to retrieve all phone numbers across multiple objects in an array. The `for_each_at_path_with_wildcard` function in simdjson extends the JSONPath querying capabilities by supporting wildcard expressions (`*`) in JSON paths. It calls a user-provided callback for each matching element, avoiding the need to materialize all results into a vector. For example, you can use `$.address.*` to fetch all fields within the `address` object or `$.phoneNumbers[*].numbers[*]` to retrieve all phone numbers across multiple objects in an array.
The `*` wildcard matches all elements at a specific level. For instance, `$.address.*` retrieves all key-value pairs in the `address` object, while `$.*.streetAddress` fetches all `streetAddress` fields across objects at the root level. You can combine wildcards with array indexing. For example, `$.phoneNumbers[*].numbers[1]` retrieves the second number from each `numbers` array in the `phoneNumbers` array. If no elements match the wildcard query, the function returns an empty result. For instance, querying `$.empty_object.*` or `$.empty_array.*` will yield an empty set. The `*` wildcard matches all elements at a specific level. For instance, `$.address.*` retrieves all key-value pairs in the `address` object, while `$.*.streetAddress` fetches all `streetAddress` fields across objects at the root level. You can combine wildcards with array indexing. For example, `$.phoneNumbers[*].numbers[1]` retrieves the second number from each `numbers` array in the `phoneNumbers` array. If no elements match the wildcard query, the callback is simply never called. For instance, querying `$.empty_object.*` or `$.empty_array.*` will yield no callbacks.
### Example Usage ### Example Usage
Here is an example demonstrating the use of `at_path_with_wildcard`: Here is an example demonstrating the use of `for_each_at_path_with_wildcard`:
```cpp ```cpp
simdjson::padded_string json_string = R"( simdjson::padded_string json_string = R"(
@@ -1796,27 +1829,22 @@ ondemand::parser parser;
ondemand::document doc = parser.iterate(json_string); ondemand::document doc = parser.iterate(json_string);
// Fetch all fields in the address object // Fetch all fields in the address object
std::vector<ondemand::value> values; auto error = doc.for_each_at_path_with_wildcard("$.address.*",
auto error = doc.at_path_with_wildcard("$.address.*").get(values); [](ondemand::value value) {
if (!error) { std::string_view field;
for (auto value : values) { if (value.get(field) == SUCCESS) {
std::string_view field; std::cout << field << std::endl;
if (value.get(field) == SUCCESS) { }
std::cout << field << std::endl; });
}
}
}
// Fetch all phone numbers // Fetch all phone numbers
error = doc.at_path_with_wildcard("$.phoneNumbers[*].numbers[*]").get(values); doc.for_each_at_path_with_wildcard("$.phoneNumbers[*].numbers[*]",
if (!error) { [](ondemand::value value) {
for (auto value : values) { std::string_view number;
std::string_view number; if (value.get(number) == SUCCESS) {
if (value.get(number) == SUCCESS) { std::cout << number << std::endl;
std::cout << number << std::endl; }
} });
}
}
``` ```
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations. This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
+49 -2
View File
@@ -14,6 +14,7 @@ speed and high convenience.
* [C++26 static reflection](#c--26-static-reflection) * [C++26 static reflection](#c--26-static-reflection)
+ [Without `string_buffer` instance](#without--string-buffer--instance) + [Without `string_buffer` instance](#without--string-buffer--instance)
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling) + [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
+ [Pretty formatted (fractured JSON)](#pretty-formatted-fractured-json)
Overview: string_builder Overview: string_builder
--------------------------- ---------------------------
@@ -332,7 +333,7 @@ pattern:
### Customization ### Customization
If you want to serialize a value in a custome way, you can do it with a If you want to serialize a value in a custom way, you can do it with a
`tag_invoke` specialization like the following example which will map `tag_invoke` specialization like the following example which will map
the year attribute to a string. the year attribute to a string.
@@ -363,4 +364,50 @@ void tag_invoke(serialize_tag, builder_type &builder, const Car& car) {
} }
} // namespace simdjson } // namespace simdjson
``` ```
### Pretty formatted (fractured JSON)
In some instances, you may want your JSON to be more readable. For this pupose, we also
support the Fractured JSON standard.
```Cpp
TableTestData data{
{{1, "Alice", true}, {2, "Bob", false}, {3, "Carol", true}, {4, "Dave", false}}
};
fractured_json_options opts;
opts.enable_table_format = true;
opts.min_table_rows = 3;
std::string formatted = simdjson::to_fractured_json_string(data, opts);
```
The result might be as follows.
```json
{
"records": [
{ "active": true , "id": 1, "name": "Alice" },
{ "active": false, "id": 2, "name": "Bob" },
{ "active": true , "id": 3, "name": "Carol" },
{ "active": false, "id": 4, "name": "Dave" }
]
}
```
The `fractured_json_options` struct allows you to customize the formatting behavior. It includes the following options:
- `max_total_line_length` (default: 120): Maximum total characters per line. Content exceeding this will be expanded to multiple lines.
- `max_inline_length` (default: 80): Maximum length for inlined elements. Simple arrays/objects shorter than this may be rendered inline.
- `max_inline_complexity` (default: 2): Maximum nesting depth for inline rendering. Elements with complexity exceeding this will be expanded. Complexity 0 = scalar, 1 = flat array/object, 2 = one level of nesting.
- `max_compact_array_complexity` (default: 1): Maximum complexity for compact array formatting. Arrays with elements of this complexity or less may have multiple items per line.
- `indent_spaces` (default: 4): Number of spaces per indentation level.
- `enable_table_format` (default: true): Enable tabular formatting for arrays of similar objects. When enabled, arrays of objects with identical keys are formatted as aligned tables.
- `min_table_rows` (default: 3): Minimum number of rows to trigger table mode.
- `table_similarity_threshold` (default: 0.8): Similarity threshold for table detection. Objects must share at least this fraction of keys to be formatted as a table.
- `enable_compact_multiline` (default: true): Enable compact multiline arrays. When enabled, arrays of simple elements may have multiple items per line.
- `max_items_per_line` (default: 10): Maximum array items per line in compact mode.
- `simple_bracket_padding` (default: true): Add space inside brackets for simple containers. When true: `{ "key": "value" }`, when false: `{"key": "value"}`.
- `colon_padding` (default: true): Add space after colons. When true: `"key": "value"`, when false: `"key":"value"`.
- `comma_padding` (default: true): Add space after commas in inline content. When true: `[1, 2, 3]`, when false: `[1,2,3]`.
+1 -1
View File
@@ -128,7 +128,7 @@ Let us consider this example:
``` ```
You might want to ensure that the result is an array of persons. You can define your You might want to ensure that the result is an array of persons. You can define your
expection with concepts like so: expectation with concepts like so:
```cpp ```cpp
template <typename T> template <typename T>
+36 -1
View File
@@ -62,6 +62,8 @@ auto json = padded_string::load("twitter.json"); // load JSON file 'twitter.json
dom::element doc = parser.parse(json); dom::element doc = parser.parse(json);
``` ```
[You can similarly fetch a file from a URL to a padded string](https://github.com/simdjson/curltostring) using our `simdjson::padded_string_builder`.
(Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII (Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
filenames: `padded_string::load(L"twitter.json")`.) filenames: `padded_string::load(L"twitter.json")`.)
@@ -123,6 +125,22 @@ Further, they may use the AreFileApisANSI function to determine whether
the filename is interpreted using the ANSI or the system default OEM the filename is interpreted using the ANSI or the system default OEM
codepage, and they may call SetFileApisToOEM accordingly. codepage, and they may call SetFileApisToOEM accordingly.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
```cpp
// if the macro _WIN32 is defined, this will not work since we do not support Windows
simdjson::padded_memory_map map(TWITTER_JSON);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
```
Using memory-file mapping requires some care. The file should not be modified while you are
accessing it.
Using the Parsed JSON Using the Parsed JSON
--------------------- ---------------------
@@ -150,6 +168,23 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to `1` when building simdjson, you can get that `-0` is mapped to `-0.0` `SIMDJSON_MINUS_ZERO_AS_FLOAT` to `1` when building simdjson, you can get that `-0` is mapped to `-0.0`
as in JavaScript. You can get the desired effect by building simdjson with cmake setting the as in JavaScript. You can get the desired effect by building simdjson with cmake setting the
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to on: `cmake -B build -D SIMDJSON_MINUS_ZERO_AS_FLOAT=ON`. `SIMDJSON_MINUS_ZERO_AS_FLOAT` to on: `cmake -B build -D SIMDJSON_MINUS_ZERO_AS_FLOAT=ON`.
* **Big Integer Support (opt-in):** By default, integers that exceed the 64-bit range cause parsing to fail with `BIGINT_ERROR`. You can opt in to big integer support so that these numbers are stored as raw digit strings on the tape instead:
```cpp
simdjson::dom::parser parser;
parser.number_as_string(true); // opt-in, default false
simdjson::dom::element doc;
auto error = parser.parse("[1, 123456789012345678901]"_padded).get(doc);
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
for (simdjson::dom::element elem : doc) {
if (elem.is_bigint()) {
std::string_view digits;
error = elem.get_bigint().get(digits);
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
std::cout << "big integer: " << digits << std::endl;
}
}
```
When enabled, big integers have type `element_type::BIGINT`. Calling `get_int64()`, `get_uint64()`, or `get_double()` on a big integer returns `INCORRECT_TYPE`. Normal numbers (int64, uint64, double) are unaffected.
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`. * **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`.
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you * **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }` know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
@@ -162,7 +197,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys) * **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
with the `size()` method. with the `size()` method.
* **Checking an Element Type:** You can check an element's type with `element.type()`. It * **Checking an Element Type:** You can check an element's type with `element.type()`. It
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::STRING`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`. returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::STRING`, `simdjson::dom::element_type::BOOL`, `simdjson::dom::element_type::NULL_VALUE` or, `simdjson::dom::element_type::BIGINT` (when big integer support is enabled).
* **Output to streams and strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`) or a prettified string version (`simdjson::prettify(element)`). Numbers are serialized as 64-bit floating-point numbers (`double`). * **Output to streams and strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`) or a prettified string version (`simdjson::prettify(element)`). Numbers are serialized as 64-bit floating-point numbers (`double`).
### Examples ### Examples
+3
View File
@@ -33,6 +33,9 @@ compiles *all* the implementations into the executable. On Intel, it will includ
(icelake, haswell, westmere and fallback), on 64-bit ARM it will include just one since running dispatching is unnecessary, and on PPC (icelake, haswell, westmere and fallback), on 64-bit ARM it will include just one since running dispatching is unnecessary, and on PPC
it will include 2 (ppc64 and fallback). it will include 2 (ppc64 and fallback).
On Loongson processors, LASX runtime dispatching is only enabled on GCC 15+, not on LLVM or older versions of GCC.
Thus unless you compile specifically for LASX or use GCC 15+, you will not benefit from LASX support.
If you know more about where you're going to run and want to save the space, you can disable any of If you know more about where you're going to run and want to save the space, you can disable any of
these implementations at compile time with `-DSIMDJSON_IMPLEMENTATION_X=0` (where X is ICELAKE, HASWELL, these implementations at compile time with `-DSIMDJSON_IMPLEMENTATION_X=0` (where X is ICELAKE, HASWELL,
WESTMERE, ARM64, PPC64, LSX, LASX and FALLBACK). WESTMERE, ARM64, PPC64, LSX, LASX and FALLBACK).
+14
View File
@@ -154,6 +154,20 @@ for (auto doc : docs) {
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API. See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
```
## Use cases ## Use cases
From [jsonlines.org](http://jsonlines.org/examples/): From [jsonlines.org](http://jsonlines.org/examples/):
+14
View File
@@ -217,6 +217,20 @@ got full document at 29
``` ```
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
```
Incomplete streams Incomplete streams
----------- -----------
+58
View File
@@ -297,4 +297,62 @@ int main() {
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
```
Further, whenever you allocate N bytes, memory allocators tend to allocate more memory, without you necessarily knowing about it. Under linux, you can use the `malloc_usable_size` function to see how much memory was actually allocated.
Under an Apple plateform, you can `malloc_size`. The following program illustrates the usage.
```cpp
#include <iostream>
#include <cstddef>
#include <memory>
#include <cstdlib>
#ifdef __APPLE__
#include <malloc/malloc.h> // for malloc_size on macOS
#endif
#ifdef __linux__
#include <malloc.h> // for malloc_usable_size on Linux
#endif
size_t get_usable_size(void* ptr) {
#ifdef __linux__
return malloc_usable_size(ptr);
#elif defined(__APPLE__)
return malloc_size(ptr);
#else
return 0; // Unsupported platform
#endif
}
int main() {
std::cout << "Demonstrating allocation overhead and rounding with operator new\n\n";
#ifdef __linux__
std::cout << "Platform: Linux\n";
#elif defined(__APPLE__)
std::cout << "Platform: macOS (using malloc_size)\n";
#else
std::cout << "Platform: Other/unsupported (usable size will show 0)\n";
#endif
std::cout << "Requested size | Actual usable size\n";
std::cout << "---------------|-------------------\n";
size_t total_requested = 0;
size_t total_usable = 0;
for (size_t requested = 1; requested <= 4096; requested++) {
total_requested += requested;
std::unique_ptr<char[]> ptr(new char[requested]); // Allocate
size_t usable = get_usable_size(ptr.get()); // Get usable size
total_usable += usable;
std::cout << requested << "\t | " << usable << "\n";
}
std::cout << "---------------|-------------------\n";
std::cout << "Total requested: " << total_requested << " bytes\n";
std::cout << "Total usable: " << total_usable << " bytes\n";
std::cout << "Total overhead: " << (total_usable - total_requested) << " bytes\n";
std::cout << "Percentage overhead: "
<< ((total_usable - total_requested) * 100.0 / total_requested) << " %\n";
return EXIT_SUCCESS;
}
``` ```
+8
View File
@@ -93,6 +93,14 @@ Float values are represented as two 64-bit tape elements:
Performance consideration: We store numbers of the main tape because we believe that locality of reference is helpful for performance. Performance consideration: We store numbers of the main tape because we believe that locality of reference is helpful for performance.
## Big Integers
When a JSON integer exceeds the 64-bit range (both signed and unsigned), it is classified as a big integer. By default, parsing returns `BIGINT_ERROR`. When `parser.number_as_string(true)` is set, the raw digits are stored on the string tape using the same format as strings (4-byte little-endian length prefix, followed by the UTF-8 digit bytes, followed by a null terminator).
A big integer is represented on the main tape as the 64-bit tape element `('Z' << 56) + x` where the payload `x` is the location on the string tape of the null-terminated digit string.
For example, the JSON value `99999999999999999999` would be stored as the string `"99999999999999999999"` on the string tape, with a `'Z'` tag on the main tape. Negative big integers include the leading minus sign.
## Root node ## Root node
Each JSON document will have two special 64-bit tape elements representing a root node, one at the beginning and one at the end. Each JSON document will have two special 64-bit tape elements representing a root node, one at the beginning and one at the end.
+3
View File
@@ -54,6 +54,9 @@ static void print_json(std::ostream& os, simdjson::dom::element element) noexcep
case simdjson::dom::element_type::NULL_VALUE: case simdjson::dom::element_type::NULL_VALUE:
os << "null" << endl; os << "null" << endl;
break; break;
case simdjson::dom::element_type::BIGINT:
os << element.get_bigint().value_unsafe() << endl;
break;
} }
} }
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+1 -1
View File
@@ -35,7 +35,7 @@ cmake .. \
-DSIMDJSON_DISABLE_DEPRECATED_API=On \ -DSIMDJSON_DISABLE_DEPRECATED_API=On \
-DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE -DSIMDJSON_FUZZ_LDFLAGS=$LIB_FUZZING_ENGINE
cmake --build . --target all_fuzzers cmake --build . --target all_fuzzers all_tests
cp fuzz/fuzz_* $OUT cp fuzz/fuzz_* $OUT
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 136 KiB

+1
View File
@@ -52,6 +52,7 @@
#include "simdjson/padded_string_view-inl.h" #include "simdjson/padded_string_view-inl.h"
#include "simdjson/dom.h" #include "simdjson/dom.h"
#include "simdjson/builder.h"
#include "simdjson/ondemand.h" #include "simdjson/ondemand.h"
#include "simdjson/convert.h" #include "simdjson/convert.h"
#include "simdjson/convert-inl.h" #include "simdjson/convert-inl.h"
+8
View File
@@ -0,0 +1,8 @@
#ifndef SIMDJSON_ARM64_BUILDER_H
#define SIMDJSON_ARM64_BUILDER_H
#include "simdjson/arm64/begin.h"
#include "simdjson/generic/builder/amalgamated.h"
#include "simdjson/arm64/end.h"
#endif // SIMDJSON_ARM64_BUILDER_H
+1
View File
@@ -428,6 +428,7 @@ namespace {
static constexpr int NUM_CHUNKS = 64 / sizeof(simd8<T>); static constexpr int NUM_CHUNKS = 64 / sizeof(simd8<T>);
static_assert(NUM_CHUNKS == 4, "ARM kernel should use four registers per 64-byte block."); static_assert(NUM_CHUNKS == 4, "ARM kernel should use four registers per 64-byte block.");
const simd8<T> chunks[NUM_CHUNKS]; const simd8<T> chunks[NUM_CHUNKS];
template<int idx> simd8<uint8_t> get() const { return idx < NUM_CHUNKS ? chunks[idx] : simd8<T>(); }
simd8x64(const simd8x64<T>& o) = delete; // no copy allowed simd8x64(const simd8x64<T>& o) = delete; // no copy allowed
simd8x64<T>& operator=(const simd8<T>& other) = delete; // no assignment allowed simd8x64<T>& operator=(const simd8<T>& other) = delete; // no assignment allowed
+14
View File
@@ -0,0 +1,14 @@
#ifndef SIMDJSON_BUILDER_H
#define SIMDJSON_BUILDER_H
#include "simdjson/builtin/builder.h"
namespace simdjson {
/**
* @copydoc simdjson::builtin::builder
*/
namespace builder = builtin::builder;
} // namespace simdjson
#endif // SIMDJSON_BUILDER_H
+4 -2
View File
@@ -20,10 +20,12 @@
#include "simdjson/ppc64.h" #include "simdjson/ppc64.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere) #elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere)
#include "simdjson/westmere.h" #include "simdjson/westmere.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx)
#include "simdjson/lsx.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) #elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx)
#include "simdjson/lasx.h" #include "simdjson/lasx.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx)
#include "simdjson/lsx.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(rvv_vls)
#include "simdjson/rvv-vls.h"
#else #else
#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION #error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION
#endif #endif
+2
View File
@@ -21,6 +21,8 @@ namespace simdjson {
namespace lsx {} namespace lsx {}
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) #elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx)
namespace lasx {} namespace lasx {}
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(rvv_vls)
namespace rvv_vls {}
#else #else
#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION #error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION
#endif #endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef SIMDJSON_BUILTIN_BUILDER_H
#define SIMDJSON_BUILTIN_BUILDER_H
#include "simdjson/builtin.h"
#include "simdjson/builtin/base.h"
#include "simdjson/generic/builder/dependencies.h"
#define SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_BUILTIN_IMPLEMENTATION_IS(arm64)
#include "simdjson/arm64/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(fallback)
#include "simdjson/fallback/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(haswell)
#include "simdjson/haswell/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(icelake)
#include "simdjson/icelake/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(ppc64)
#include "simdjson/ppc64/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere)
#include "simdjson/westmere/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx)
#include "simdjson/lsx/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx)
#include "simdjson/lasx/builder.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(rvv_vls)
#include "simdjson/rvv-vls/builder.h"
#else
#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION
#endif
#undef SIMDJSON_CONDITIONAL_INCLUDE
namespace simdjson {
/**
* @copydoc simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::builder
*/
namespace builder = SIMDJSON_BUILTIN_IMPLEMENTATION::builder;
} // namespace simdjson
#endif // SIMDJSON_BUILTIN_BUILDER_H
+3 -1
View File
@@ -23,6 +23,8 @@
#include "simdjson/lsx/implementation.h" #include "simdjson/lsx/implementation.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) #elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx)
#include "simdjson/lasx/implementation.h" #include "simdjson/lasx/implementation.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(rvv_vls)
#include "simdjson/rvv-vls/implementation.h"
#else #else
#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION #error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION
#endif #endif
@@ -39,4 +41,4 @@ namespace simdjson {
const implementation * builtin_implementation(); const implementation * builtin_implementation();
} // namespace simdjson } // namespace simdjson
#endif // SIMDJSON_BUILTIN_IMPLEMENTATION_H #endif // SIMDJSON_BUILTIN_IMPLEMENTATION_H
+3 -1
View File
@@ -24,6 +24,8 @@
#include "simdjson/lsx/ondemand.h" #include "simdjson/lsx/ondemand.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) #elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx)
#include "simdjson/lasx/ondemand.h" #include "simdjson/lasx/ondemand.h"
#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(rvv_vls)
#include "simdjson/rvv-vls/ondemand.h"
#else #else
#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION #error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION
#endif #endif
@@ -37,4 +39,4 @@ namespace simdjson {
namespace ondemand = SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand; namespace ondemand = SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand;
} // namespace simdjson } // namespace simdjson
#endif // SIMDJSON_BUILTIN_ONDEMAND_H #endif // SIMDJSON_BUILTIN_ONDEMAND_H
+3 -1
View File
@@ -289,7 +289,9 @@ namespace std {
// when the compiler is optimizing. // when the compiler is optimizing.
// We only set SIMDJSON_DEVELOPMENT_CHECKS if both __OPTIMIZE__ // We only set SIMDJSON_DEVELOPMENT_CHECKS if both __OPTIMIZE__
// and NDEBUG are not defined. // and NDEBUG are not defined.
#if !defined(__OPTIMIZE__) && !defined(NDEBUG) // We recognize _DEBUG as overriding __OPTIMIZE__ so that if both
// __OPTIMIZE__ and _DEBUG are defined, we still set SIMDJSON_DEVELOPMENT_CHECKS.
#if ((!defined(__OPTIMIZE__) || defined(_DEBUG)) && !defined(NDEBUG))
#define SIMDJSON_DEVELOPMENT_CHECKS 1 #define SIMDJSON_DEVELOPMENT_CHECKS 1
#endif // __OPTIMIZE__ #endif // __OPTIMIZE__
#endif // _MSC_VER #endif // _MSC_VER
-1
View File
@@ -119,5 +119,4 @@
#define SIMDJSON_CONSTEVAL 0 #define SIMDJSON_CONSTEVAL 0
#endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L #endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
#endif // !defined(SIMDJSON_CONSTEVAL) #endif // !defined(SIMDJSON_CONSTEVAL)
#endif // SIMDJSON_COMPILER_CHECK_H #endif // SIMDJSON_COMPILER_CHECK_H
+2
View File
@@ -9,6 +9,7 @@
#include "simdjson/dom/object.h" #include "simdjson/dom/object.h"
#include "simdjson/dom/parser.h" #include "simdjson/dom/parser.h"
#include "simdjson/dom/serialization.h" #include "simdjson/dom/serialization.h"
#include "simdjson/dom/fractured_json.h"
// Inline functions // Inline functions
#include "simdjson/dom/array-inl.h" #include "simdjson/dom/array-inl.h"
@@ -19,5 +20,6 @@
#include "simdjson/dom/parser-inl.h" #include "simdjson/dom/parser-inl.h"
#include "simdjson/internal/tape_ref-inl.h" #include "simdjson/internal/tape_ref-inl.h"
#include "simdjson/dom/serialization-inl.h" #include "simdjson/dom/serialization-inl.h"
#include "simdjson/dom/fractured_json-inl.h"
#endif // SIMDJSON_DOM_H #endif // SIMDJSON_DOM_H
+9
View File
@@ -141,6 +141,15 @@ inline bool document::dump_raw_tape(std::ostream &os) const noexcept {
case 'r': // we start and end with the root node case 'r': // we start and end with the root node
// should we be hitting the root node? // should we be hitting the root node?
return false; return false;
case 'Z': // we have a big integer
os << "bigint ";
std::memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t));
os << std::string_view(
reinterpret_cast<const char *>(string_buf.get() + payload + sizeof(uint32_t)),
string_length
);
os << '\n';
break;
default: default:
return false; return false;
} }
+22
View File
@@ -81,6 +81,10 @@ simdjson_inline simdjson_result<bool> simdjson_result<dom::element>::get_bool()
if (error()) { return error(); } if (error()) { return error(); }
return first.get_bool(); return first.get_bool();
} }
simdjson_inline simdjson_result<std::string_view> simdjson_result<dom::element>::get_bigint() const noexcept {
if (error()) { return error(); }
return first.get_bigint();
}
simdjson_inline bool simdjson_result<dom::element>::is_array() const noexcept { simdjson_inline bool simdjson_result<dom::element>::is_array() const noexcept {
return !error() && first.is_array(); return !error() && first.is_array();
@@ -110,6 +114,9 @@ simdjson_inline bool simdjson_result<dom::element>::is_bool() const noexcept {
simdjson_inline bool simdjson_result<dom::element>::is_null() const noexcept { simdjson_inline bool simdjson_result<dom::element>::is_null() const noexcept {
return !error() && first.is_null(); return !error() && first.is_null();
} }
simdjson_inline bool simdjson_result<dom::element>::is_bigint() const noexcept {
return !error() && first.is_bigint();
}
simdjson_inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](std::string_view key) const noexcept { simdjson_inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](std::string_view key) const noexcept {
if (error()) { return error(); } if (error()) { return error(); }
@@ -218,6 +225,15 @@ inline simdjson_result<bool> element::get_bool() const noexcept {
} }
return INCORRECT_TYPE; return INCORRECT_TYPE;
} }
inline simdjson_result<std::string_view> element::get_bigint() const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable());
switch (tape.tape_ref_type()) {
case internal::tape_type::BIGINT:
return tape.get_string_view();
default:
return INCORRECT_TYPE;
}
}
inline simdjson_result<const char *> element::get_c_str() const noexcept { inline simdjson_result<const char *> element::get_c_str() const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
switch (tape.tape_ref_type()) { switch (tape.tape_ref_type()) {
@@ -360,6 +376,10 @@ inline bool element::is_null() const noexcept {
return tape.is_null_on_tape(); return tape.is_null_on_tape();
} }
inline bool element::is_bigint() const noexcept {
return tape.tape_ref_type() == internal::tape_type::BIGINT;
}
#if SIMDJSON_EXCEPTIONS #if SIMDJSON_EXCEPTIONS
inline element::operator bool() const noexcept(false) { return get<bool>(); } inline element::operator bool() const noexcept(false) { return get<bool>(); }
@@ -492,6 +512,8 @@ inline std::ostream& operator<<(std::ostream& out, element_type type) {
return out << "bool"; return out << "bool";
case element_type::NULL_VALUE: case element_type::NULL_VALUE:
return out << "null"; return out << "null";
case element_type::BIGINT:
return out << "bigint";
default: default:
return out << "unexpected content!!!"; // abort() usage is forbidden in the library return out << "unexpected content!!!"; // abort() usage is forbidden in the library
} }
+17 -1
View File
@@ -21,7 +21,8 @@ enum class element_type {
DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double. DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double.
STRING = '"', ///< std::string_view STRING = '"', ///< std::string_view
BOOL = 't', ///< bool BOOL = 't', ///< bool
NULL_VALUE = 'n' ///< null NULL_VALUE = 'n', ///< null
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
}; };
/** /**
@@ -120,6 +121,14 @@ public:
*/ */
inline simdjson_result<bool> get_bool() const noexcept; inline simdjson_result<bool> get_bool() const noexcept;
/**
* Read this element as a big integer (raw digit string).
*
* @returns A string_view of the raw digits, or:
* INCORRECT_TYPE if the JSON element is not a big integer.
*/
inline simdjson_result<std::string_view> get_bigint() const noexcept;
/** /**
* Whether this element is a json array. * Whether this element is a json array.
* *
@@ -175,6 +184,11 @@ public:
*/ */
inline bool is_null() const noexcept; inline bool is_null() const noexcept;
/**
* Whether this element is a big integer (number exceeding 64-bit range).
*/
inline bool is_bigint() const noexcept;
/** /**
* Tell whether the value can be cast to provided type (T). * Tell whether the value can be cast to provided type (T).
* *
@@ -533,6 +547,7 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64() const noexcept; simdjson_inline simdjson_result<uint64_t> get_uint64() const noexcept;
simdjson_inline simdjson_result<double> get_double() const noexcept; simdjson_inline simdjson_result<double> get_double() const noexcept;
simdjson_inline simdjson_result<bool> get_bool() const noexcept; simdjson_inline simdjson_result<bool> get_bool() const noexcept;
simdjson_inline simdjson_result<std::string_view> get_bigint() const noexcept;
simdjson_inline bool is_array() const noexcept; simdjson_inline bool is_array() const noexcept;
simdjson_inline bool is_object() const noexcept; simdjson_inline bool is_object() const noexcept;
@@ -543,6 +558,7 @@ public:
simdjson_inline bool is_number() const noexcept; simdjson_inline bool is_number() const noexcept;
simdjson_inline bool is_bool() const noexcept; simdjson_inline bool is_bool() const noexcept;
simdjson_inline bool is_null() const noexcept; simdjson_inline bool is_null() const noexcept;
simdjson_inline bool is_bigint() const noexcept;
simdjson_inline simdjson_result<dom::element> operator[](std::string_view key) const noexcept; simdjson_inline simdjson_result<dom::element> operator[](std::string_view key) const noexcept;
simdjson_inline simdjson_result<dom::element> operator[](const char *key) const noexcept; simdjson_inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
File diff suppressed because it is too large Load Diff
+159
View File
@@ -0,0 +1,159 @@
#ifndef SIMDJSON_DOM_FRACTURED_JSON_H
#define SIMDJSON_DOM_FRACTURED_JSON_H
#include "simdjson/dom/base.h"
#include "simdjson/dom/element.h"
namespace simdjson {
/**
* Configuration options for FracturedJson formatting.
*
* FracturedJson intelligently chooses between different layout strategies
* (inline, compact multiline, table, expanded) based on content complexity,
* length, and structure similarity.
*/
struct fractured_json_options {
/**
* Maximum total characters per line (default: 120).
* Content exceeding this will be expanded to multiple lines.
*/
size_t max_total_line_length = 120;
/**
* Maximum length for inlined elements (default: 80).
* Simple arrays/objects shorter than this may be rendered inline.
*/
size_t max_inline_length = 80;
/**
* Maximum nesting depth for inline rendering (default: 2).
* Elements with complexity exceeding this will be expanded.
* Complexity 0 = scalar, 1 = flat array/object, 2 = one level of nesting.
*/
size_t max_inline_complexity = 2;
/**
* Maximum complexity for compact array formatting (default: 1).
* Arrays with elements of this complexity or less may have multiple
* items per line.
*/
size_t max_compact_array_complexity = 1;
/**
* Number of spaces per indentation level (default: 4).
*/
size_t indent_spaces = 4;
/**
* Enable tabular formatting for arrays of similar objects (default: true).
* When enabled, arrays of objects with identical keys are formatted
* as aligned tables.
*/
bool enable_table_format = true;
/**
* Minimum number of rows to trigger table mode (default: 3).
*/
size_t min_table_rows = 3;
/**
* Similarity threshold for table detection (default: 0.8).
* Objects must share at least this fraction of keys to be formatted
* as a table.
*/
double table_similarity_threshold = 0.8;
/**
* Enable compact multiline arrays (default: true).
* When enabled, arrays of simple elements may have multiple items
* per line.
*/
bool enable_compact_multiline = true;
/**
* Maximum array items per line in compact mode (default: 10).
*/
size_t max_items_per_line = 10;
/**
* Add space inside brackets for simple containers (default: true).
* When true: { "key": "value" }
* When false: {"key": "value"}
*/
bool simple_bracket_padding = true;
/**
* Add space after colons (default: true).
* When true: "key": "value"
* When false: "key":"value"
*/
bool colon_padding = true;
/**
* Add space after commas in inline content (default: true).
* When true: [1, 2, 3]
* When false: [1,2,3]
*/
bool comma_padding = true;
};
/**
* Format JSON using FracturedJson formatting with default options.
*
* FracturedJson produces human-readable yet compact output by intelligently
* choosing between inline, compact multiline, table, and expanded layouts.
*
* dom::parser parser;
* element doc = parser.parse(json_string);
* cout << fractured_json(doc) << endl;
*/
template <class T>
std::string fractured_json(T x);
/**
* Format JSON using FracturedJson formatting with custom options.
*
* dom::parser parser;
* element doc = parser.parse(json_string);
* fractured_json_options opts;
* opts.max_total_line_length = 80;
* cout << fractured_json(doc, opts) << endl;
*/
template <class T>
std::string fractured_json(T x, const fractured_json_options& options);
#if SIMDJSON_EXCEPTIONS
template <class T>
std::string fractured_json(simdjson_result<T> x);
template <class T>
std::string fractured_json(simdjson_result<T> x, const fractured_json_options& options);
#endif
/**
* Format a JSON string using FracturedJson formatting.
*
* This is useful for formatting output from the builder/static reflection API
* or any valid JSON string.
*
* // With static reflection
* MyStruct data = {...};
* auto minified = simdjson::to_json_string(data);
* auto formatted = simdjson::fractured_json_string(minified.value());
*
* // Or with any JSON string
* std::string json = R"({"key":"value"})";
* auto formatted = simdjson::fractured_json_string(json);
*/
inline std::string fractured_json_string(std::string_view json_str);
/**
* Format a JSON string using FracturedJson formatting with custom options.
*/
inline std::string fractured_json_string(std::string_view json_str,
const fractured_json_options& options);
} // namespace simdjson
#endif // SIMDJSON_DOM_FRACTURED_JSON_H
+3 -1
View File
@@ -36,10 +36,11 @@ inline bool parser::dump_raw_tape(std::ostream &os) const noexcept {
} }
inline simdjson_result<size_t> parser::read_file(std::string_view path) noexcept { inline simdjson_result<size_t> parser::read_file(std::string_view path) noexcept {
const std::string path_copy(path);
// Open the file // Open the file
SIMDJSON_PUSH_DISABLE_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS
SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe
std::FILE *fp = std::fopen(path.data(), "rb"); std::FILE *fp = std::fopen(path_copy.c_str(), "rb");
SIMDJSON_POP_DISABLE_WARNINGS SIMDJSON_POP_DISABLE_WARNINGS
if (fp == nullptr) { if (fp == nullptr) {
@@ -132,6 +133,7 @@ inline simdjson_result<element> parser::parse_into_document(document& provided_d
buf += 3; buf += 3;
len -= 3; len -= 3;
} }
implementation->_number_as_string = _number_as_string;
_error = implementation->parse(buf, len, provided_doc); _error = implementation->parse(buf, len, provided_doc);
if (_error) { return _error; } if (_error) { return _error; }
+10
View File
@@ -610,6 +610,13 @@ public:
inline bool dump_raw_tape(std::ostream &os) const noexcept; inline bool dump_raw_tape(std::ostream &os) const noexcept;
/**
* When enabled, big integers (exceeding uint64 range) are stored as strings
* in the tape instead of returning BIGINT_ERROR. Default: false.
*/
inline void number_as_string(bool enabled) noexcept { _number_as_string = enabled; }
inline bool number_as_string() const noexcept { return _number_as_string; }
private: private:
/** /**
* The maximum document length this parser will automatically support. * The maximum document length this parser will automatically support.
@@ -618,6 +625,9 @@ private:
*/ */
size_t _max_capacity; size_t _max_capacity;
/** Whether to store big integers as strings instead of returning BIGINT_ERROR */
bool _number_as_string{false};
/** /**
* The loaded buffer (reused each time load() is called) * The loaded buffer (reused each time load() is called)
*/ */
+6
View File
@@ -460,6 +460,12 @@ inline void string_builder<serializer>::append(simdjson::dom::element value) {
case tape_type::STRING: case tape_type::STRING:
format.string(iter.get_string_view()); format.string(iter.get_string_view());
break; break;
case tape_type::BIGINT: {
// Big integer stored as string — output raw digits (no quotes)
auto sv = iter.get_string_view();
format.chars(sv.data(), sv.data() + sv.size());
break;
}
case tape_type::INT64: case tape_type::INT64:
format.number(iter.next_tape_value<int64_t>()); format.number(iter.next_tape_value<int64_t>());
iter.json_index++; // numbers take up 2 spots, so we need to increment iter.json_index++; // numbers take up 2 spots, so we need to increment
+1 -1
View File
@@ -8,7 +8,7 @@
namespace simdjson { namespace simdjson {
inline bool is_fatal(error_code error) noexcept { inline bool is_fatal(error_code error) noexcept {
return error == TAPE_ERROR || error == INCOMPLETE_ARRAY_OR_OBJECT; return error == TAPE_ERROR || error == INCOMPLETE_ARRAY_OR_OBJECT || error == OUT_OF_ORDER_ITERATION || error == DEPTH_ERROR;
} }
namespace internal { namespace internal {
@@ -41,6 +41,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) {
#endif// _MSC_VER #endif// _MSC_VER
} }
simdjson_inline int trailing_zeroes(uint64_t input_num) {
#ifdef _MSC_VER
unsigned long trailing_zero = 0;
// Search the mask data from least significant bit (LSB)
// to most significant bit (MSB) for a set bit (1).
if (_BitScanForward64(&trailing_zero, input_num))
return (int)trailing_zero;
else return 64;
#else
return __builtin_ctzll(input_num);
#endif// _MSC_VER
}
} // unnamed namespace } // unnamed namespace
} // namespace fallback } // namespace fallback
} // namespace simdjson } // namespace simdjson
+8
View File
@@ -0,0 +1,8 @@
#ifndef SIMDJSON_FALLBACK_BUILDER_H
#define SIMDJSON_FALLBACK_BUILDER_H
#include "simdjson/fallback/begin.h"
#include "simdjson/generic/builder/amalgamated.h"
#include "simdjson/fallback/end.h"
#endif // SIMDJSON_FALLBACK_BUILDER_H
+5 -3
View File
@@ -17,10 +17,12 @@
#include "simdjson/arm64/begin.h" #include "simdjson/arm64/begin.h"
#elif SIMDJSON_IMPLEMENTATION_PPC64 #elif SIMDJSON_IMPLEMENTATION_PPC64
#include "simdjson/ppc64/begin.h" #include "simdjson/ppc64/begin.h"
#elif SIMDJSON_IMPLEMENTATION_LSX
#include "simdjson/lsx/begin.h"
#elif SIMDJSON_IMPLEMENTATION_LASX #elif SIMDJSON_IMPLEMENTATION_LASX
#include "simdjson/lasx/begin.h" #include "simdjson/lasx/begin.h"
#elif SIMDJSON_IMPLEMENTATION_LSX
#include "simdjson/lsx/begin.h"
#elif SIMDJSON_IMPLEMENTATION_RVV_VLS
#include "simdjson/rvv-vls/begin.h"
#elif SIMDJSON_IMPLEMENTATION_FALLBACK #elif SIMDJSON_IMPLEMENTATION_FALLBACK
#include "simdjson/fallback/begin.h" #include "simdjson/fallback/begin.h"
#else #else
@@ -48,4 +50,4 @@ enum class number_type {
} // namespace SIMDJSON_IMPLEMENTATION } // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson } // namespace simdjson
#endif // SIMDJSON_GENERIC_BASE_H #endif // SIMDJSON_GENERIC_BASE_H
@@ -0,0 +1,13 @@
#if defined(SIMDJSON_CONDITIONAL_INCLUDE) && !defined(SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H)
#error simdjson/generic/builder/dependencies.h must be included before simdjson/generic/builder/amalgamated.h!
#endif
#include "simdjson/generic/builder/json_string_builder.h"
#include "simdjson/generic/builder/json_builder.h"
#include "simdjson/generic/builder/fractured_json_builder.h"
// JSON builder inline definitions
#include "simdjson/generic/builder/json_string_builder-inl.h"
@@ -0,0 +1,14 @@
#ifdef SIMDJSON_CONDITIONAL_INCLUDE
#error simdjson/generic/builder/dependencies.h must be included before defining SIMDJSON_CONDITIONAL_INCLUDE!
#endif
#ifndef SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
#define SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
// Internal headers needed for builder generics.
// All includes not under simdjson/generic/builder must be here!
// Otherwise, amalgamation will fail.
#include "simdjson/concepts.h"
#include "simdjson/dom/fractured_json.h"
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
@@ -0,0 +1,117 @@
#ifndef SIMDJSON_GENERIC_FRACTURED_JSON_BUILDER_H
#define SIMDJSON_GENERIC_FRACTURED_JSON_BUILDER_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#include "simdjson/generic/builder/json_builder.h"
#include "simdjson/dom/fractured_json.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace builder {
/**
* Serialize an object to a FracturedJson-formatted string.
*
* FracturedJson produces human-readable yet compact JSON output by intelligently
* choosing between different layout strategies (inline, compact multiline, table,
* expanded) based on content complexity, length, and structure similarity.
*
* This function combines the builder's serialization with FracturedJson formatting:
* 1. Serializes the object to minified JSON using reflection
* 2. Parses and reformats using FracturedJson
*
* Example:
* struct User { int id; std::string name; bool active; };
* User user{1, "Alice", true};
* auto result = to_fractured_json_string(user);
* // result.value() == "{ \"id\": 1, \"name\": \"Alice\", \"active\": true }"
*
* @param obj The object to serialize (must be a reflectable type)
* @param opts FracturedJson formatting options
* @param initial_capacity Initial buffer capacity for serialization
* @return The formatted JSON string, or an error
*/
template <class T>
simdjson_warn_unused simdjson_result<std::string> to_fractured_json_string(
const T& obj,
const fractured_json_options& opts = {},
size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
// Step 1: Serialize to minified JSON
std::string formatted;
auto error = to_json_string(obj, initial_capacity).get(formatted);
if (error) {
return error;
}
// Step 2: Reformat with FracturedJson
return fractured_json_string(formatted, opts);
}
/**
* Extract specific fields from an object and format with FracturedJson.
*
* Example:
* struct User { int id; std::string name; std::string email; bool active; };
* User user{1, "Alice", "alice@example.com", true};
* auto result = extract_fractured_json<"id", "name">(user);
* // result.value() == "{ \"id\": 1, \"name\": \"Alice\" }"
*
* @param obj The object to serialize
* @param opts FracturedJson formatting options
* @param initial_capacity Initial buffer capacity for serialization
* @return The formatted JSON string containing only the specified fields
*/
template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
simdjson_warn_unused simdjson_result<std::string> extract_fractured_json(
const T& obj,
const fractured_json_options& opts = {},
size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
// Step 1: Extract fields to minified JSON
std::string formatted;
auto error = extract_from<FieldNames...>(obj, initial_capacity).get(formatted);
if (error) {
return error;
}
// Step 2: Reformat with FracturedJson
return fractured_json_string(formatted, opts);
}
} // namespace builder
} // namespace SIMDJSON_IMPLEMENTATION
// Global namespace convenience functions
/**
* Serialize an object to a FracturedJson-formatted string.
* Global namespace version for convenience.
*/
template <class T>
simdjson_warn_unused simdjson_result<std::string> to_fractured_json_string(
const T& obj,
const fractured_json_options& opts = {},
size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
return SIMDJSON_IMPLEMENTATION::builder::to_fractured_json_string(obj, opts, initial_capacity);
}
/**
* Extract specific fields from an object and format with FracturedJson.
* Global namespace version for convenience.
*/
template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
simdjson_warn_unused simdjson_result<std::string> extract_fractured_json(
const T& obj,
const fractured_json_options& opts = {},
size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
return SIMDJSON_IMPLEMENTATION::builder::extract_fractured_json<FieldNames...>(obj, opts, initial_capacity);
}
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_FRACTURED_JSON_BUILDER_H
@@ -1,7 +1,3 @@
/**
* This file is part of the builder API. It is temporarily in the ondemand directory
* but we will move it to a builder directory later.
*/
#ifndef SIMDJSON_GENERIC_BUILDER_H #ifndef SIMDJSON_GENERIC_BUILDER_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE #ifndef SIMDJSON_CONDITIONAL_INCLUDE
@@ -279,7 +275,7 @@ simdjson_warn_unused simdjson_result<std::string> to_json_string(const Z &z, siz
} }
template <class Z> template <class Z>
simdjson_warn_unused simdjson_error to_json(const Z &z, std::string &s, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) {
string_builder b(initial_capacity); string_builder b(initial_capacity);
append(b, z); append(b, z);
std::string_view view; std::string_view view;
@@ -356,7 +352,7 @@ simdjson_warn_unused simdjson_result<std::string> to_json(const Z &z, size_t ini
return std::string(s); return std::string(s);
} }
template <class Z> template <class Z>
simdjson_warn_unused simdjson_error to_json(const Z &z, std::string &s, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity); SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
SIMDJSON_IMPLEMENTATION::builder::append(b, z); SIMDJSON_IMPLEMENTATION::builder::append(b, z);
std::string_view view; std::string_view view;
@@ -1,9 +1,6 @@
/**
* This file is part of the builder API. It is temporarily in the ondemand
* directory but we will move it to a builder directory later.
*/
#include <array> #include <array>
#include <cstring> #include <cstring>
#include <limits>
#include <type_traits> #include <type_traits>
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H #ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
@@ -31,12 +28,50 @@
#define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1
#endif #endif
#endif #endif
#if defined(__loongarch_sx)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX
#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1
#endif
#endif
#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \
defined(__riscv_vector)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV
#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1
#endif
#endif
#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64
#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1
#endif
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_NEON #if SIMDJSON_EXPERIMENTAL_HAS_NEON
#include <arm_neon.h> #include <arm_neon.h>
#ifdef _MSC_VER
#include <intrin.h>
#endif
#endif #endif
#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 #if SIMDJSON_EXPERIMENTAL_HAS_SSE2
#include <emmintrin.h> #include <emmintrin.h>
#ifdef _MSC_VER
#include <intrin.h>
#endif #endif
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_LSX
#include <lsxintrin.h>
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_RVV
#include <riscv_vector.h>
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_PPC64
#include <altivec.h>
#ifdef bool
#undef bool
#endif
#ifdef vector
#undef vector
#endif
#endif
namespace simdjson { namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION { namespace SIMDJSON_IMPLEMENTATION {
@@ -137,16 +172,45 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
} }
return _mm_movemask_epi8(running) != 0; return _mm_movemask_epi8(running) != 0;
} }
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) {
return simple_needs_escaping(view);
}
size_t i = 0;
__vector unsigned char running = vec_splats((unsigned char)0);
__vector unsigned char v34 = vec_splats((unsigned char)34);
__vector unsigned char v92 = vec_splats((unsigned char)92);
__vector unsigned char v32 = vec_splats((unsigned char)32);
for (; i + 15 < view.size(); i += 16) {
__vector unsigned char word =
vec_vsx_ld(0, reinterpret_cast<const unsigned char *>(view.data() + i));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92));
running = vec_or(running,
(__vector unsigned char)vec_cmplt(word, v32));
}
if (i < view.size()) {
__vector unsigned char word = vec_vsx_ld(
0, reinterpret_cast<const unsigned char *>(view.data() + view.length() - 16));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92));
running = vec_or(running,
(__vector unsigned char)vec_cmplt(word, v32));
}
return !vec_all_eq(running, vec_splats((unsigned char)0));
}
#else #else
simdjson_inline bool fast_needs_escaping(std::string_view view) { simdjson_inline bool fast_needs_escaping(std::string_view view) {
return simple_needs_escaping(view); return simple_needs_escaping(view);
} }
#endif #endif
SIMDJSON_CONSTEXPR_LAMBDA inline size_t // Scalar fallback for finding next quotable character
find_next_json_quotable_character(const std::string_view view, SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t
size_t location) noexcept { find_next_json_quotable_character_scalar(const std::string_view view,
size_t location) noexcept {
for (auto pos = view.begin() + location; pos != view.end(); ++pos) { for (auto pos = view.begin() + location; pos != view.end(); ++pos) {
if (json_quotable_character[static_cast<uint8_t>(*pos)]) { if (json_quotable_character[static_cast<uint8_t>(*pos)]) {
return pos - view.begin(); return pos - view.begin();
@@ -155,6 +219,213 @@ find_next_json_quotable_character(const std::string_view view,
return size_t(view.size()); return size_t(view.size());
} }
// SIMD-accelerated position finding that directly locates the first quotable
// character, combining detection and position extraction in a single pass to
// minimize redundant work.
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
// SIMD constants for characters requiring escape
uint8x16_t v34 = vdupq_n_u8(34); // '"'
uint8x16_t v92 = vdupq_n_u8(92); // '\\'
uint8x16_t v32 = vdupq_n_u8(32); // control char threshold
while (remaining >= 16) {
uint8x16_t word = vld1q_u8(ptr);
// Check for quotable characters: '"', '\\', or control chars (< 32)
uint8x16_t needs_escape = vceqq_u8(word, v34);
needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92));
needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32));
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4);
const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
if(mask != 0) {
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
auto trailing_zero = trailing_zeroes(mask);
return offset + (trailing_zero >> 2);
}
ptr += 16;
remaining -= 16;
}
// Scalar fallback for remaining bytes
size_t current = len - remaining;
return find_next_json_quotable_character_scalar(view, current);
}
#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
// SIMD constants
__m128i v34 = _mm_set1_epi8(34); // '"'
__m128i v92 = _mm_set1_epi8(92); // '\\'
__m128i v31 = _mm_set1_epi8(31); // for control char detection
while (remaining >= 16) {
__m128i word = _mm_loadu_si128(reinterpret_cast<const __m128i *>(ptr));
// Check for quotable characters
__m128i needs_escape = _mm_cmpeq_epi8(word, v34);
needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92));
needs_escape = _mm_or_si128(
needs_escape,
_mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128()));
int mask = _mm_movemask_epi8(needs_escape);
if (mask != 0) {
// Found quotable character - use trailing zero count to find position
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
return offset + trailing_zeroes(mask);
}
ptr += 16;
remaining -= 16;
}
// Scalar fallback for remaining bytes
size_t current = len - remaining;
return find_next_json_quotable_character_scalar(view, current);
}
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
//SIMD constants for characters requiring escape
__m128i v34 = __lsx_vreplgr2vr_b(34); // '"'
__m128i v92 = __lsx_vreplgr2vr_b(92); // '\\'
__m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold
while (remaining >= 16){
__m128i word = __lsx_vld(ptr, 0);
//Check for the quotable characters: '"', '\\', or control char (<32)
__m128i needs_escape = __lsx_vseq_b(word, v34);
needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92));
needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32));
if (!__lsx_bz_v(needs_escape)){
//Found quotable character - extract exact byte position
uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0);
uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1);
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
if ( lo != 0) {
return offset + trailing_zeroes(lo) / 8;
} else {
return offset + 8 + trailing_zeroes(hi) / 8;
}
}
ptr += 16;
remaining -= 16;
}
size_t current = len - remaining;
return find_next_json_quotable_character_scalar(view, current);
}
#elif SIMDJSON_EXPERIMENTAL_HAS_RVV
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
while (remaining > 0) {
size_t vl = __riscv_vsetvl_e8m1(remaining);
vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl);
// Check for quotable characters: '"', '\\', or control chars (< 32)
vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl);
needs_escape = __riscv_vmor(needs_escape,
__riscv_vmseq(word, (uint8_t)92, vl), vl);
needs_escape = __riscv_vmor(needs_escape,
__riscv_vmsltu(word, (uint8_t)32, vl), vl);
long first = __riscv_vfirst(needs_escape, vl);
if (first >= 0) {
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
return offset + first;
}
ptr += vl;
remaining -= vl;
}
return len;
}
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
// SIMD constants for characters requiring escape
__vector unsigned char v34 = vec_splats((unsigned char)34); // '"'
__vector unsigned char v92 = vec_splats((unsigned char)92); // '\\'
__vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold
// Bitmask for vec_vbpermq to extract one bit per byte
const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50,
0x48, 0x40, 0x38, 0x30, 0x28, 0x20,
0x18, 0x10, 0x08, 0x00};
while (remaining >= 16) {
__vector unsigned char word =
vec_vsx_ld(0, reinterpret_cast<const unsigned char *>(ptr));
// Check for quotable characters: '"', '\\', or control chars (< 32)
__vector unsigned char needs_escape =
(__vector unsigned char)vec_cmpeq(word, v34);
needs_escape = vec_or(needs_escape,
(__vector unsigned char)vec_cmpeq(word, v92));
needs_escape = vec_or(needs_escape,
(__vector unsigned char)vec_cmplt(word, v32));
__vector unsigned long long result =
(__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask);
#ifdef __LITTLE_ENDIAN__
unsigned int mask = static_cast<unsigned int>(result[1]);
#else
unsigned int mask = static_cast<unsigned int>(result[0]);
#endif
if (mask != 0) {
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
return offset + __builtin_ctz(mask);
}
ptr += 16;
remaining -= 16;
}
// Scalar fallback for remaining bytes
size_t current = len - remaining;
return find_next_json_quotable_character_scalar(view, current);
}
#else
SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
return find_next_json_quotable_character_scalar(view, location);
}
#endif
SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = {
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006",
"\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r",
@@ -167,7 +438,7 @@ SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = {
// control characters (U+0000 through U+001F). There are two-character sequence // control characters (U+0000 through U+001F). There are two-character sequence
// escape representations of some popular characters: // escape representations of some popular characters:
// \", \\, \b, \f, \n, \r, \t. // \", \\, \b, \f, \n, \r, \t.
SIMDJSON_CONSTEXPR_LAMBDA void escape_json_char(char c, char *&out) { SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&out) {
if (c == '"') { if (c == '"') {
memcpy(out, "\\\"", 2); memcpy(out, "\\\"", 2);
out += 2; out += 2;
@@ -181,14 +452,20 @@ SIMDJSON_CONSTEXPR_LAMBDA void escape_json_char(char c, char *&out) {
} }
} }
// Writes the escaped version of input to out, returning the number of bytes
// written. Uses SIMD position finding to locate quotable characters efficiently.
inline size_t write_string_escaped(const std::string_view input, char *out) { inline size_t write_string_escaped(const std::string_view input, char *out) {
size_t mysize = input.size(); size_t mysize = input.size();
if (!fast_needs_escaping(input)) { // fast path!
// Use SIMD position finder directly - it returns mysize if no escape needed
size_t location = find_next_json_quotable_character(input, 0);
if (location == mysize) {
// Fast path: no escaping needed
memcpy(out, input.data(), input.size()); memcpy(out, input.data(), input.size());
return input.size(); return input.size();
} }
const char *const initout = out; const char *const initout = out;
size_t location = find_next_json_quotable_character(input, 0);
memcpy(out, input.data(), location); memcpy(out, input.data(), location);
out += location; out += location;
escape_json_char(input[location], out); escape_json_char(input[location], out);
@@ -220,7 +497,7 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
return true; return true;
} }
// check for overflow, most of the time there is no overflow // check for overflow, most of the time there is no overflow
if (simdjson_likely(position + upcoming_bytes < position)) { if (simdjson_unlikely(position + upcoming_bytes < position)) {
return false; return false;
} }
// We will rarely get here. // We will rarely get here.
@@ -387,12 +664,29 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
} }
} }
else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) { else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) {
// Process 4 digits at a time instead of 2, reducing store operations
// and divisions by approximately half for large numbers.
constexpr size_t max_number_size = 20; constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) { if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type; using unsigned_type = typename std::make_unsigned<number_type>::type;
unsigned_type pv = static_cast<unsigned_type>(v); unsigned_type pv = static_cast<unsigned_type>(v);
size_t dc = internal::digit_count(pv); size_t dc = internal::digit_count(pv);
char *write_pointer = buffer.get() + position + dc - 1; char *write_pointer = buffer.get() + position + dc - 1;
// Process 4 digits per iteration for large numbers
while (pv >= 10000) {
unsigned_type q = pv / 10000;
unsigned_type r = pv % 10000;
unsigned_type r_hi = r / 100; // High 2 digits of remainder
unsigned_type r_lo = r % 100; // Low 2 digits of remainder
// Write low 2 digits first (rightmost), then high 2 digits
memcpy(write_pointer - 1, &internal::decimal_table[r_lo * 2], 2);
memcpy(write_pointer - 3, &internal::decimal_table[r_hi * 2], 2);
write_pointer -= 4;
pv = q;
}
// Handle remaining 1-4 digits with original 2-digit loop
while (pv >= 100) { while (pv >= 100) {
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2); memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
write_pointer -= 2; write_pointer -= 2;
@@ -407,6 +701,7 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
} }
} }
else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) { else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) {
// Same 4-digit batching as unsigned path for signed integers
constexpr size_t max_number_size = 20; constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) { if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type; using unsigned_type = typename std::make_unsigned<number_type>::type;
@@ -420,6 +715,20 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
buffer.get()[position] = '-'; buffer.get()[position] = '-';
position += negative ? 1 : 0; position += negative ? 1 : 0;
char *write_pointer = buffer.get() + position + dc - 1; char *write_pointer = buffer.get() + position + dc - 1;
// Process 4 digits per iteration for large numbers
while (pv >= 10000) {
unsigned_type q = pv / 10000;
unsigned_type r = pv % 10000;
unsigned_type r_hi = r / 100;
unsigned_type r_lo = r % 100;
memcpy(write_pointer - 1, &internal::decimal_table[r_lo * 2], 2);
memcpy(write_pointer - 3, &internal::decimal_table[r_hi * 2], 2);
write_pointer -= 4;
pv = q;
}
// Handle remaining 1-4 digits
while (pv >= 100) { while (pv >= 100) {
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2); memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
write_pointer -= 2; write_pointer -= 2;
@@ -447,6 +756,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
simdjson_inline void simdjson_inline void
string_builder::escape_and_append(std::string_view input) noexcept { string_builder::escape_and_append(std::string_view input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters. // escaping might turn a control character into \x00xx so 6 characters.
// Guard against size_t overflow in the multiplication below.
if (input.size() > (std::numeric_limits<size_t>::max)() / 6) {
set_valid(false);
return;
}
if (capacity_check(6 * input.size())) { if (capacity_check(6 * input.size())) {
position += write_string_escaped(input, buffer.get() + position); position += write_string_escaped(input, buffer.get() + position);
} }
@@ -455,6 +769,11 @@ string_builder::escape_and_append(std::string_view input) noexcept {
simdjson_inline void simdjson_inline void
string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { string_builder::escape_and_append_with_quotes(std::string_view input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters. // escaping might turn a control character into \x00xx so 6 characters.
// Guard against size_t overflow in the arithmetic below.
if (input.size() > ((std::numeric_limits<size_t>::max)() - 2) / 6) {
set_valid(false);
return;
}
if (capacity_check(2 + 6 * input.size())) { if (capacity_check(2 + 6 * input.size())) {
buffer.get()[position++] = '"'; buffer.get()[position++] = '"';
position += write_string_escaped(input, buffer.get() + position); position += write_string_escaped(input, buffer.get() + position);
@@ -519,8 +838,8 @@ simdjson_inline void string_builder::append(const T &opt) {
template <typename T> template <typename T>
requires(require_custom_serialization<T>) requires(require_custom_serialization<T>)
simdjson_inline void string_builder::append(const T &val) { simdjson_inline void string_builder::append(T &&val) {
serialize(*this, val); serialize(*this, std::forward<T>(val));
} }
template <typename T> template <typename T>
@@ -1,7 +1,3 @@
/**
* This file is part of the builder API. It is temporarily in the ondemand directory
* but we will move it to a builder directory later.
*/
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_H #ifndef SIMDJSON_GENERIC_STRING_BUILDER_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE #ifndef SIMDJSON_CONDITIONAL_INCLUDE
@@ -24,9 +20,8 @@ struct has_custom_serialization : std::false_type {};
inline constexpr struct serialize_tag { inline constexpr struct serialize_tag {
template <typename T> template <typename T>
requires custom_deserializable<T> constexpr void operator()(SIMDJSON_IMPLEMENTATION::builder::string_builder& b, T&& obj) const{
constexpr void operator()(SIMDJSON_IMPLEMENTATION::builder::string_builder& b, T& obj) const{ return tag_invoke(*this, b, std::forward<T>(obj));
return tag_invoke(*this, b, obj);
} }
@@ -165,7 +160,7 @@ public:
template <typename T> template <typename T>
requires(require_custom_serialization<T>) requires(require_custom_serialization<T>)
simdjson_inline void append(const T &val); simdjson_inline void append(T &&val);
// Support for string-like types // Support for string-like types
template <typename T> template <typename T>
@@ -285,7 +280,7 @@ simdjson_warn_unused simdjson_result<std::string> to_json(const Z &z, size_t ini
return std::string(s); return std::string(s);
} }
template <class Z> template <class Z>
simdjson_warn_unused simdjson_error to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::SIMDJSON_IMPLEMENTATION::builder::string_builder::DEFAULT_INITIAL_CAPACITY) {
simdjson::SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity); simdjson::SIMDJSON_IMPLEMENTATION::builder::string_builder b(initial_capacity);
b.append(z); b.append(z);
std::string_view sv; std::string_view sv;
@@ -1,4 +1,4 @@
#if defined(SIMDJSON_CONDITIONAL_INCLUDE) && !defined(SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H) #if defined(SIMDJSON_CONDITIONAL_INCLUDE) && !defined(SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H)
#error simdjson/generic/ondemand/dependencies.h must be included before simdjson/generic/ondemand/amalgamated.h! #error simdjson/generic/ondemand/dependencies.h must be included before simdjson/generic/ondemand/amalgamated.h!
#endif #endif
@@ -14,9 +14,6 @@
#include "simdjson/generic/ondemand/raw_json_string.h" #include "simdjson/generic/ondemand/raw_json_string.h"
#include "simdjson/generic/ondemand/parser.h" #include "simdjson/generic/ondemand/parser.h"
// JSON builder - needed for extract_into functionality
#include "simdjson/generic/ondemand/json_string_builder.h"
// All other declarations // All other declarations
#include "simdjson/generic/ondemand/array.h" #include "simdjson/generic/ondemand/array.h"
#include "simdjson/generic/ondemand/array_iterator.h" #include "simdjson/generic/ondemand/array_iterator.h"
@@ -44,13 +41,9 @@
#include "simdjson/generic/ondemand/object_iterator-inl.h" #include "simdjson/generic/ondemand/object_iterator-inl.h"
#include "simdjson/generic/ondemand/parser-inl.h" #include "simdjson/generic/ondemand/parser-inl.h"
#include "simdjson/generic/ondemand/raw_json_string-inl.h" #include "simdjson/generic/ondemand/raw_json_string-inl.h"
#include "simdjson/generic/ondemand/serialization-inl.h"
#include "simdjson/generic/ondemand/token_iterator-inl.h" #include "simdjson/generic/ondemand/token_iterator-inl.h"
#include "simdjson/generic/ondemand/value_iterator-inl.h" #include "simdjson/generic/ondemand/value_iterator-inl.h"
#include "simdjson/generic/ondemand/serialization-inl.h"
// JSON builder inline definitions
#include "simdjson/generic/ondemand/json_string_builder-inl.h"
#include "simdjson/generic/ondemand/json_builder.h"
// JSON path accessor (compile-time) - must be after inline definitions // JSON path accessor (compile-time) - must be after inline definitions
#include "simdjson/generic/ondemand/compile_time_accessors.h" #include "simdjson/generic/ondemand/compile_time_accessors.h"
+35 -44
View File
@@ -170,46 +170,34 @@ inline simdjson_result<value> array::at_path(std::string_view json_path) noexcep
return at_pointer(json_pointer); return at_pointer(json_pointer);
} }
inline simdjson_result<std::vector<value>> array::at_path_with_wildcard(std::string_view json_path) noexcept { #if SIMDJSON_SUPPORTS_CONCEPTS
std::vector<value> result; template <typename Func>
requires std::invocable<Func, value>
#else
template <typename Func>
#endif
inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
auto result_pair = get_next_key_and_json_path(json_path); auto result_pair = get_next_key_and_json_path(json_path);
std::string_view key = result_pair.first; std::string_view key = result_pair.first;
std::string_view remaining_path = result_pair.second; std::string_view remaining_path = result_pair.second;
// Wildcard case // Wildcard case
if(key=="*"){ if (key=="*"){
for(auto element: *this){ for(auto element: *this) {
value val;
if(element.error()){ SIMDJSON_TRY(element.get(val));
return element.error(); if (remaining_path.empty()) {
} callback(val);
} else {
if(remaining_path.empty()){ error_code err = element.for_each_at_path_with_wildcard(remaining_path, callback);
// Use value_unsafe() because we've already checked for errors above. if(err) { return err; }
// The 'element' is a simdjson_result<value> wrapper, and we need to extract
// the underlying value. value_unsafe() is safe here because error() returned false.
result.push_back(std::move(element).value_unsafe());
}else{
auto nested_result = element.at_path_with_wildcard(remaining_path);
if(nested_result.error()){
return nested_result.error();
}
// Same logic as above.
std::vector<value> nested_matches = std::move(nested_result).value_unsafe();
result.insert(result.end(),
std::make_move_iterator(nested_matches.begin()),
std::make_move_iterator(nested_matches.end()));
} }
} }
return result; return SUCCESS;
}else{ } else {
// Specific index case in which we access the element at the given index // Specific index case in which we access the element at the given index
size_t idx=0; size_t idx = 0;
for(char c:key){ for (char c : key) {
if(c < '0' || c > '9'){ if(c < '0' || c > '9'){
return INVALID_JSON_POINTER; return INVALID_JSON_POINTER;
} }
@@ -217,16 +205,13 @@ inline simdjson_result<std::vector<value>> array::at_path_with_wildcard(std::str
} }
auto element = at(idx); auto element = at(idx);
value val;
if(element.error()){ SIMDJSON_TRY(element.get(val));
return element.error(); if (remaining_path.empty()){
} callback(val);
return SUCCESS;
if(remaining_path.empty()){ } else {
result.push_back(std::move(element).value_unsafe()); return element.for_each_at_path_with_wildcard(remaining_path, callback);
return result;
}else{
return element.at_path_with_wildcard(remaining_path);
} }
} }
} }
@@ -289,9 +274,15 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdj
if (error()) { return error(); } if (error()) { return error(); }
return first.at_path(json_path); return first.at_path(json_path);
} }
simdjson_inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array>::at_path_with_wildcard(std::string_view json_path) noexcept { #if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, SIMDJSON_IMPLEMENTATION::ondemand::value>
#else
template <typename Func>
#endif
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array>::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
if (error()) { return error(); } if (error()) { return error(); }
return first.at_path_with_wildcard(json_path); return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
} }
simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array>::raw_json() noexcept { simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array>::raw_json() noexcept {
if (error()) { return error(); } if (error()) { return error(); }
+18 -4
View File
@@ -119,13 +119,21 @@ public:
inline simdjson_result<value> at_path(std::string_view json_path) noexcept; inline simdjson_result<value> at_path(std::string_view json_path) noexcept;
/** /**
* Get all values matching the given JSONPath expression with wildcard support. * Call the provided callback for each value matching the given JSONPath
* expression with wildcard support.
* Supports wildcard patterns like "[*]" to match all array elements. * Supports wildcard patterns like "[*]" to match all array elements.
* *
* @param json_path JSONPath expression with wildcards * @param json_path JSONPath expression with wildcards
* @return Vector of values matching the wildcard pattern * @param callback Function called for each matching value
* @return error_code indicating success or failure
*/ */
inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept; #if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, value>
#else
template <typename Func>
#endif
inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept;
/** /**
* Consumes the array and returns a string_view instance corresponding to the * Consumes the array and returns a string_view instance corresponding to the
@@ -249,7 +257,13 @@ public:
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at(size_t index) noexcept; simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at(size_t index) noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept; simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_path(std::string_view json_path) noexcept;
simdjson_inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) noexcept; #if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, SIMDJSON_IMPLEMENTATION::ondemand::value>
#else
template <typename Func>
#endif
simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept;
simdjson_inline simdjson_result<std::string_view> raw_json() noexcept; simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_CONCEPTS
// TODO: move this code into object-inl.h // TODO: move this code into object-inl.h
@@ -17,6 +17,10 @@ simdjson_inline array_iterator::array_iterator(const value_iterator &_iter) noex
{} {}
simdjson_inline simdjson_result<value> array_iterator::operator*() noexcept { simdjson_inline simdjson_result<value> array_iterator::operator*() noexcept {
#if SIMDJSON_DEVELOPMENT_CHECKS
SIMDJSON_ASSUME(!has_been_referenced);
has_been_referenced = true;
#endif
if (iter.error()) { iter.abandon(); return iter.error(); } if (iter.error()) { iter.abandon(); return iter.error(); }
return value(iter.child()); return value(iter.child());
} }
@@ -27,6 +31,9 @@ simdjson_inline bool array_iterator::operator!=(const array_iterator &) const no
return iter.is_open(); return iter.is_open();
} }
simdjson_inline array_iterator &array_iterator::operator++() noexcept { simdjson_inline array_iterator &array_iterator::operator++() noexcept {
#if SIMDJSON_DEVELOPMENT_CHECKS
has_been_referenced = false;
#endif
error_code error; error_code error;
// PERF NOTE this is a safety rail ... users should exit loops as soon as they receive an error, so we'll never get here. // PERF NOTE this is a safety rail ... users should exit loops as soon as they receive an error, so we'll never get here.
// However, it does not seem to make a perf difference, so we add it out of an abundance of caution. // However, it does not seem to make a perf difference, so we add it out of an abundance of caution.
@@ -2,6 +2,7 @@
#ifndef SIMDJSON_CONDITIONAL_INCLUDE #ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_H #define SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_H
#include <iterator>
#include "simdjson/generic/implementation_simdjson_result_base.h" #include "simdjson/generic/implementation_simdjson_result_base.h"
#include "simdjson/generic/ondemand/base.h" #include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/ondemand/value_iterator.h" #include "simdjson/generic/ondemand/value_iterator.h"
@@ -17,11 +18,17 @@ namespace ondemand {
* *
* This is an input_iterator, meaning: * This is an input_iterator, meaning:
* - It is forward-only * - It is forward-only
* - * must be called exactly once per element. * - * must be called at most once per element.
* - ++ must be called exactly once in between each * (*, ++, *, ++, * ...) * - ++ must be called exactly once in between each * (*, ++, *, ++, * ...)
*/ */
class array_iterator { class array_iterator {
public: public:
using iterator_category = std::input_iterator_tag;
using value_type = simdjson_result<value>;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = value_type;
/** Create a new, invalid array iterator. */ /** Create a new, invalid array iterator. */
simdjson_inline array_iterator() noexcept = default; simdjson_inline array_iterator() noexcept = default;
@@ -65,6 +72,9 @@ public:
simdjson_warn_unused simdjson_inline bool at_end() const noexcept; simdjson_warn_unused simdjson_inline bool at_end() const noexcept;
private: private:
#if SIMDJSON_DEVELOPMENT_CHECKS
bool has_been_referenced{false};
#endif
value_iterator iter{}; value_iterator iter{};
simdjson_inline array_iterator(const value_iterator &iter) noexcept; simdjson_inline array_iterator(const value_iterator &iter) noexcept;
@@ -82,6 +92,12 @@ namespace simdjson {
template<> template<>
struct simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> { struct simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base<SIMDJSON_IMPLEMENTATION::ondemand::array_iterator> {
using iterator_category = std::input_iterator_tag;
using value_type = simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = value_type;
simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::array_iterator &&value) noexcept; ///< @private simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::array_iterator &&value) noexcept; ///< @private
simdjson_inline simdjson_result(error_code error) noexcept; ///< @private simdjson_inline simdjson_result(error_code error) noexcept; ///< @private
simdjson_inline simdjson_result() noexcept = default; simdjson_inline simdjson_result() noexcept = default;
@@ -8,7 +8,6 @@
// Internal headers needed for ondemand generics. // Internal headers needed for ondemand generics.
// All includes not under simdjson/generic/ondemand must be here! // All includes not under simdjson/generic/ondemand must be here!
// Otherwise, amalgamation will fail. // Otherwise, amalgamation will fail.
#include "simdjson/concepts.h"
#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY #include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY
#include "simdjson/implementation.h" #include "simdjson/implementation.h"
#include "simdjson/padded_string.h" #include "simdjson/padded_string.h"
@@ -16,6 +16,8 @@
#include "simdjson/generic/ondemand/deserialize.h" #include "simdjson/generic/ondemand/deserialize.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE #endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <limits>
namespace simdjson { namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION { namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand { namespace ondemand {
@@ -131,6 +133,18 @@ simdjson_inline simdjson_result<int64_t> document::get_int64() noexcept {
simdjson_inline simdjson_result<int64_t> document::get_int64_in_string() noexcept { simdjson_inline simdjson_result<int64_t> document::get_int64_in_string() noexcept {
return get_root_value_iterator().get_root_int64_in_string(true); return get_root_value_iterator().get_root_int64_in_string(true);
} }
simdjson_inline simdjson_result<uint32_t> document::get_uint32() noexcept {
uint64_t result;
SIMDJSON_TRY(get_uint64().get(result));
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<uint32_t>(result);
}
simdjson_inline simdjson_result<int32_t> document::get_int32() noexcept {
int64_t result;
SIMDJSON_TRY(get_int64().get(result));
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<int32_t>(result);
}
simdjson_inline simdjson_result<double> document::get_double() noexcept { simdjson_inline simdjson_result<double> document::get_double() noexcept {
return get_root_value_iterator().get_root_double(true); return get_root_value_iterator().get_root_double(true);
} }
@@ -164,6 +178,8 @@ template<> simdjson_inline simdjson_result<std::string_view> document::get() & n
template<> simdjson_inline simdjson_result<double> document::get() & noexcept { return get_double(); } template<> simdjson_inline simdjson_result<double> document::get() & noexcept { return get_double(); }
template<> simdjson_inline simdjson_result<uint64_t> document::get() & noexcept { return get_uint64(); } template<> simdjson_inline simdjson_result<uint64_t> document::get() & noexcept { return get_uint64(); }
template<> simdjson_inline simdjson_result<int64_t> document::get() & noexcept { return get_int64(); } template<> simdjson_inline simdjson_result<int64_t> document::get() & noexcept { return get_int64(); }
template<> simdjson_inline simdjson_result<uint32_t> document::get() & noexcept { return get_uint32(); }
template<> simdjson_inline simdjson_result<int32_t> document::get() & noexcept { return get_int32(); }
template<> simdjson_inline simdjson_result<bool> document::get() & noexcept { return get_bool(); } template<> simdjson_inline simdjson_result<bool> document::get() & noexcept { return get_bool(); }
template<> simdjson_inline simdjson_result<value> document::get() & noexcept { return get_value(); } template<> simdjson_inline simdjson_result<value> document::get() & noexcept { return get_value(); }
@@ -174,6 +190,8 @@ template<> simdjson_warn_unused simdjson_inline error_code document::get(std::st
template<> simdjson_warn_unused simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); } template<> simdjson_warn_unused simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); } template<> simdjson_warn_unused simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); } template<> simdjson_warn_unused simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint32_t& out) & noexcept { return get_uint32().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(int32_t& out) & noexcept { return get_int32().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); } template<> simdjson_warn_unused simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); } template<> simdjson_warn_unused simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); }
@@ -347,8 +365,14 @@ simdjson_inline simdjson_result<value> document::at_path(std::string_view json_p
} }
} }
simdjson_inline simdjson_result<std::vector<value>> document::at_path_with_wildcard(std::string_view json_path) noexcept { #if SIMDJSON_SUPPORTS_CONCEPTS
rewind(); // Rewind the document each time at_path_with_wildcard is called template <typename Func>
requires std::invocable<Func, value>
#else
template <typename Func>
#endif
simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called
if (json_path.empty()) { if (json_path.empty()) {
return INVALID_JSON_POINTER; return INVALID_JSON_POINTER;
} }
@@ -356,9 +380,9 @@ simdjson_inline simdjson_result<std::vector<value>> document::at_path_with_wildc
SIMDJSON_TRY(type().get(t)); SIMDJSON_TRY(type().get(t));
switch (t) { switch (t) {
case json_type::array: case json_type::array:
return (*this).get_array().at_path_with_wildcard(json_path); return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
case json_type::object: case json_type::object:
return (*this).get_object().at_path_with_wildcard(json_path); return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
default: default:
return INVALID_JSON_POINTER; return INVALID_JSON_POINTER;
} }
@@ -504,6 +528,14 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
if (error()) { return error(); } if (error()) { return error(); }
return first.get_int64_in_string(); return first.get_int64_in_string();
} }
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_uint32() noexcept {
if (error()) { return error(); }
return first.get_uint32();
}
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_int32() noexcept {
if (error()) { return error(); }
return first.get_int32();
}
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_double() noexcept { simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_double() noexcept {
if (error()) { return error(); } if (error()) { return error(); }
return first.get_double(); return first.get_double();
@@ -687,9 +719,15 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
return first.at_path(json_path); return first.at_path(json_path);
} }
simdjson_inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::at_path_with_wildcard(std::string_view json_path) noexcept { #if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, SIMDJSON_IMPLEMENTATION::ondemand::value>
#else
template <typename Func>
#endif
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
if (error()) { return error(); } if (error()) { return error(); }
return first.at_path_with_wildcard(json_path); return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
} }
#if SIMDJSON_STATIC_REFLECTION #if SIMDJSON_STATIC_REFLECTION
@@ -731,6 +769,18 @@ simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64() noexc
simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64_in_string() noexcept { return doc->get_root_value_iterator().get_root_uint64_in_string(false); } simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64_in_string() noexcept { return doc->get_root_value_iterator().get_root_uint64_in_string(false); }
simdjson_inline simdjson_result<int64_t> document_reference::get_int64() noexcept { return doc->get_root_value_iterator().get_root_int64(false); } simdjson_inline simdjson_result<int64_t> document_reference::get_int64() noexcept { return doc->get_root_value_iterator().get_root_int64(false); }
simdjson_inline simdjson_result<int64_t> document_reference::get_int64_in_string() noexcept { return doc->get_root_value_iterator().get_root_int64_in_string(false); } simdjson_inline simdjson_result<int64_t> document_reference::get_int64_in_string() noexcept { return doc->get_root_value_iterator().get_root_int64_in_string(false); }
simdjson_inline simdjson_result<uint32_t> document_reference::get_uint32() noexcept {
uint64_t result;
SIMDJSON_TRY(doc->get_root_value_iterator().get_root_uint64(false).get(result));
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<uint32_t>(result);
}
simdjson_inline simdjson_result<int32_t> document_reference::get_int32() noexcept {
int64_t result;
SIMDJSON_TRY(doc->get_root_value_iterator().get_root_int64(false).get(result));
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<int32_t>(result);
}
simdjson_inline simdjson_result<double> document_reference::get_double() noexcept { return doc->get_root_value_iterator().get_root_double(false); } simdjson_inline simdjson_result<double> document_reference::get_double() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
simdjson_inline simdjson_result<double> document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); } simdjson_inline simdjson_result<double> document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
simdjson_inline simdjson_result<std::string_view> document_reference::get_string(bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(false, allow_replacement); } simdjson_inline simdjson_result<std::string_view> document_reference::get_string(bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(false, allow_replacement); }
@@ -748,6 +798,8 @@ template<> simdjson_inline simdjson_result<std::string_view> document_reference:
template<> simdjson_inline simdjson_result<double> document_reference::get() & noexcept { return get_double(); } template<> simdjson_inline simdjson_result<double> document_reference::get() & noexcept { return get_double(); }
template<> simdjson_inline simdjson_result<uint64_t> document_reference::get() & noexcept { return get_uint64(); } template<> simdjson_inline simdjson_result<uint64_t> document_reference::get() & noexcept { return get_uint64(); }
template<> simdjson_inline simdjson_result<int64_t> document_reference::get() & noexcept { return get_int64(); } template<> simdjson_inline simdjson_result<int64_t> document_reference::get() & noexcept { return get_int64(); }
template<> simdjson_inline simdjson_result<uint32_t> document_reference::get() & noexcept { return get_uint32(); }
template<> simdjson_inline simdjson_result<int32_t> document_reference::get() & noexcept { return get_int32(); }
template<> simdjson_inline simdjson_result<bool> document_reference::get() & noexcept { return get_bool(); } template<> simdjson_inline simdjson_result<bool> document_reference::get() & noexcept { return get_bool(); }
template<> simdjson_inline simdjson_result<value> document_reference::get() & noexcept { return get_value(); } template<> simdjson_inline simdjson_result<value> document_reference::get() & noexcept { return get_value(); }
#if SIMDJSON_EXCEPTIONS #if SIMDJSON_EXCEPTIONS
@@ -786,7 +838,13 @@ simdjson_inline simdjson_result<number> document_reference::get_number() noexcep
simdjson_inline simdjson_result<std::string_view> document_reference::raw_json_token() noexcept { return doc->raw_json_token(); } simdjson_inline simdjson_result<std::string_view> document_reference::raw_json_token() noexcept { return doc->raw_json_token(); }
simdjson_inline simdjson_result<value> document_reference::at_pointer(std::string_view json_pointer) noexcept { return doc->at_pointer(json_pointer); } simdjson_inline simdjson_result<value> document_reference::at_pointer(std::string_view json_pointer) noexcept { return doc->at_pointer(json_pointer); }
simdjson_inline simdjson_result<value> document_reference::at_path(std::string_view json_path) noexcept { return doc->at_path(json_path); } simdjson_inline simdjson_result<value> document_reference::at_path(std::string_view json_path) noexcept { return doc->at_path(json_path); }
simdjson_inline simdjson_result<std::vector<value>> document_reference::at_path_with_wildcard(std::string_view json_path) noexcept { return doc->at_path_with_wildcard(json_path); } #if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, value>
#else
template <typename Func>
#endif
simdjson_inline error_code document_reference::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { return doc->for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback)); }
simdjson_inline simdjson_result<std::string_view> document_reference::raw_json() noexcept { return doc->raw_json();} simdjson_inline simdjson_result<std::string_view> document_reference::raw_json() noexcept { return doc->raw_json();}
simdjson_inline document_reference::operator document&() const noexcept { return *doc; } simdjson_inline document_reference::operator document&() const noexcept { return *doc; }
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION #if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
@@ -879,6 +937,14 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
if (error()) { return error(); } if (error()) { return error(); }
return first.get_int64_in_string(); return first.get_int64_in_string();
} }
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_uint32() noexcept {
if (error()) { return error(); }
return first.get_uint32();
}
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_int32() noexcept {
if (error()) { return error(); }
return first.get_int32();
}
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_double() noexcept { simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_double() noexcept {
if (error()) { return error(); } if (error()) { return error(); }
return first.get_double(); return first.get_double();
@@ -1043,11 +1109,17 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
} }
return first.at_path(json_path); return first.at_path(json_path);
} }
simdjson_inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::at_path_with_wildcard(std::string_view json_path) noexcept { #if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, SIMDJSON_IMPLEMENTATION::ondemand::value>
#else
template <typename Func>
#endif
simdjson_inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
if (error()) { if (error()) {
return error(); return error();
} }
return first.at_path_with_wildcard(json_path); return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
} }
#if SIMDJSON_STATIC_REFLECTION #if SIMDJSON_STATIC_REFLECTION
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>

Some files were not shown because too many files have changed in this diff Show More