Compare commits

...

87 Commits

Author SHA1 Message Date
Francisco Geiman Thiesen 4d9d948627 deserialize: single-pass dispatch for structs with optional fields
The reflection deserializer used `obj[key].get(out.member)` per struct
field, which delegates to find_field_unordered. For *absent* optional
fields this scans the entire JSON object before returning NO_SUCH_FIELD
— O(K) per absent optional, where K is the JSON object's field count.
A struct with N optionals where most are absent pays O(N*K) per record.

CITM hits this hard: CITMEvent has 7 optional fields; most events leave
several of them null/missing, so we re-scan each event many times.

Switch to single-pass dispatch when the struct has any std::optional
member: walk the JSON object exactly once, dispatch each visited key
to the matching struct member via a compile-time-generated key
comparison (with length pre-filter), let object_iterator::operator++
auto-skip values for unknown keys.

For structs with all-required fields (Twitter Status, User), keep the
existing per-field obj[key] path. It's O(1) per field on in-order JSON
and faster than the unrolled compare chain for dense, in-order objects.

Measured (TRUE A/B, 7 alternating rounds, single docker invocation):
  CITM    static_reflection: 2084 -> 2387 MB/s   +14.5%
  CITM    from:              2053 -> 2358 MB/s   +14.9%
  Twitter static_reflection: 3416 -> 3419 MB/s   break-even
  Twitter from:              3416 -> 3417 MB/s   break-even

All static_reflection_comprehensive_tests pass.
2026-05-09 04:55:37 -07:00
Daniel Lemire ec49efa5da avoid unnecessary pointer member (#2715) 2026-05-08 00:26:57 -04:00
Daniel Lemire a16e5128fe pendantic guard 2026-05-07 23:13:40 -04:00
Alecto Irene Perez bd42858383 fix -Wunneeded-internal-declaration warning (#2714) 2026-05-07 21:46:52 -04:00
jmestwa-coder 7d30ff2574 JSON Pointer array index overflow handling (#2713) 2026-05-07 19:07:39 -04:00
Francisco Geiman Thiesen 42c047a927 builder: position-as-local writer for reflection serializer (#2708)
The dominant cost in the reflection serializer was the strict-aliasing
reload pattern: every char* write through `string_builder::buffer.get()`
forced the compiler to assume `string_builder::position` and `::capacity`
may have been clobbered, so it reloaded both members from memory after
each byte. perf annotate showed `ldr [position]` and `ldr [capacity]`
taking 15-20% of CITM serialization time.

This change refactors the reflection atom path to use an internal
`writer` struct (buffer pointer + position + capacity + back-ref to
string_builder). The writer lives as a stack-local in the top-level
append() entry point, threaded through the inlined atom() chain via
`writer&`. After SROA, the three fields are register-resident across
the entire serialization. The `pos` is never round-tripped through
memory between writes — it's a local size_t.

This mirrors the pattern Glaze uses (passing `B&& b, auto&& ix` through
every helper). The simdjson-specific bit is keeping the public
string_builder API intact: only the internal atom() family is
refactored to take `writer&`. The top-level append(string_builder&, T)
constructs a writer on the stack, threads it through atom(), then
syncs the local position back at the end.

For string fields specifically, the escape path is also inlined
through the writer — the existing `write_string_escaped(input, out)`
helper already takes a destination pointer and returns bytes-written,
so it composes cleanly with `w.ptr + w.pos`. Without this, sync/
reload around each string write was a real cost on Twitter (-7%).

Performance
===========

TRUE A/B in single docker invocation, 7 alternating rounds, byte-
identical output, all static_reflection_comprehensive_tests pass.
Build: clang-p2996 21.0.0git, -O3 -DNDEBUG, aarch64 Linux.

CITM Catalog (496 682 bytes):
  baseline (master): 4356 MB/s
  patched:           6776 MB/s   +55.5%
  Glaze:             4860 MB/s   simdjson now 39.4% AHEAD of Glaze

Twitter (81 927 bytes):
  baseline (master): 6437 MB/s
  patched:           6452 MB/s   +0.2% (break-even)
  Twitter is string-heavy; the writer's gain over the existing
  member-based path is small here, but it no longer regresses.

Default initial capacity is unchanged at 1024.
2026-05-07 13:28:03 -04:00
Alecto Irene Perez 376ef7e8e9 Add support for writing NaN/Infinity if SIMDJSON_ENABLE_NAN_INF is enabled (#2711)
* Add tests for dumping out NaN/Infinity to the json builder

* If SIMDJSON_ENABLE_NAN_INF is set, write out NaN and Infinity

* Add tests for dumping out NaN/Infinity when serializing DOM

* If SIMDJSON_ENABLE_NAN_INF, use 'NaN' and 'Infinity' for nan/inf

* Fix bug where 'Inf' as a root atom + spaces of padding parses incorrectly

* Update FracturedJson printers so tables containing NaN/Infinity are aligned
2026-05-07 13:11:03 -04:00
Daniel Lemire 0a23ebbfef adding gcc16 (#2712) 2026-05-07 13:10:42 -04:00
Daniel Lemire 9803884546 overflow patch. 2026-05-06 17:37:18 -04:00
Alecto Irene Perez b9b20be80e Add support for parsing NaN and Infinity as requested in #1540, #2414, and #2540 (#2696)
* add compile option 'SIMDJSON_ENABLE_NAN_INF' but disable by default

* extend parser to support NaN/Infinity when SIMDJSON_ENABLE_NAN_INF=1

* update tests to check parsing of NaN/Infinity, when enabled

* update minefield tests: mark nan/inf tests as passing when nan/inf is ON

* update CI/CD to run tests with extensions for NaN/Infinity enabled
2026-05-04 15:24:26 -04:00
Francisco Geiman Thiesen f902769b35 builder: force-inline atom templates and replace integer writer (#2707)
* builder: force-inline atom templates and replace integer writer

Two complementary changes that together speed up reflection-driven JSON
serialization by ~28% on integer-heavy structs (CITM) and ~12% on
string-heavy structs (Twitter).

(1) Add simdjson_really_inline (always_inline) to the hot atom<T>
    template overloads (arithmetic, struct, container, optional,
    string-like). The constexpr-only declaration was only a hint; the
    compiler routinely chose to leave atom<unsigned long> as a real
    out-of-line function.

(2) Replace string_builder::append<UInt> body with a forward
    cascade-on-magnitude integer writer. The old code computed
    digit_count(v) upfront and wrote backward in a loop; the new code is
    a straight-line if/else cascade that writes digits forward, no loop,
    no helper call.

Either change alone gives only modest gains. Together they unlock the
compiler's cross-call optimization: with all atoms inlined and the
integer writer reduced to straight-line code, the compiler can hoist
b.position into a register across the whole struct serialization, fold
redundant capacity_check calls, and eliminate the strict-aliasing
penalty that otherwise forces b.position/b.capacity reloads after every
char* write.

Output is byte-identical to master on CITM (496682 bytes) and Twitter
(81927 bytes). All static_reflection_comprehensive_tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* builder: de-recurse write_uint_jeaiii so always_inline applies on g++/MSVC

g++ ('inlining failed in call to always_inline ...: function not
considered for inlining') and MSVC ('warning C4714: __forceinline not
inlined' under warnings-as-errors) both refuse to inline recursive
functions marked simdjson_really_inline. The original write_uint_jeaiii
called itself in the >=10^4 branches.

Refactor into a non-recursive DAG of helpers: write_lt100, write_lt10000,
write_4_digits, write_lt1e8, write_uint_jeaiii. Each calls strictly
smaller-domain helpers, no cycles. Same straight-line cascade behavior,
same byte output, but every node is now a candidate for always_inline on
all compilers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* builder: port signed-int append to jeaiii writer and drop digit_count helpers

The signed-integer branch in string_builder::append was structurally
identical to the OLD unsigned branch — same digit_count() upfront +
backward 4-digit batched loop. Port it to use the same forward
write_uint_jeaiii() helper as the unsigned branch (write '-'
unconditionally and advance position only if negative — branchless).

This makes int_log2 / fast_digit_count_32 / fast_digit_count_64 /
digit_count fully unused (verified via grep across include/ and src/);
remove them, ~80 lines of dead code.

The signed write path now benefits from the same compiler-level
optimization (full inlining, capacity-check fusion, no opaque-loop
boundary) as the unsigned path. Signed-int microbench (200 × 100k
values, mixed magnitude and sign): 2272 → 2685 MB/s (+18.2%).
CITM and Twitter benchmarks unchanged in shape and remain byte-identical
to baseline output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix tests

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Lemire <daniel@lemire.me>
2026-05-04 13:55:49 -04:00
Daniel Lemire 389f7ce8e3 fixing issue 2703 (#2705) 2026-04-30 21:59:36 -04:00
Francisco Geiman Thiesen 32b52e3f34 builder: coalesce per-field separator+key+colon into one constexpr write (#2698)
Each non-first field in a reflection-driven struct serialization was
emitting three separate string_builder::append calls (',', "\"key\"", ':'),
each going through capacity_check + a small write. Combine them into a
single compile-time string per field so each field does one capacity_check
and one memcpy. Same pattern fixed in atom(), append(), and extract_from().

Measured under clang-p2996 -O3 -DNDEBUG -freflection -std=c++26
(median of 5 contemporaneous runs, output byte-identical to baseline):

  CITM serialization (496682 bytes):
    simdjson_reuse_buffer:        3192 -> 3673 MB/s  (+15.1%)
    simdjson_static_reflection:   3014 -> 3443 MB/s  (+14.2%)
    simdjson_to:                  2717 -> 3234 MB/s  (+19.0%)
    simdjson_to_reuse:            2836 -> 3226 MB/s  (+13.7%)

  Twitter serialization (81927 bytes):
    simdjson_reuse_buffer:        6637 -> 7162 MB/s   (+7.9%)
    simdjson_static_reflection:   5527 -> 5915 MB/s   (+7.0%)
    simdjson_to:                  5070 -> 5350 MB/s   (+5.5%)
    simdjson_to_reuse:            4976 -> 5303 MB/s   (+6.6%)

  extract_from (out-of-tree micro-bench, 100 records per call):
    User 4-of-9 fields:           1833 -> 1888 MB/s   (+3.0%)
    Status 3-of-6 fields:         4286 -> 4334 MB/s   (+1.1%)

Parsing benchmarks unchanged. tests/builder/static_reflection_comprehensive_tests
still passes (round-trip through extract_from / extract_into is verified).
2026-04-30 21:02:41 -04:00
jmestwa-coder b3fe96cdb2 Align source() output for comma-delimited streams with json_sequence behavior (#2699) 2026-04-29 13:05:45 -04:00
Daniel Lemire 45d296e219 Update compiler requirements in basics.md
Added support for Fil-C, a memory-safe C/C++ compiler, to the requirements section.
2026-04-22 13:54:15 -04:00
Daniel Lemire c1681e9d0a build with filc (#2695) 2026-04-22 13:17:25 -04:00
Daniel Lemire 63b10bca21 new optimization to the ARM NEON kernel (#2692)
* new optimization to the ARM NEON kernel

* portability hack
2026-04-20 17:16:50 -04:00
Daniel Lemire 3b782fab7a fixes 2690 (typo) 2026-04-20 12:09:39 -04:00
Daniel Lemire df16e96767 fix: ondemand for wildcard matches (#2686)
* fixing issue 2684

* simplifying.

* update

* more fixes

* moving example

* adding forward declaration
2026-04-17 14:20:57 -04:00
Daniel Lemire 2e7ad956eb Merge branch 'master' of github.com:simdjson/simdjson 2026-04-15 12:52:47 -04:00
Daniel Lemire 30d7204312 pedantic guard 2026-04-15 12:52:31 -04:00
metsw24-max b6af1a0456 Fix undefined behavior in document_stream parsing due to unsafe std::isspace usage (#2680) 2026-04-14 23:03:44 -04:00
Daniel Lemire 7cc60672e3 Merge branch 'master' of github.com:simdjson/simdjson 2026-04-13 17:55:05 -04:00
Daniel Lemire 3ecda9ee99 pedantic checks for 32-bit systems who try to allocate enormous
capacities.
2026-04-13 17:54:35 -04:00
Daniel Lemire b648a5fc0a Add std::ranges support for On-Demand API (variant) (#2678)
* add std::ranges support for On-Demand API (#2382)

Add zero-cost range wrappers (array_range, object_range) that satisfy
std::ranges::input_range, enabling std::views::transform and other
C++20 range adaptors with the On-Demand parser.

Uses direct forwarding via simdjson_inline with no value buffering,
avoiding the per-element overhead (~20%) of the previous approach.
Guarded by SIMDJSON_SUPPORTS_RANGES.

* fix: replace non-ASCII em dash in test comment

The just_ascii CI check flags any non-ASCII characters in source files.

* let us see what we get with this...

* minor tweak

* minor update

* update doc

---------

Co-authored-by: Justin Li <justin53@bu.edu>
2026-04-13 15:25:02 -04:00
Daniel Lemire 4ec44e88c9 adding memory-file mapping to Windows + better doc (#2676)
* adding memory-file mapping to Windows.

* making memory-file mapping optional under windows, as it is fragile

* removing non-ascii

* saving.

* bumping up

* take 2
2026-04-12 18:12:31 -04:00
Daniel Lemire ede3129081 modernizing the benchmarks (#2679) 2026-04-12 18:09:46 -04:00
Daniel Lemire 8ea4c1c2e6 being more precise 2026-04-12 17:06:27 -04:00
Daniel Lemire 486b2a3828 minor update 2026-04-11 14:45:38 -04:00
Daniel Lemire 22773f3c70 documenting C++26 usage with iterate_many 2026-04-11 14:41:05 -04:00
Daniel Lemire 249eb2c28a better construction. 2026-04-11 14:23:15 -04:00
Daniel Lemire 94c429aa70 finishing up the new streaming (#2674)
* finishing up the new streaming

* fixed silly warnings.

* tweak

* adding more tests

* minor fixes

* more fixes
2026-04-11 14:16:43 -04:00
Jaël Champagne Gareau 72e51a9a81 Add support for RFC 7464 JSON text sequences and comma-delimited documents (#2664)
* add support for RFC 7464 documents

* add threaded comma-delimited parse_many support

* fix failing tests in CI
2026-04-10 20:26:00 -04:00
Daniel Lemire 1a57afec1f Various guards (#2673)
* adding a guard in document::allocate.

Co-authored-by: jmestwa-coder jmestwa@gmail.com

* adding a max depth

Co-authored-by: jmestwa-coder jmestwa@gmail.com
2026-04-10 20:19:46 -04:00
Daniel Lemire 4de7426b9f amalgamate should provide nicer error messages (#2672) 2026-04-10 17:42:57 -04:00
Daniel Lemire 85cadf4074 adding a C++17 padded_input for convenience. (#2667)
* adding a C++17 padded_input for convenience.

* adding header

* tuning.

* being explicit

* reworking the docu

* avoid windows.h

* Update include/simdjson/padded_string_view-inl.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/ondemand/ondemand_padded_input.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/ondemand/ondemand_readme_examples.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update doc/performance.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* minor update

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-09 14:07:44 -04:00
Daniel Lemire bc48e72070 literals should be guarded (#2669) 2026-04-09 14:07:29 -04:00
Jaël Champagne Gareau 8955c2c6d5 Fix failing just_ascii test (#2661)
* fix just_ascii errors in test/benchmark sources

* make just_ascii test run in CI
2026-04-05 11:11:35 -04:00
Daniel Lemire 73e69a5e84 Get reflection working without warnings under GCC 16 (#2658)
* wip gcc16

* progress

* minor gcc16 fixes

* final step

* Update include/simdjson/padded_string-inl.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update include/simdjson/padded_string.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* renaming test function

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-03 15:32:42 -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
178 changed files with 46332 additions and 4252 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).
+1 -1
View File
@@ -20,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.
+26
View File
@@ -0,0 +1,26 @@
name: gcc 16
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
container:
image: 'gcc:16'
steps:
- uses: actions/checkout@v6
- name: Install dependencies
run: |
apt -y update
apt -y --no-install-recommends install cmake ninja-build
- name: Build and test
run: |
cmake -B build -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -GNinja
cmake --build build
ctest --test-dir build --parallel $(nproc)
+11
View File
@@ -42,3 +42,14 @@ jobs:
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json && echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
cd ../tests/installation_tests/find && cd ../tests/installation_tests/find &&
mkdir buildshared && cd buildshared && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../buildshared/destination .. && cmake --build . mkdir buildshared && cd buildshared && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../buildshared/destination .. && cmake --build .
- name: Use cmake (parsing for NaN/Infinity enabled)
run: |
mkdir build_nan_inf &&
cd build_nan_inf &&
cmake -DSIMDJSON_ENABLE_NAN_INF=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j &&
cmake --install . &&
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
cd ../tests/installation_tests/find &&
mkdir build_nan_inf && cd build_nan_inf && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build_nan_inf/destination .. && cmake --build .
+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]
+16 -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]')
@@ -21,6 +21,13 @@ jobs:
cmake -DSIMDJSON_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. && cmake -DSIMDJSON_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. &&
cmake --build . && cmake --build . &&
ctest --output-on-failure -LE explicitonly -j ctest --output-on-failure -LE explicitonly -j
- name: Use cmake with address sanitizer (Parsing of NaN/Infinity enabled)
run: |
mkdir builddebug_nan_inf &&
cd builddebug_nan_inf &&
cmake -DSIMDJSON_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -DSIMDJSON_ENABLE_NAN_INF=ON .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j
ubuntu-build-undefined-sanitizer: ubuntu-build-undefined-sanitizer:
if: >- if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') && ! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
@@ -39,3 +46,10 @@ jobs:
cmake -DSIMDJSON_SANITIZE_UNDEFINED=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. && cmake -DSIMDJSON_SANITIZE_UNDEFINED=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. &&
cmake --build . && cmake --build . &&
ctest --output-on-failure -LE explicitonly -j ctest --output-on-failure -LE explicitonly -j
- name: Use cmake with undefined sanitizer (Parsing of NaN/Infinity enabled)
run: |
mkdir builddebugundefsani_nan_inf &&
cd builddebugundefsani_nan_inf &&
cmake -DSIMDJSON_SANITIZE_UNDEFINED=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -DSIMDJSON_ENABLE_NAN_INF=ON .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j
+2 -1
View File
@@ -12,11 +12,12 @@ jobs:
shared: [ON, OFF] shared: [ON, OFF]
cxx: [g++-13, clang++-16] cxx: [g++-13, clang++-16]
sanitizer: [ON, OFF] sanitizer: [ON, OFF]
nan_inf: [ON, OFF]
build_type: [RelWithDebInfo, Debug, Release] build_type: [RelWithDebInfo, Debug, Release]
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Prepare - name: Prepare
run: cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_SANITIZE=${{matrix.sanitizer}} -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build run: cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_SANITIZE=${{matrix.sanitizer}} -DBUILD_SHARED_LIBS=${{matrix.shared}} -DSIMDJSON_ENABLE_NAN_INF=${{matrix.nan_inf}} -B build
env: env:
CXX: ${{matrix.cxx}} CXX: ${{matrix.cxx}}
- name: Build - name: Build
+11 -7
View File
@@ -13,18 +13,22 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release} - {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release} - {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release} - {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug} - {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release} - {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo} - {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: ON}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: ON}
# Exercise the opt-in Windows memory-file mapping path at least once in CI.
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: OFF}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Configure - name: Configure
run: | run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=${{matrix.memory_map}} -DSIMDJSON_ENABLE_NAN_INF=${{matrix.nan_inf}} -B build
- name: Build Debug - name: Build Debug
run: cmake --build build --config ${{matrix.build_type}} --verbose run: cmake --build build --config ${{matrix.build_type}} --verbose
- name: Run tests - name: Run tests
+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)
+97 -11
View File
@@ -12,7 +12,7 @@ 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.4 VERSION 4.6.1
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
@@ -29,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)
@@ -75,10 +75,48 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
) )
endif() endif()
# padded_memory_map is always available on POSIX. On Windows it is disabled
# by default because it depends on the `CreateFileMapping2` / `MapViewOfFile3`
# APIs, which require Windows 10 version 1803 or later and are exported via
# onecore.lib rather than the default kernel32.lib. Turn this option ON to
# opt into the feature on Windows; simdjson will then set the appropriate
# Windows version macros and link onecore, so everything that links
# simdjson picks up both the compile-time declarations and the import
# library automatically. The option is a no-op on POSIX (where the feature
# is unconditionally enabled).
option(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
"Enable simdjson::padded_memory_map on Windows (requires Windows 10 \
version 1803 or later). Always enabled on POSIX." OFF)
if(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS)
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1
)
if(WIN32)
# Raise the Windows version floor so that <windows.h> declares the
# modern memory-mapping APIs, and link the import library that
# actually exports them. _WIN32_WINNT / WINVER / NTDDI_VERSION together
# tell <sdkddkver.h> which APIs to light up.
simdjson_add_props(
target_compile_definitions PUBLIC
_WIN32_WINNT=0x0A00
WINVER=0x0A00
NTDDI_VERSION=0x0A000006 # NTDDI_WIN10_RS5, Windows 10 version 1809
)
simdjson_add_props(
target_link_libraries PUBLIC
onecore
)
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)
@@ -92,13 +130,16 @@ 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 # Enable precompiled headers for faster builds
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16") if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
target_precompile_headers(simdjson PRIVATE set(SIMDJSON_PRECOMPILE_HEADERS
<algorithm> <algorithm>
<array> <array>
<atomic> <atomic>
<bit>
<cassert> <cassert>
<cctype> <cctype>
<cerrno> <cerrno>
@@ -111,6 +152,12 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
<utility> <utility>
<vector> <vector>
) )
if(SIMDJSON_HAS_BIT_HEADER)
list(APPEND SIMDJSON_PRECOMPILE_HEADERS <bit>)
endif()
target_precompile_headers(simdjson PRIVATE ${SIMDJSON_PRECOMPILE_HEADERS})
endif() endif()
if(SIMDJSON_BUILD_STATIC_LIB) if(SIMDJSON_BUILD_STATIC_LIB)
@@ -155,13 +202,37 @@ if(MSVC)
endif() endif()
endif() endif()
if(SIMDJSON_STATIC_REFLECTION) if(SIMDJSON_STATIC_REFLECTION)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION MATCHES "^21")
execute_process(
COMMAND ${CMAKE_CXX_COMPILER} --version
OUTPUT_VARIABLE CLANG_VERSION_OUTPUT
ERROR_VARIABLE CLANG_VERSION_ERROR
RESULT_VARIABLE CLANG_VERSION_RESULT
)
if(CLANG_VERSION_RESULT EQUAL 0 AND CLANG_VERSION_OUTPUT MATCHES "https://github.com/bloomberg/clang-p2996.git")
set(IS_BLOOMBERG_P2996_CLANG ON)
message(STATUS "Using Bloomberg P2996 Clang fork")
endif()
endif()
# 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:
if(IS_BLOOMBERG_P2996_CLANG)
simdjson_add_props( simdjson_add_props(
target_compile_options PUBLIC target_compile_options PUBLIC
-freflection -fexpansion-statements -stdlib=libc++ -std=c++26 -freflection -fexpansion-statements -stdlib=libc++ -std=c++26
) )
else()
simdjson_add_props(
target_compile_options PUBLIC
-freflection -std=c++26
)
endif()
else() else()
simdjson_add_props(target_compile_features PUBLIC cxx_std_11) simdjson_add_props(target_compile_features PUBLIC cxx_std_11)
endif() endif()
@@ -183,6 +254,12 @@ 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)
option(SIMDJSON_ENABLE_NAN_INF "Allow parsing of NaN and Infinity JSON values" OFF)
if(SIMDJSON_ENABLE_NAN_INF)
message(STATUS "simdjson NaN and Infinity parsing is enabled.")
simdjson_add_props(target_compile_definitions PUBLIC SIMDJSON_ENABLE_NAN_INF=1)
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
@@ -216,6 +293,7 @@ endif()
# ---- Install rules ---- # ---- Install rules ----
if(SIMDJSON_INSTALL)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
include(GNUInstallDirs) include(GNUInstallDirs)
@@ -296,6 +374,7 @@ install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc" FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
) )
endif()
# #
# CPack # CPack
@@ -373,18 +452,25 @@ add_subdirectory(fuzz)
# #
# Source files should be just ASCII # Source files should be just ASCII
# #
find_program(FIND find) find_program(FIND_CMD find)
find_program(FILE file) find_program(FILE_CMD file)
find_program(GREP grep) find_program(GREP_CMD grep)
if(FIND AND FILE AND GREP) if(FIND_CMD AND FILE_CMD AND GREP_CMD)
add_test( add_test(
NAME just_ascii NAME just_ascii
COMMAND sh -c "\ COMMAND sh -c "\
${FIND} include src windows tools singleheader tests examples benchmark \ non_ascii=$(${FIND_CMD} include src windows tools singleheader tests examples benchmark \
-path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \ -path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \
-type f -exec ${FILE} '{}' \; | ${GREP} -qv ASCII || exit 0 && exit 1" -type f -exec ${FILE_CMD} '{}' \; | ${GREP_CMD} -v ASCII); \
if [ -n \"$non_ascii\" ]; then \
echo 'The following files contain non-ASCII characters:'; \
echo \"$non_ascii\"; \
exit 1; \
fi"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
) )
else()
message(WARNING "just_ascii test disabled because required tools were not found: find='${FIND_CMD}', file='${FILE_CMD}', grep='${GREP_CMD}'")
endif() endif()
## ##
+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.4" PROJECT_NUMBER = "4.6.1"
# 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
+10 -1
View File
@@ -64,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.
@@ -187,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
-------------- --------------
@@ -209,10 +212,16 @@ 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 Citing this work
----------------- -----------------
+3 -1
View File
@@ -1,9 +1,10 @@
add_subdirectory(dom) add_subdirectory(dom)
include_directories( . linux ) include_directories( . )
link_libraries(simdjson-windows-headers test-data) link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson) link_libraries(simdjson)
link_libraries(counters)
if(SIMDJSON_STATIC_REFLECTION) if(SIMDJSON_STATIC_REFLECTION)
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1) add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
endif(SIMDJSON_STATIC_REFLECTION) endif(SIMDJSON_STATIC_REFLECTION)
@@ -15,6 +16,7 @@ if (TARGET benchmark::benchmark)
link_libraries(benchmark::benchmark) link_libraries(benchmark::benchmark)
add_executable(bench_parse_call bench_parse_call.cpp) add_executable(bench_parse_call bench_parse_call.cpp)
add_executable(bench_dom_api bench_dom_api.cpp) add_executable(bench_dom_api bench_dom_api.cpp)
add_executable(bench_stream_formats bench_stream_formats.cpp)
if(SIMDJSON_EXCEPTIONS) if(SIMDJSON_EXCEPTIONS)
add_executable(bench_ondemand bench_ondemand.cpp) add_executable(bench_ondemand bench_ondemand.cpp)
if(TARGET yyjson) if(TARGET yyjson)
File diff suppressed because it is too large Load Diff
+1
View File
@@ -124,6 +124,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
#include "kostya/boostjson.h" #include "kostya/boostjson.h"
#include "large_random/simdjson_ondemand.h" #include "large_random/simdjson_ondemand.h"
#include "large_random/simdjson_ondemand_ranges.h"
#if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED #if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
#include "large_random/simdjson_ondemand_unordered.h" #include "large_random/simdjson_ondemand_unordered.h"
#endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED #endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
+216
View File
@@ -0,0 +1,216 @@
#include <benchmark/benchmark.h>
#include <string>
#include "simdjson.h"
using namespace simdjson;
namespace {
enum class stream_case {
ndjson_small,
ndjson_large,
rfc7464_small,
rfc7464_large,
comma_delimited_small,
comma_delimited_large
};
constexpr size_t TARGET_BYTES = 128 * 1000 * 1000;
constexpr size_t SMALL_PAYLOAD = 16;
constexpr size_t LARGE_PAYLOAD = 4096;
constexpr size_t BATCH_SIZE = 1 << 20;
struct stream_dataset {
padded_string json;
size_t count{};
};
std::string make_document(size_t id, size_t payload_size) {
return std::string{"{\"id\":"} + std::to_string(id) +
",\"name\":\"aaaaaaaa\",\"payload\":\"" +
std::string(payload_size, 'x') + "\",\"flag\":true}";
}
stream_dataset build_dataset(stream_case which) {
const bool small = which == stream_case::ndjson_small ||
which == stream_case::rfc7464_small ||
which == stream_case::comma_delimited_small;
const bool rfc = which == stream_case::rfc7464_small ||
which == stream_case::rfc7464_large;
const bool comma = which == stream_case::comma_delimited_small ||
which == stream_case::comma_delimited_large;
const size_t payload_size = small ? SMALL_PAYLOAD : LARGE_PAYLOAD;
const size_t count = TARGET_BYTES / (payload_size + 48);
std::string out;
out.reserve(count * (payload_size + 64));
for (size_t i = 0; i < count; i++) {
if (rfc) {
out += char(0x1E);
}
if (comma && i > 0) {
out += ',';
}
out += make_document(i, payload_size);
if (!comma) {
out += '\n';
}
}
return {padded_string(out), count};
}
const stream_dataset &get_dataset(stream_case which) {
static const stream_dataset ndjson_small =
build_dataset(stream_case::ndjson_small);
static const stream_dataset ndjson_large =
build_dataset(stream_case::ndjson_large);
static const stream_dataset rfc_small =
build_dataset(stream_case::rfc7464_small);
static const stream_dataset rfc_large =
build_dataset(stream_case::rfc7464_large);
static const stream_dataset comma_small =
build_dataset(stream_case::comma_delimited_small);
static const stream_dataset comma_large =
build_dataset(stream_case::comma_delimited_large);
switch (which) {
case stream_case::ndjson_small:
return ndjson_small;
case stream_case::ndjson_large:
return ndjson_large;
case stream_case::rfc7464_small:
return rfc_small;
case stream_case::rfc7464_large:
return rfc_large;
case stream_case::comma_delimited_small:
return comma_small;
case stream_case::comma_delimited_large:
return comma_large;
}
return ndjson_small;
}
void set_counters(benchmark::State &state, const stream_dataset &dataset) {
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(dataset.json.size()));
state.SetItemsProcessed(int64_t(state.iterations()) * int64_t(dataset.count));
}
template <stream_case which, bool threaded = true>
static void bench_ondemand(benchmark::State &state) {
const auto &dataset = get_dataset(which);
ondemand::parser parser;
parser.threaded = threaded;
stream_format format = stream_format::whitespace_delimited;
if constexpr (which == stream_case::rfc7464_small ||
which == stream_case::rfc7464_large) {
format = stream_format::json_sequence;
} else if constexpr (which == stream_case::comma_delimited_small ||
which == stream_case::comma_delimited_large) {
format = stream_format::comma_delimited;
}
for (const auto _ : state) {
ondemand::document_stream docs;
auto error = parser.iterate_many(dataset.json, BATCH_SIZE, format).get(docs);
if (error) {
state.SkipWithError(error_message(error));
return;
}
uint64_t sum = 0;
for (auto doc : docs) {
ondemand::object obj;
if ((error = doc.get_object().get(obj))) {
state.SkipWithError(error_message(error));
return;
}
uint64_t id;
if ((error = obj["id"].get_uint64().get(id))) {
state.SkipWithError(error_message(error));
return;
}
sum += id;
}
benchmark::DoNotOptimize(sum);
}
set_counters(state, dataset);
}
template <stream_case which>
static void bench_dom(benchmark::State &state) {
const auto &dataset = get_dataset(which);
dom::parser parser;
parser.threaded = true;
stream_format format = stream_format::whitespace_delimited;
if constexpr (which == stream_case::rfc7464_small ||
which == stream_case::rfc7464_large) {
format = stream_format::json_sequence;
} else if constexpr (which == stream_case::comma_delimited_small ||
which == stream_case::comma_delimited_large) {
format = stream_format::comma_delimited;
}
for (const auto _ : state) {
dom::document_stream docs;
auto error = parser.parse_many(dataset.json, BATCH_SIZE, format).get(docs);
if (error) {
state.SkipWithError(error_message(error));
return;
}
uint64_t sum = 0;
for (auto doc : docs) {
uint64_t id;
if ((error = doc["id"].get(id))) {
state.SkipWithError(error_message(error));
return;
}
sum += id;
}
benchmark::DoNotOptimize(sum);
}
set_counters(state, dataset);
}
} // namespace
BENCHMARK(bench_ondemand<stream_case::ndjson_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::ndjson_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::rfc7464_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::rfc7464_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
// Non-threaded comma_delimited for comparison
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small, false>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large, false>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::ndjson_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::ndjson_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::rfc7464_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::rfc7464_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::comma_delimited_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::comma_delimited_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK_MAIN();
+2 -39
View File
@@ -1,4 +1,5 @@
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
@@ -25,7 +26,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "linux-perf-events.h"
#ifdef __linux__ #ifdef __linux__
#include <libgen.h> #include <libgen.h>
#endif #endif
@@ -204,12 +204,8 @@ struct feature_benchmarker {
} }
// Rate of 1-7-structural misses per 8-structural flip // Rate of 1-7-structural misses per 8-structural flip
double struct1_7_miss_rate(BenchmarkStage stage) const { double struct1_7_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped); return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped);
#endif
} }
// Extra cost of an 8-15 structural block over a 1-7 structural block // Extra cost of an 8-15 structural block over a 1-7 structural block
double struct8_15_cost(BenchmarkStage stage) const { double struct8_15_cost(BenchmarkStage stage) const {
@@ -221,12 +217,8 @@ struct feature_benchmarker {
} }
// Rate of 8-15-structural misses per 8-structural flip // Rate of 8-15-structural misses per 8-structural flip
double struct8_15_miss_rate(BenchmarkStage stage) const { double struct8_15_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped); return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped);
#endif
} }
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!) // Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
@@ -239,12 +231,8 @@ struct feature_benchmarker {
} }
// Rate of 16-structural misses per 16-structural flip // Rate of 16-structural misses per 16-structural flip
double struct16_miss_rate(BenchmarkStage stage) const { double struct16_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped); return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped);
#endif
} }
@@ -258,12 +246,8 @@ struct feature_benchmarker {
} }
// Rate of UTF-8 misses per UTF-8 flip // Rate of UTF-8 misses per UTF-8 flip
double utf8_miss_rate(BenchmarkStage stage) const { double utf8_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped); return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped);
#endif
} }
// Extra cost of having escapes in a block // Extra cost of having escapes in a block
double escape_cost(BenchmarkStage stage) const { double escape_cost(BenchmarkStage stage) const {
@@ -275,12 +259,8 @@ struct feature_benchmarker {
} }
// Rate of escape misses per escape flip // Rate of escape misses per escape flip
double escape_miss_rate(BenchmarkStage stage) const { double escape_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped); return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped);
#endif
} }
@@ -378,22 +358,6 @@ struct feature_benchmarker {
} }
}; };
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
double calc_misses = features.calc_expected_misses(stage, results);
double calc_miss_cost = features.calc_expected_miss_cost(stage, results);
printf(" | %-8s ", benchmark_stage_name(stage));
printf("| %-15s ", filename);
printf("| %8.3g ", features.calc_expected_feature_cost(stage, results));
printf("| %8.3g ", calc_miss_cost);
printf("| %8.3g ", calc);
printf("| %8.3g ", actual);
printf("| %+8.3g ", actual - calc);
printf("| %13llu ", (long long unsigned)(calc_misses));
}
#else
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) { void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks); double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results); double calc = features.calc_expected(stage, results);
@@ -417,7 +381,6 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
} }
printf("|\n"); printf("|\n");
} }
#endif
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Read options // Read options
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef _BENCHMARK_H_ #ifndef _BENCHMARK_H_
#define _BENCHMARK_H_ #define _BENCHMARK_H_
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
/* /*
* Prints the best number of operations per cycle where * Prints the best number of operations per cycle where
+4 -11
View File
@@ -1,7 +1,8 @@
#ifndef __BENCHMARKER_H #ifndef __BENCHMARKER_H
#define __BENCHMARKER_H #define __BENCHMARKER_H
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include "simdjson.h" #include "simdjson.h"
#include <cassert> #include <cassert>
@@ -28,11 +29,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "linux-perf-events.h"
#ifdef __linux__ #ifdef __linux__
#include <libgen.h> #include <libgen.h>
#endif #endif
#include "simdjson.h"
#include <functional> #include <functional>
@@ -423,18 +422,12 @@ struct benchmarker {
stage.instructions() / static_cast<double>(stats->structurals), stage.instructions() / static_cast<double>(stats->structurals),
stage.instructions() / static_cast<double>(stage.cycles()) stage.instructions() / static_cast<double>(stage.cycles())
); );
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS printf("%s%-13s: %7.0f branch misses (%6.2f%%)\n",
// NOTE: removed cycles/miss because it is a somewhat misleading stat
printf("%s%-13s: %7.0f branch misses (%6.2f%%) - %.0f cache misses (%6.2f%%) - %.2f cache references\n",
prefix, prefix,
"Misses", "Misses",
stage.branch_misses(), stage.branch_misses(),
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()), percent(stage.branch_misses(), all_stages_without_allocation.branch_misses())
stage.cache_misses(),
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
stage.cache_references()
); );
#endif
} }
} }
@@ -1,4 +1,5 @@
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include <random> #include <random>
#include <vector> #include <vector>
+3 -2
View File
@@ -1,11 +1,12 @@
include_directories( .. ../linux ) include_directories( .. )
link_libraries(simdjson-windows-headers test-data) link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson) link_libraries(simdjson)
link_libraries(counters)
add_executable(perfdiff perfdiff.cpp) add_executable(perfdiff perfdiff.cpp)
add_executable(parse parse.cpp) add_executable(parse parse.cpp)
add_executable(parse_stream parse_stream.cpp) add_executable(parse_stream parse_stream.cpp)
add_executable(statisticalmodel statisticalmodel.cpp)
add_executable(parse_noutf8validation parse.cpp) add_executable(parse_noutf8validation parse.cpp)
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION) target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
+2 -2
View File
@@ -1,4 +1,5 @@
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
@@ -24,7 +25,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "linux-perf-events.h"
#ifdef __linux__ #ifdef __linux__
#include <libgen.h> #include <libgen.h>
#endif #endif
-207
View File
@@ -1,207 +0,0 @@
#include <iostream>
#include <unistd.h>
#include "simdjson.h"
#ifdef __linux__
#include "linux-perf-events.h"
#endif
size_t count_nonasciibytes(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += input[i] >> 7;
}
return count;
}
size_t count_backslash(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += (input[i] == '\\') ? 1 : 0;
}
return count;
}
struct stat_s {
size_t integer_count;
size_t float_count;
size_t string_count;
size_t backslash_count;
size_t non_ascii_byte_count;
size_t object_count;
size_t array_count;
size_t null_count;
size_t true_count;
size_t false_count;
size_t byte_count;
size_t structural_indexes_count;
bool valid;
};
using stat_t = struct stat_s;
simdjson_inline void simdjson_process_atom(stat_t &s,
simdjson::dom::element element) {
if (element.is<int64_t>()) {
s.integer_count++;
} else if(element.is<std::string_view>()) {
s.string_count++;
} else if(element.is<double>()) {
s.float_count++;
} else if (element.is<bool>()) {
bool v;
simdjson::error_code error;
if ((error = element.get(v))) { std::cerr << error << std::endl; abort(); }
if (v) {
s.true_count++;
} else {
s.false_count++;
}
} else if (element.is_null()) {
s.null_count++;
}
}
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
simdjson::error_code error;
if (element.is<simdjson::dom::array>()) {
s.array_count++;
simdjson::dom::array array;
if ((error = element.get(array))) { std::cerr << error << std::endl; abort(); }
for (auto child : array) {
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
simdjson_recurse(s, child);
} else {
simdjson_process_atom(s, child);
}
}
} else if (element.is<simdjson::dom::object>()) {
s.object_count++;
simdjson::dom::object object;
if ((error = element.get(object))) { std::cerr << error << std::endl; abort(); }
for (auto field : object) {
s.string_count++; // for key
if (field.value.is<simdjson::dom::array>() || field.value.is<simdjson::dom::object>()) {
simdjson_recurse(s, field.value);
} else {
simdjson_process_atom(s, field.value);
}
}
} else {
simdjson_process_atom(s, element);
}
}
stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
stat_t answer{};
simdjson::dom::parser parser;
simdjson::dom::element doc;
auto error = parser.parse(p).get(doc);
if (error) {
answer.valid = false;
return answer;
}
answer.valid = true;
answer.backslash_count =
count_backslash(reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.non_ascii_byte_count = count_nonasciibytes(
reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.byte_count = p.size();
answer.structural_indexes_count = parser.implementation->n_structural_indexes;
simdjson_recurse(answer, doc);
return answer;
}
int main(int argc, char *argv[]) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
abort();
}
}
#else
int optind = 1;
#endif
if (optind >= argc) {
std::cerr << "Reads json, prints stats. " << std::endl;
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
exit(1);
}
const char *filename = argv[optind];
if (optind + 1 < argc) {
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
<< std::endl;
}
simdjson::padded_string p;
auto error = simdjson::padded_string::load(filename).get(p);
if (error) {
std::cerr << "Could not load the file " << filename << std::endl;
return EXIT_FAILURE;
}
stat_t s = simdjson_compute_stats(p);
if (!s.valid) {
std::cerr << "not a valid JSON" << std::endl;
return EXIT_FAILURE;
}
printf("# integer_count float_count string_count backslash_count "
"non_ascii_byte_count object_count array_count null_count true_count "
"false_count byte_count structural_indexes_count ");
#ifdef __linux__
printf(" stage1_cycle_count stage1_instruction_count stage2_cycle_count "
" stage2_instruction_count stage3_cycle_count "
"stage3_instruction_count ");
#else
printf("(you are not under linux, so perf counters are disaabled)");
#endif
printf("\n");
printf("%zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu ", s.integer_count,
s.float_count, s.string_count, s.backslash_count,
s.non_ascii_byte_count, s.object_count, s.array_count, s.null_count,
s.true_count, s.false_count, s.byte_count, s.structural_indexes_count);
#ifdef __linux__
simdjson::dom::parser parser;
simdjson::error_code alloc_error = parser.allocate(p.size());
if (alloc_error) {
std::cerr << alloc_error << std::endl;
return EXIT_FAILURE;
}
const uint32_t iterations = p.size() < 1 * 1000 * 1000 ? 1000 : 50;
std::vector<int> evts;
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
LinuxEvents<PERF_TYPE_HARDWARE> unified(evts);
unsigned long cy1 = 0, cy2 = 0;
unsigned long cl1 = 0, cl2 = 0;
std::vector<unsigned long long> results;
results.resize(evts.size());
for (uint32_t i = 0; i < iterations; i++) {
unified.start();
// The default template is simdjson::architecture::NATIVE.
bool isok = (parser.implementation->stage1((const uint8_t *)p.data(), p.size(), simdjson::stage1_mode::regular) == simdjson::SUCCESS);
unified.end(results);
cy1 += results[0];
cl1 += results[1];
unified.start();
isok = isok && (parser.implementation->stage2(parser.doc) == simdjson::SUCCESS);
unified.end(results);
cy2 += results[0];
cl2 += results[1];
if (!isok) {
std::cerr << "failure?" << std::endl;
}
}
printf("%f %f %f %f ", static_cast<double>(cy1) / static_cast<double>(iterations), static_cast<double>(cl1) / static_cast<double>(iterations),
static_cast<double>(cy2) / static_cast<double>(iterations), static_cast<double>(cl2) / static_cast<double>(iterations));
#endif // __linux__
printf("\n");
return EXIT_SUCCESS;
}
-201
View File
@@ -1,201 +0,0 @@
#ifndef __EVENT_COUNTER_H
#define __EVENT_COUNTER_H
#ifndef SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
#ifdef __aarch64__
// on ARM, we use just cycles and instructions
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 1
#else
// elsewhere, we try to use four counters.
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 0
#endif
#endif
#include <cassert>
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#ifdef __linux__
#include "linux-perf-events.h"
#include <libgen.h>
#endif
#if __APPLE__ && __aarch64__
#include "apple/apple_arm_events.h"
#endif
#include "simdjson.h"
using std::string;
using std::vector;
using std::chrono::steady_clock;
using std::chrono::time_point;
using std::chrono::duration;
struct event_count {
duration<double> elapsed;
vector<unsigned long long> event_counts;
event_count() : elapsed(0), event_counts{0,0,0,0,0} {}
event_count(const duration<double> _elapsed, const vector<unsigned long long> _event_counts) : elapsed(_elapsed), event_counts(_event_counts) {}
event_count(const event_count& other): elapsed(other.elapsed), event_counts(other.event_counts) { }
// The types of counters (so we can read the getter more easily)
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS
};
#else
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS,
BRANCH_MISSES,
CACHE_REFERENCES,
CACHE_MISSES
};
#endif
double elapsed_sec() const { return duration<double>(elapsed).count(); }
double elapsed_ns() const { return duration<double, std::nano>(elapsed).count(); }
double cycles() const { return static_cast<double>(event_counts[CPU_CYCLES]); }
double instructions() const { return static_cast<double>(event_counts[INSTRUCTIONS]); }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return static_cast<double>(event_counts[BRANCH_MISSES]); }
double cache_references() const { return static_cast<double>(event_counts[CACHE_REFERENCES]); }
double cache_misses() const { return static_cast<double>(event_counts[CACHE_MISSES]); }
#endif
event_count& operator=(const event_count& other) {
this->elapsed = other.elapsed;
this->event_counts = other.event_counts;
return *this;
}
event_count operator+(const event_count& other) const {
return event_count(elapsed+other.elapsed, {
event_counts[0]+other.event_counts[0],
event_counts[1]+other.event_counts[1],
event_counts[2]+other.event_counts[2],
event_counts[3]+other.event_counts[3],
event_counts[4]+other.event_counts[4],
});
}
void operator+=(const event_count& other) {
*this = *this + other;
}
};
struct event_aggregate {
int iterations = 0;
event_count total{};
event_count best{};
event_count worst{};
event_aggregate() {}
void operator<<(const event_count& other) {
if (iterations == 0 || other.elapsed < best.elapsed) {
best = other;
}
if (iterations == 0 || other.elapsed > worst.elapsed) {
worst = other;
}
iterations++;
total += other;
}
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
double total_elapsed_ns() const { return total.elapsed_ns(); }
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
double cycles() const { return total.cycles() / iterations; }
double instructions() const { return total.instructions() / iterations; }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return total.branch_misses() / iterations; }
double cache_references() const { return total.cache_references() / iterations; }
double cache_misses() const { return total.cache_misses() / iterations; }
#endif
};
struct event_collector {
event_count count{};
time_point<steady_clock> start_clock{};
#if defined(__linux__)
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
event_collector() : linux_events(vector<int>{
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
#else
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
PERF_COUNT_HW_BRANCH_MISSES,
PERF_COUNT_HW_CACHE_REFERENCES,
PERF_COUNT_HW_CACHE_MISSES
#endif
}) {}
bool has_events() {
return linux_events.is_working();
}
#elif __APPLE__ && __aarch64__
AppleEvents apple_events;
performance_counters diff;
event_collector() : diff(0) {
apple_events.setup_performance_counters();
}
bool has_events() {
return apple_events.setup_performance_counters();
}
#else
event_collector() {}
bool has_events() {
return false;
}
#endif
simdjson_inline void start() {
#if defined(__linux)
linux_events.start();
#elif __APPLE__ && __aarch64__
if(has_events()) { diff = apple_events.get_counters(); }
#endif
start_clock = steady_clock::now();
}
simdjson_inline event_count& end() {
time_point<steady_clock> end_clock = steady_clock::now();
#if defined(__linux)
linux_events.end(count.event_counts);
#elif __APPLE__ && __aarch64__
if(has_events()) {
performance_counters end = apple_events.get_counters();
diff = end - diff;
}
count.event_counts[0] = diff.cycles;
count.event_counts[1] = diff.instructions;
count.event_counts[2] = diff.missed_branches;
count.event_counts[3] = 0;
count.event_counts[4] = 0;
#endif
count.elapsed = end_clock - start_clock;
return count;
}
};
#endif
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef BENCHMARK_HELPERS_H #ifndef BENCHMARK_HELPERS_H
#define BENCHMARK_HELPERS_H #define BENCHMARK_HELPERS_H
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include <atomic> #include <atomic>
event_collector collector; event_collector collector;
+2 -13
View File
@@ -1,7 +1,8 @@
#pragma once #pragma once
#include "simdjson.h" #include "simdjson.h"
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include <iostream> #include <iostream>
namespace json_benchmark { namespace json_benchmark {
@@ -58,11 +59,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) { if (collector.has_events()) {
state.counters["instructions"] = events.instructions(); state.counters["instructions"] = events.instructions();
state.counters["cycles"] = events.cycles(); state.counters["cycles"] = events.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["branch_miss"] = events.branch_misses(); state.counters["branch_miss"] = events.branch_misses();
state.counters["cache_miss"] = events.cache_misses();
state.counters["cache_ref"] = events.cache_references();
#endif
state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration()); state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration());
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles(); state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration()); state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration());
@@ -70,11 +67,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
state.counters["best_instructions"] = events.best.instructions(); state.counters["best_instructions"] = events.best.instructions();
state.counters["best_cycles"] = events.best.cycles(); state.counters["best_cycles"] = events.best.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["best_branch_miss"] = events.best.branch_misses(); state.counters["best_branch_miss"] = events.best.branch_misses();
state.counters["best_cache_miss"] = events.best.cache_misses();
state.counters["best_cache_ref"] = events.best.cache_references();
#endif
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration()); state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration());
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles(); state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
@@ -95,11 +88,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) { if (collector.has_events()) {
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0); label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0); label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0); label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
#endif
} }
label << " items=" << setw(10) << bench.items_per_iteration() << setw(0); label << " items=" << setw(10) << bench.items_per_iteration() << setw(0);
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "json_benchmark/string_runner.h" #include "json_benchmark/string_runner.h"
#include <fstream>
#include <map> #include <map>
#include <string> #include <string>
@@ -0,0 +1,32 @@
#pragma once
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
#include "large_random.h"
namespace large_random {
using namespace simdjson;
// Identical to simdjson_ondemand but uses get_range() for iteration.
// Demonstrates that the ranges wrapper has zero per-element overhead.
struct simdjson_ondemand_ranges {
static constexpr diff_flags DiffFlags = diff_flags::NONE;
ondemand::parser parser{};
bool run(simdjson::padded_string &json, std::vector<point> &result) {
auto doc = parser.iterate(json);
for (auto coord_result : ondemand::get_range(doc.get_array())) {
ondemand::object coord = coord_result;
result.emplace_back(json_benchmark::point{coord.find_field("x"), coord.find_field("y"), coord.find_field("z")});
}
return true;
}
};
BENCHMARK_TEMPLATE(large_random, simdjson_ondemand_ranges)->UseManualTime();
} // namespace large_random
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
+6 -1
View File
@@ -103,7 +103,12 @@ error_code Sax::RunNoExcept(const padded_string &json) noexcept {
error_code Sax::Allocate(size_t new_capacity) { error_code Sax::Allocate(size_t new_capacity) {
// string_capacity copied from document::allocate // string_capacity copied from document::allocate
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64); // a document with only zero-length strings... could have capacity/3 string
// and we would need capacity/3 * 5 bytes on the string buffer
if(5 * (new_capacity / 3) + SIMDJSON_PADDING < SIMDJSON_PADDING) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * (new_capacity / 3) + SIMDJSON_PADDING, 64);
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]); string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; } if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
if (capacity == 0) { // set max depth the first time only if (capacity == 0) { // set max depth the first time only
-105
View File
@@ -1,105 +0,0 @@
#pragma once
#ifdef __linux__
#include <asm/unistd.h> // for __NR_perf_event_open
#include <linux/perf_event.h> // for perf event constants
#include <sys/ioctl.h> // for ioctl
#include <unistd.h> // for syscall
#include <cerrno> // for errno
#include <cstring> // for memset
#include <stdexcept>
#include <iostream>
#include <vector>
template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
int fd;
bool working;
perf_event_attr attribs{};
size_t num_events{};
std::vector<uint64_t> temp_result_vec{};
std::vector<uint64_t> ids{};
public:
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
memset(&attribs, 0, sizeof(attribs));
attribs.type = TYPE;
attribs.size = sizeof(attribs);
attribs.disabled = 1;
attribs.exclude_kernel = 1;
attribs.exclude_hv = 1;
attribs.sample_period = 0;
attribs.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
const int pid = 0; // the current process
const int cpu = -1; // all CPUs
const unsigned long flags = 0;
int group = -1; // no group
num_events = config_vec.size();
ids.resize(config_vec.size());
uint32_t i = 0;
for (auto config : config_vec) {
attribs.config = config;
int _fd = static_cast<int>(syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags));
if (_fd == -1) {
report_error("perf_event_open");
}
ioctl(_fd, PERF_EVENT_IOC_ID, &ids[i++]);
if (group == -1) {
group = _fd;
fd = _fd;
}
}
temp_result_vec.resize(num_events * 2 + 1);
}
~LinuxEvents() { if (fd != -1) { close(fd); } }
inline void start() {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_RESET)");
}
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
}
}
}
inline void end(std::vector<unsigned long long> &results) {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
}
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
report_error("read");
}
}
// our actual results are in slots 1,3,5, ... of this structure
for (uint32_t i = 1; i < temp_result_vec.size(); i += 2) {
results[i / 2] = temp_result_vec[i];
}
for (uint32_t i = 2; i < temp_result_vec.size(); i += 2) {
if(ids[i/2-1] != temp_result_vec[i]) {
report_error("event mismatch");
}
}
}
bool is_working() {
return working;
}
private:
void report_error(const std::string &) {
working = false;
}
};
#endif
+1 -1
View File
@@ -10,7 +10,7 @@ namespace partial_tweets {
// { // {
// "created_at": "Sun Aug 31 00:29:15 +0000 2014", // "created_at": "Sun Aug 31 00:29:15 +0000 2014",
// "id": 505874924095815700, // "id": 505874924095815700,
// "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖", // "text": "@aym0566x ...",
// "in_reply_to_status_id": null, // "in_reply_to_status_id": null,
// "user": { // "user": {
// "id": 1186275104, // "id": 1186275104,
+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,6 +1,7 @@
#ifndef BENCHMARK_HELPER_HPP #ifndef BENCHMARK_HELPER_HPP
#define BENCHMARK_HELPER_HPP #define BENCHMARK_HELPER_HPP
#include "event_counter.h" #include <counters/event_counter.h>
using namespace counters;
#include <atomic> #include <atomic>
inline event_collector &get_collector() { inline event_collector &get_collector() {
@@ -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)
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}") 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;
}; };
// Type aliases
using Event = CITMEvent;
using Performance = CITMPerformance;
using Price = CITMPrice;
using SeatArea = CITMArea;
using SeatCategoryInfo = CITMSeatCategory;
#endif #endif
@@ -0,0 +1,88 @@
#ifndef GLAZE_CITM_CATALOG_DATA_H
#define GLAZE_CITM_CATALOG_DATA_H
#include <cstdint>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include <glaze/glaze.hpp>
// Glaze-specific shadow types. We mirror the Rust serde struct here (rather
// than the C++ CitmCatalog) so that fields the source JSON encodes as `null`
// (e.g. CITMEvent.name) parse cleanly into std::optional. This matches what
// the Rust benchmark does, and the resulting JSON output volume is therefore
// directly comparable to the Rust numbers.
struct GlazeCITMPrice {
uint64_t amount;
uint64_t audienceSubCategoryId;
uint64_t seatCategoryId;
};
struct GlazeCITMArea {
uint64_t areaId;
std::vector<uint64_t> blockIds;
};
struct GlazeCITMSeatCategory {
std::vector<GlazeCITMArea> areas;
uint64_t seatCategoryId;
};
struct GlazeCITMPerformance {
uint64_t id;
uint64_t eventId;
std::optional<std::string> logo;
std::optional<std::string> name;
std::vector<GlazeCITMPrice> prices;
std::vector<GlazeCITMSeatCategory> seatCategories;
std::optional<std::string> seatMapImage;
uint64_t start;
std::string venueCode;
};
struct GlazeCITMEvent {
uint64_t id;
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> logo;
std::vector<uint64_t> subTopicIds;
std::optional<std::string> subjectCode;
std::optional<std::string> subtitle;
std::vector<uint64_t> topicIds;
};
struct GlazeCitmCatalog {
std::map<std::string, GlazeCITMEvent> events;
std::vector<GlazeCITMPerformance> performances;
};
inline GlazeCitmCatalog glaze_deserialize_citm(const std::string &json_str) {
GlazeCitmCatalog data;
constexpr glz::opts opts{.error_on_unknown_keys = false};
auto err = glz::read<opts>(data, json_str);
if (err) {
throw std::runtime_error("glaze citm parse error: " +
glz::format_error(err, json_str));
}
return data;
}
inline std::string glaze_serialize_citm(const GlazeCitmCatalog &data) {
std::string out;
// skip_null_members = false: emit `"field":null` for unset optionals so the
// output has the same field count as simdjson's (which writes `"field":""`).
// Glaze still produces 4-char `null` vs simdjson's 2-char `""`, so it's not
// byte-identical, but the work-per-field is comparable.
constexpr glz::opts opts{.skip_null_members = false};
auto err = glz::write<opts>(data, out);
if (err) {
throw std::runtime_error("glaze citm write error");
}
return out;
}
#endif // GLAZE_CITM_CATALOG_DATA_H
@@ -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
@@ -0,0 +1,95 @@
// Standalone Glaze benchmark for CITM Catalog (see glaze_twitter_bench.cpp
// header comment for context).
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include "../citm_catalog_benchmark/glaze_citm_catalog_data.h"
#include "../benchmark_utils/benchmark_helper.h"
namespace {
std::string read_file(const std::string &filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 65536;
std::ifstream stream(filename, std::ios::binary);
if (!stream) {
std::cerr << "Could not open file: " << filename << std::endl;
std::exit(EXIT_FAILURE);
}
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;
}
void bench_glaze_serialization(GlazeCitmCatalog &data) {
std::string output = glaze_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_glaze",
bench([&data, &measured_volume, &output_volume]() {
std::string output = glaze_serialize_citm(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
void bench_glaze_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_glaze_parsing",
bench([&json_str, &result]() {
try {
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
} // namespace
int main(int argc, char *argv[]) {
const char *json_file = std::getenv("CITM_JSON");
if (!json_file) {
json_file = "buildreflect/jsonexamples/citm_catalog.json";
}
if (argc > 1) {
json_file = argv[1];
}
std::string json_str = read_file(json_file);
const char *mode = std::getenv("BENCH_MODE");
if (!mode) mode = "all";
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
if (std::string(mode) == "all" || std::string(mode) == "parse") {
printf("\n=== Glaze CITM Parsing ===\n");
bench_glaze_parsing(json_str);
}
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
printf("\n=== Glaze CITM Serialization ===\n");
bench_glaze_serialization(data);
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,99 @@
// Standalone Glaze benchmark for Twitter, built with g++ instead of the
// p2996 clang fork (which crashes on Glaze's heavy template metaprogramming).
// Reuses the same data structs and bench() helper as the in-tree benchmarks
// so the throughput numbers are directly comparable.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "../twitter_benchmark/twitter_data.h"
#include "../twitter_benchmark/glaze_twitter_data.h"
#include "../benchmark_utils/benchmark_helper.h"
namespace {
std::string read_file(const std::string &filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 4096;
std::ifstream stream(filename);
if (!stream) {
std::cerr << "Could not open file: " << filename << std::endl;
std::exit(EXIT_FAILURE);
}
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;
}
void bench_glaze_serialization(TwitterData &data) {
std::string output = glaze_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_glaze",
bench([&data, &measured_volume, &output_volume]() {
std::string output = glaze_serialize(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
void bench_glaze_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_glaze_parsing",
bench([&json_str, &result]() {
try {
TwitterData data = glaze_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
} // namespace
int main(int argc, char *argv[]) {
const char *json_file = std::getenv("TWITTER_JSON");
if (!json_file) {
json_file = "buildreflect/jsonexamples/twitter.json";
}
if (argc > 1) {
json_file = argv[1];
}
std::string json_str = read_file(json_file);
const char *mode = std::getenv("BENCH_MODE");
if (!mode) mode = "all";
TwitterData data = glaze_deserialize(json_str);
if (std::string(mode) == "all" || std::string(mode) == "parse") {
printf("\n=== Glaze Twitter Parsing ===\n");
bench_glaze_parsing(json_str);
}
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
printf("\n=== Glaze Twitter Serialization ===\n");
bench_glaze_serialization(data);
}
return EXIT_SUCCESS;
}
+225 -356
View File
@@ -5,137 +5,40 @@ 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 {
@@ -147,263 +50,120 @@ pub unsafe extern "C" fn twitter_from_str(raw_input: *const c_char, raw_input_l
} }
#[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_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_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,19 +269,42 @@ 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) {
printf("# Failed to parse Twitter data for Rust benchmark\n");
} else {
bench_rust(td); bench_rust(td);
serde_benchmark::free_twitter(td); serde_benchmark::free_twitter(td);
} }
}
#endif #endif
#if SIMDJSON_BENCH_CPP_REFLECT #if SIMDJSON_BENCH_CPP_REFLECT
if (matches_filter("reflect_cpp", filter)) { if (matches_filter("reflect_cpp", filter)) {
@@ -0,0 +1,31 @@
#ifndef GLAZE_TWITTER_DATA_H
#define GLAZE_TWITTER_DATA_H
#include "twitter_data.h"
#include <glaze/glaze.hpp>
#include <stdexcept>
#include <string>
// Glaze auto-reflects aggregate types whose field names already match JSON
// keys (snake_case here matches the JSON), so no glz::meta is required.
inline TwitterData glaze_deserialize(const std::string &json_str) {
TwitterData data;
constexpr glz::opts opts{.error_on_unknown_keys = false};
auto err = glz::read<opts>(data, json_str);
if (err) {
throw std::runtime_error("glaze parse error: " + glz::format_error(err, json_str));
}
return data;
}
inline std::string glaze_serialize(const TwitterData &data) {
std::string out;
auto err = glz::write_json(data, out);
if (err) {
throw std::runtime_error("glaze write error");
}
return out;
}
#endif // GLAZE_TWITTER_DATA_H
@@ -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
+9 -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"
@@ -20,6 +20,14 @@ if(SIMDJSON_GOOGLE_BENCHMARKS)
) )
endif() endif()
CPMAddPackage(
NAME counters
URL https://github.com/lemire/counters/archive/refs/tags/v3.1.0.zip
OPTIONS
"COUNTERS_BUILD_TESTS OFF"
"COUNTERS_INSTALL OFF"
)
CPMAddPackage( CPMAddPackage(
NAME simdjson-data NAME simdjson-data
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
+210 -80
View File
@@ -28,6 +28,9 @@ 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)
- [C++20 Ranges Support](#c20-ranges-support)
- [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)
@@ -56,7 +59,7 @@ Requirements
The simdjson library is widely deployed in popular systems such as the Node.js runtime The simdjson library is widely deployed in popular systems such as the Node.js runtime
environment. environment.
- A recent compiler (LLVM clang 6 or better, GNU GCC 7.4 or better, Xcode 11 or better) on POSIX systems such as macOS, FreeBSD or Linux. We require that the compiler supports the C++11 standard or better. We test the library on a big-endian system (IBM s390x with Linux). - A recent compiler (LLVM clang 6 or better, GNU GCC 7.4 or better, Xcode 11 or better) on POSIX systems such as macOS, FreeBSD or Linux. We require that the compiler supports the C++11 standard or better. We test the library on a big-endian system (IBM s390x with Linux). We support [Fil-C, the memory-safe C/C++ compiler](https://fil-c.org).
- Visual Studio 2017 or better. We support the LLVM clang compiler under Visual Studio (clang-cl) as well as the regular Visual Studio compiler. For better release performance (both compile time and execution time), we recommend Visual Studio users adopt LLVM (clang-cl). We discourage using GCC under Windows: there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412). - Visual Studio 2017 or better. We support the LLVM clang compiler under Visual Studio (clang-cl) as well as the regular Visual Studio compiler. For better release performance (both compile time and execution time), we recommend Visual Studio users adopt LLVM (clang-cl). We discourage using GCC under Windows: there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
Support for AVX-512 require a processor with AVX512-VBMI2 support (Ice Lake or better, AMD Zen 4 or better) under a 64-bit system and a recent compiler (LLVM clang 6 or better, GCC 8 or better, Visual Studio 2019 or better). You need a correspondingly recent assembler such as gas (2.30+) or nasm (2.14+): recent compilers usually come with recent assemblers. If you mix a recent compiler with an incompatible/old assembler (e.g., when using a recent compiler with an old Linux distribution), you may get errors at build time because the compiler produces instructions that the assembler does not recognize: you should update your assembler to match your compiler (e.g., upgrade binutils to version 2.30 or better under Linux) or use an older compiler matching the capabilities of your assembler. Support for AVX-512 require a processor with AVX512-VBMI2 support (Ice Lake or better, AMD Zen 4 or better) under a 64-bit system and a recent compiler (LLVM clang 6 or better, GCC 8 or better, Visual Studio 2019 or better). You need a correspondingly recent assembler such as gas (2.30+) or nasm (2.14+): recent compilers usually come with recent assemblers. If you mix a recent compiler with an incompatible/old assembler (e.g., when using a recent compiler with an old Linux distribution), you may get errors at build time because the compiler produces instructions that the assembler does not recognize: you should update your assembler to match your compiler (e.g., upgrade binutils to version 2.30 or better under Linux) or use an older compiler matching the capabilities of your assembler.
@@ -161,54 +164,77 @@ The basics: loading and parsing JSON documents
---------------------------------------------- ----------------------------------------------
The simdjson library allows you to navigate and validate JSON documents ([RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html)). The simdjson library allows you to navigate and validate JSON documents ([RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html)).
As required by the standard, your JSON document should be in a Unicode (UTF-8) string. The whole Your JSON document should be a valid Unicode (UTF-8) string.
string, from the beginning to the end, needs to be valid: we do not attempt to tolerate bad
inputs before or after a document.
For efficiency reasons, simdjson requires a string with a few bytes (`simdjson::SIMDJSON_PADDING`) To parse JSON, create a `ondemand::parser` and call its `iterate()` method on a padded input.
at the end, these bytes may be read but their content does not affect the parsing. In practice, The simplest way to load a JSON file is with `padded_string::load`:
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
want to if you want to avoid runtime warnings with some sanitizers. We expect the user
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
access by creating a `ondemand::parser` and calling the `iterate()` method. The iterate method
quickly indexes the input string and may detect some errors. The following example illustrates
how to get started with an input JSON file (`"twitter.json"`):
```cpp ```cpp
ondemand::parser parser; ondemand::parser parser;
auto json = padded_string::load("twitter.json"); // load JSON file 'twitter.json'. auto json = padded_string::load("twitter.json");
ondemand::document doc = parser.iterate(json); // position a pointer at the beginning of the JSON data ondemand::document doc = parser.iterate(json);
``` ```
(Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII For inline JSON strings, use the `_padded` suffix:
filenames: `padded_string::load(L"twitter.json")`.)
If you prefer not to create your own `ondemand::parser` instance, you can access ```cpp
a thread-local version by calling `ondemand::parser.get_parser()`. ondemand::parser parser;
auto json = "[1,2,3]"_padded;
ondemand::document doc = parser.iterate(json);
```
If you are compiling with C++17 or better, you can use `simdjson::padded_input`
which accepts any string-like input and handles padding automatically:
```cpp
ondemand::parser parser;
std::string_view json = "[1,2,3]";
simdjson::padded_input input(json);
ondemand::document doc = parser.iterate(input);
// Also works with std::string, considering reserved capacity
std::string json_str = "[1,2,3]";
json_str.reserve(100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying
ondemand::document doc2 = parser.iterate(input2);
```
The simdjson library also accepts `std::string` instances directly---if the provided
reference is non-const, it will allocate padding as needed:
```cpp
ondemand::parser parser;
std::string json = "[1,2,3]";
ondemand::document doc = parser.iterate(json);
```
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
### Advanced input options
This section covers additional ways to provide JSON input to simdjson, including
options for fine-grained control over padding and memory.
**Thread-local parser.** If you prefer not to create your own `ondemand::parser` instance, you can access
a thread-local version by calling `ondemand::parser.get_parser()`:
```cpp ```cpp
ondemand::document doc = ondemand::parser.get_parser().iterate(json); ondemand::document doc = ondemand::parser.get_parser().iterate(json);
``` ```
However, you should be careful because a parser instance can only be used for one A parser instance can only be used for one document at a time, so
document at a time, thus it is only applicable when you are only parsing one the thread-local parser is only applicable when you parse one
document per thread at any one time. document per thread at any one time.
You can also create a padded string---and call `iterate()`: **`padded_input` details (C++17+).** The actual padding only occurs when the JSON string ends near the boundary of a memory page, which is
uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind
might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input`
on systems without a page size of at least 4096: virtually all systems qualify except for
some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, a `simdjson::padded_input` instance will not copy the data and will only act
as a view (it does not own the memory).
```cpp **User-managed buffers.** If you have a buffer of your own with enough padding already (`SIMDJSON_PADDING` extra bytes allocated), you can use `padded_string_view` to pass it in:
ondemand::parser parser;
auto json = "[1,2,3]"_padded; // The _padded suffix creates a simdjson::padded_string instance
ondemand::document doc = parser.iterate(json); // parse a string
```
If you have a buffer of your own with enough padding already (SIMDJSON_PADDING extra bytes allocated), you can use `padded_string_view` to pass it in:
```cpp ```cpp
ondemand::parser parser; ondemand::parser parser;
@@ -217,60 +243,99 @@ strcpy(json, "[1]");
ondemand::document doc = parser.iterate(json, strlen(json), sizeof(json)); ondemand::document doc = parser.iterate(json, strlen(json), sizeof(json));
``` ```
The simdjson library will also accept `std::string` instances. If the provided **Copying into a `padded_string`.** You can copy your data directly into a `simdjson::padded_string`:
reference is non-const, it will allocate padding as needed.
You can copy your data directly on a `simdjson::padded_string` as follows:
```cpp ```cpp
const char * data = "my data"; // 7 bytes const char * data = "my data"; // 7 bytes
simdjson::padded_string my_padded_data(data, 7); // copies to a padded buffer simdjson::padded_string my_padded_data(data, 7); // copies to a padded buffer
``` ```
Or as follows... Or from a `std::string`:
```cpp ```cpp
std::string data = "my data"; std::string data = "my data";
simdjson::padded_string my_padded_data(data); // copies to a padded buffer simdjson::padded_string my_padded_data(data); // copies to a padded buffer
``` ```
You can then parse the JSON data from the `simdjson::padded_string` instance: **`std::string` and sanitizer warnings.** Whenever you pass an `std::string` reference to `parser::iterate`,
the parser may access bytes beyond the end of
```cpp
ondemand::document doc = parser.iterate(my_padded_data);
```
Whenever you pass an `std::string` reference to `parser::iterate`,
the parser will access the bytes beyond the end of
the string but before the end of the allocated memory (`std::string::capacity()`). the string but before the end of the allocated memory (`std::string::capacity()`).
If you are using a sanitizer that checks for reading uninitialized bytes or `std::string`'s Sanitizers that check for reading uninitialized bytes may produce warnings.
container-overflow checks, you may encounter sanitizer warnings. You can safely ignore these warnings, or call `simdjson::pad(std::string&)` to pad the
You can safely ignore these warnings. Or you can call `simdjson::pad(std::string&)` to pad the string explicitly:
string with `SIMDJSON_PADDING` spaces: this function returns a `simdjson::padding_string_view` which can be be passed to the parser's iterator function:
```cpp ```cpp
std::string json = "[1]"; std::string json = "[1]";
ondemand::document doc = parser.iterate(simdjson::pad(json)); ondemand::document doc = parser.iterate(simdjson::pad(json));
``` ```
We recommend against creating many `std::string` or many `std::padding_string` instances in your application to store your JSON data. We recommend against creating many `std::string` or many `std::padded_string` instances in your application to store your JSON data.
Consider reusing the same buffers and limiting memory allocations. Consider reusing the same buffers and limiting memory allocations.
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details. **Memory-file mapping.** You can use `simdjson::padded_memory_map` to create a
`simdjson::padded_string_view` from a file on disk. On POSIX systems (Linux,
macOS, BSD, ...) it uses `mmap` for true zero-copy access and is always
available. On Windows it is an **opt-in** feature because it relies on the
`CreateFileMapping2` / `MapViewOfFile3` APIs (Windows 10, version 1803 or
later) which are exported from `onecore.lib` rather than the default
`kernel32.lib`. To enable it, you must satisfy **all** of the following:
Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples: 1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before including simdjson, in every translation
unit that uses `padded_memory_map`.
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn) The Windows implementation then uses `CreateFileMapping2` / `MapViewOfFile3`
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo) for true zero-copy access whenever possible, with a transparent
buffered-read fallback for files that end too close to a page boundary.
*Windows-specific*: Windows users who need to read files with The availability of the class can be tested with the preprocessor macro
`SIMDJSON_HAS_PADDED_MEMORY_MAP`.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view();
ondemand::document doc = parser.iterate(view);
```
**Windows-specific notes.** Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
filenames: `padded_string::load(L"twitter.json")`. Windows users who need to read files with
non-ANSI characters in the name should set their code page to non-ANSI characters in the name should set their code page to
UTF-8 (65001). This should be the default with Windows 11 and better. UTF-8 (65001). This should be the default with Windows 11 and better.
Further, they may use the AreFileApisANSI function to determine whether 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.
Some users may want to browse code along with the compiled assembly:
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo)
**Summary of input types:**
| Input Type / Method | Padding Requirement | How Padding is Handled | Ownership / Copying | Notes / Warnings |
|----------------------------------------------|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------|----------------------------------------------------------------------------------|
| `padded_string::load("file.json")` | Automatic (SIMDJSON_PADDING extra bytes) | Library allocates padded buffer and loads file into it | Owned by `padded_string` | Recommended for files; safest and simplest. |
| `"...json..."_padded` literal | Automatic (built-in padding) | Creates `padded_string` with padding | Owned by `padded_string` | Convenient for small hardcoded JSON. |
| `simdjson::padded_input` (C++17+) | Automatic when needed | Adds padding **only** if the string ends near a memory page boundary. For `std::string`, considers `capacity()` | Usually a non-owning view (no copy most times) | Safe on standard OS (page size ≥ 4096). May trigger sanitizer/valgrind warnings (harmless). Avoid on niche embedded systems. |
| User buffer with explicit padding | Must have at least `SIMDJSON_PADDING` extra allocated bytes after JSON content | Pass via `iterate(ptr, json_length, total_allocated_size)` or `padded_string_view` | User-owned (no copy) | Use `char buf[len + SIMDJSON_PADDING]`. Library reads (but never writes) into padding. |
| `std::string` (non-const) | Library checks `capacity()` | If insufficient, library may allocate a padded copy | May copy (depends on capacity) | Can trigger sanitizer warnings on uninitialized bytes. Use `simdjson::pad(json)` to avoid. |
| `simdjson::pad(std::string&)` | Adds padding if needed | Returns `padded_string_view` pointing to the (possibly resized) string | References original string | Recommended to silence sanitizers when using `std::string`. |
| `padded_string(data, length)` or `padded_string(std::string)` | Automatic (copies into padded buffer) | Explicit copy into owned padded buffer | Owned by `padded_string` | Safe when you want full ownership and padding guaranteed. |
| `padded_string_view` (manual) | User guarantees `SIMDJSON_PADDING` extra bytes after the viewed length | User provides pointer + length + capacity | Non-owning view | Low-level; requires careful buffer management. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping / padded read | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Always available on POSIX (zero-copy `mmap`). On Windows, opt-in via `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON` (requires Windows 10 1803+ and links `onecore.lib`) and `#include <windows.h>` before simdjson; uses `CreateFileMapping2` + `MapViewOfFile3`. |
Documents are iterators Documents are iterators
----------------------- -----------------------
@@ -374,6 +439,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
--------------------- ---------------------
@@ -417,7 +486,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,
@@ -427,8 +496,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.
@@ -469,7 +538,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
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
@@ -1281,10 +1350,6 @@ You can also use the custom `Car` type as part of a template such as `std::vecto
simdjson::ondemand::parser parser; simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(json); simdjson::ondemand::document doc = parser.iterate(json);
std::vector<Car> cars(doc); std::vector<Car> cars(doc);
// visual studio users need an explicit call:
// std::vector<Car> cars = doc.get<std::vector<Car>>();
// because the compiler does not know whether to convert
// doc to an unsigned int or to a vector.
for(Car& c : cars) { for(Car& c : cars) {
std::cout << c.year << std::endl; std::cout << c.year << std::endl;
} }
@@ -1378,6 +1443,8 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
that are not made by Toyota. that are not made by Toyota.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
### 3. Using static reflection (C++26) ### 3. Using static reflection (C++26)
@@ -1454,6 +1521,10 @@ void f() {
} }
``` ```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
#### Special cases #### Special cases
However, there are instances where the construction cannot However, there are instances where the construction cannot
@@ -1531,6 +1602,10 @@ type without a document instance like so:
Car car = simdjson::from(json); Car car = simdjson::from(json);
``` ```
The string must be a `simdjson::padded_string_view`, which can be created from an std::string
instance with `simdjson::pad()` function, from a `simdjson::padded_string` instance, or string literal using the `_padded` user-defined literal.
You can also use the `simdjson::from` syntax without exceptions, like so: You can also use the `simdjson::from` syntax without exceptions, like so:
```cpp ```cpp
Car car; Car car;
@@ -1771,15 +1846,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"(
@@ -1808,31 +1883,86 @@ 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) {
for (auto value : values) {
std::string_view field; std::string_view field;
if (value.get(field) == SUCCESS) { if (value.get(field) == SUCCESS) {
std::cout << field << std::endl; 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.
## C++20 Ranges Support
When compiling with C++20 (or later), you can use `std::ranges` with the On-Demand API
via the `get_range()` helper. This enables use of range adaptors such as `std::views::transform`.
```cpp
#include "simdjson.h"
#include <ranges>
#include <string>
#include <vector>
auto json = R"([
{ "name": "Alice", "age": 30 },
{ "name": "Bob", "age": 25 },
{ "name": "Carol", "age": 35 }
])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
// Use std::views::transform to extract names
auto names = ondemand::get_range(arr)
| std::views::transform([](auto elem) -> std::string {
return std::string(std::string_view(elem["name"]));
});
for (auto name : names) {
std::cout << name << std::endl; // Alice, Bob, Carol
}
```
The `get_range()` and `get_key_value_range()` functions wrap an `ondemand::array`
or `ondemand::object` in a `std::ranges::view` that satisfies `std::ranges::input_range`.
They work with both exception and non-exception code:
```cpp
// With exceptions:
auto range = ondemand::get_range(doc.get_array());
// Without exceptions:
ondemand::array arr;
if (doc.get_array().get(arr) == SUCCESS) {
auto range = ondemand::get_range(arr);
for (auto elem : range) { /* ... */ }
}
```
Object iteration uses `get_key_value_range()` and yields `simdjson_result<ondemand::field>` elements:
```cpp
auto obj = doc.get_object();
for (auto field_result : ondemand::get_key_value_range(obj)) {
std::cout << field_result.key() << std::endl;
}
```
The range wrappers are zero-cost: they forward directly to the underlying
On-Demand iterators with no value buffering or extra per-element overhead.
## Compile-Time JSONPath and JSON Pointer (C++26 Reflection) ## Compile-Time JSONPath and JSON Pointer (C++26 Reflection)
The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression
+23
View File
@@ -32,6 +32,21 @@ your code with the `SIMDJSON_STATIC_REFLECTION` macro set:
The `simdjson::compile_time::parse_json` function parses a JSON document at **compile time** and returns a `constexpr` structure reflecting its content. We support the full range of JSON values, which are mapped to C++ types as in The `simdjson::compile_time::parse_json` function parses a JSON document at **compile time** and returns a `constexpr` structure reflecting its content. We support the full range of JSON values, which are mapped to C++ types as in
the following table. the following table.
For convenience, you can also use the `""_json` user-defined literal operator, which is available in the `simdjson::literals` namespace:
```cpp
using namespace simdjson::literals;
constexpr auto cfg = R"(
{
"port": 8080,
"host": "localhost"
}
)"_json;
```
Alternatively, you can use the qualified name `simdjson::literals::operator""_json`.
| JSON type | C++ type | | JSON type | C++ type |
|----------------|----------------------------------| |----------------|----------------------------------|
@@ -61,6 +76,8 @@ You can do so, at compile-time, as follows:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto cfg = R"( constexpr auto cfg = R"(
{ {
@@ -79,6 +96,8 @@ constexpr auto cfg = R"(
You can nest objects and arrays: You can nest objects and arrays:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto data = R"( constexpr auto data = R"(
{ {
@@ -98,6 +117,8 @@ constexpr auto data = R"(
Top-level arrays are allowed: Top-level arrays are allowed:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto arr = R"( constexpr auto arr = R"(
[1, 2, 3] [1, 2, 3]
@@ -116,6 +137,8 @@ want to check that it conforms to your expectation. You can do so with concepts.
Let us consider this example: Let us consider this example:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto config = R"( constexpr auto config = R"(
[ [
+70 -1
View File
@@ -125,6 +125,42 @@ 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:**
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view`
from a file on disk without copying the file contents into your own buffer.
On POSIX systems (Linux, macOS, BSD, ...) it uses `mmap` for true zero-copy
access. On Windows it is available as an **opt-in** feature and requires:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`.
When enabled on Windows, the implementation uses `CreateFileMapping2` and
`MapViewOfFile3` for true zero-copy mapping whenever the file does not end
within `SIMDJSON_PADDING` bytes of a page boundary; otherwise it falls back
to reading the file into a padded heap buffer. If those requirements are
not met, the class is not declared and the code below will fail to compile.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
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
--------------------- ---------------------
@@ -152,6 +188,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) { ... }`
@@ -164,7 +217,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
@@ -737,6 +790,9 @@ void basics_treewalk_1() {
} }
``` ```
Notice that we do not include `dom::element_type::BIGINT` in this example
as `dom::element_type::BIGINT` type is only generated if the parser was
set to support big integers (`parser.number_as_string(true)`).
Reusing the parser for maximum efficiency Reusing the parser for maximum efficiency
@@ -849,6 +905,19 @@ simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len,
Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes. Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes.
If you are compiling your project with C++17 or better, you can use a `simdjson::padded_input`:
```cpp
simdjson::dom::parser parser;
std::string_view json = "[1,2,3]";
simdjson::padded_input input(json); // Automatically pads if needed
simdjson::dom::element element = parser.parse(input);
```
The actual padding only occurs if the JSON string ends near the boundary of a memory page, which is uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input` on systems without a page size of at least 4096: virtually all systems qualify except for some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, an `simdjson::padded_input` instance will not copy the data and will only act
as a view (it does not own the memory).
Performance Tips Performance Tips
--------------------- ---------------------
+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).
+350 -18
View File
@@ -22,10 +22,12 @@ Contents
- [Threads](#threads) - [Threads](#threads)
- [Support](#support) - [Support](#support)
- [API](#api) - [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases) - [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position) - [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams) - [Incomplete streams](#incomplete-streams)
- [C++20 features](#c20-features) - [C++20 features](#c20-features)
- [C++26 features (static reflection)](#c26-features-static-reflection)
Motivation Motivation
----------- -----------
@@ -132,7 +134,7 @@ E.g., `[1,2]{"32":1}` is recognized as two documents.
Some official formats **(non-exhaustive list)**: Some official formats **(non-exhaustive list)**:
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/) - [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/)
- [JSON lines (JSONL)](http://jsonlines.org/) - [JSON lines (JSONL)](http://jsonlines.org/)
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson! - [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming) - [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
API API
@@ -154,6 +156,86 @@ 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.
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most efficient
way to feed `iterate_many` is to use `simdjson::padded_memory_map`. It returns
a `padded_string_view` with the right amount of trailing padding, so you can
hand it straight to `iterate_many` without ever copying the file contents into
your own buffer.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::ondemand::parser parser;
simdjson::ondemand::document_stream stream;
auto error = parser.iterate_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.iterate(...)`:
```cpp
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/):
@@ -264,39 +346,131 @@ Importantly, you should only call `truncated_bytes()` after iterating through al
Comma-separated documents Comma-separated documents
----------- -----------
We also support comma-separated documents, but with some performance limitations. The `iterate_many` function takes in an option to allow parsing of comma separated documents (which defaults on false). In this mode, the entire buffer is processed in one batch. Therefore, the total size of the document should not exceed the maximal capacity of the parser (4 GB). This mode also effectively disallow multithreading. It is therefore mostly suitable for not "very large" inputs. In this mode, the batch_size parameter To parse comma-separated documents like `{"a":1},{"b":2},{"c":3}`, use the `stream_format::comma_delimited` parameter:
is effectively ignored, as it is set to at least the document size.
Example: ```cpp
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
Whitespace around the commas is allowed:
```cpp
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
```
Nested commas inside objects and arrays are preserved:
```cpp
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
// Correctly parses as 2 documents, not 6
```
Mixed document types are supported:
```cpp ```cpp
auto json = R"(1, 2, 3, 4, "a", "b", "c", {"hello": "world"}, [1, 2, 3])"_padded; auto json = R"(1, 2, 3, 4, "a", "b", "c", {"hello": "world"}, [1, 2, 3])"_padded;
ondemand::parser parser; ondemand::parser parser;
ondemand::document_stream doc_stream; ondemand::document_stream doc_stream;
// We pass '32' as the batch size, but it is a bogus parameter because, since auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
// we pass 'true' to the allow_comma parameter, the batch size will be set to at least simdjson::stream_format::comma_delimited).get(doc_stream);
// the document size.
auto error = parser.iterate_many(json, 32, true).get(doc_stream);
if (error) { std::cerr << error << std::endl; return; } if (error) { std::cerr << error << std::endl; return; }
for (auto doc : doc_stream) { for (auto doc : doc_stream) {
std::cout << doc.type() << std::endl; std::cout << doc.type() << std::endl;
} }
// Prints: number number number number string string string object array
``` ```
This will print: Extra top-level separators are tolerated for compatibility with the legacy
`allow_comma_separated` behavior. For example, leading commas, trailing commas,
and repeated commas are treated as empty separators rather than documents.
### Legacy `allow_comma_separated` parameter (deprecated)
The `allow_comma_separated` boolean parameter is deprecated. When set to `true`, it now internally maps to `stream_format::comma_delimited`.
The old single-batch limitation no longer applies - comma-delimited parsing now supports multi-batch processing and threading for optimal performance on large files.
JSON Text Sequences (RFC 7464)
------------------------------
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
Example input:
``` ```
number <RS>{"name":"doc1"}<LF>
number <RS>{"name":"doc2"}<LF>
number <RS>{"name":"doc3"}<LF>
number
string
string
string
object
array
``` ```
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
simdjson::padded_string input(input_str);
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
```
The `stream_format` enum has the following values:
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
- `stream_format::comma_delimited`: Comma-separated JSON documents
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
JSON Array As A Document Stream
-------------------------------
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
```cpp
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
```cpp
// All of these work:
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
auto c = R"([])"_padded; // empty array → 0 docs
```
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `iterate_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
C++20 features C++20 features
-------------------- --------------------
@@ -404,3 +578,161 @@ Otherwise you may use this longer version for explicit handling of errors:
cars.push_back(c); cars.push_back(c);
} }
``` ```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
C++26 features (static reflection)
-----------------------------------
If you have a C++26 compatible compiler with [P2996](https://wg21.link/P2996)
static reflection support, you can compile the simdjson library with the
`SIMDJSON_STATIC_REFLECTION` macro set to `1`. When this is the case, simdjson
can deserialize a stream of JSON documents directly into your own structures
**without** writing any `tag_invoke` function. The library inspects the
non-static public members of your type at compile time and produces the
parsing code automatically.
```cpp
#define SIMDJSON_STATIC_REFLECTION 1
#include "simdjson.h"
```
Consider the same `Car` structure used in the C++20 example, but **without**
any `tag_invoke` glue:
```cpp
struct Car {
std::string make;
std::string model;
int year;
std::vector<double> tire_pressure;
};
```
With C++26 static reflection enabled, you can iterate a stream of cars and
push them into a `std::vector<Car>` directly:
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] }
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] }
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
This works for every `stream_format` value supported by `iterate_many`. The
following examples each parse the same three cars, but laid out using a
different streaming convention.
### Whitespace-delimited (default, NDJSON / JSON Lines)
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] }
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] }
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::whitespace_delimited).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
cars.push_back((Car)doc); // throws on error
}
```
### Comma-delimited documents
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
### A single JSON array as a stream of documents
When the input is a single JSON array, you can stream its elements one at a
time without materializing the entire array as a `std::vector` upfront:
```cpp
auto json = R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } ] )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
### JSON Text Sequences (RFC 7464)
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
auto append = [&](std::string_view doc) {
input_str += '\x1e'; input_str += doc; input_str += '\x0a';
};
append(R"({ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9 ] })");
append(R"({ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0 ] })");
append(R"({ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0 ] })");
simdjson::padded_string input(input_str);
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
In every case, the user-defined type (`Car` here) does not need a hand-written
`tag_invoke` overload: the library generates the deserialization code from the
type's public data members at compile time.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
+194 -1
View File
@@ -18,6 +18,7 @@ Contents
- [How it works](#how-it-works) - [How it works](#how-it-works)
- [Support](#support) - [Support](#support)
- [API](#api) - [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases) - [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position) - [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams) - [Incomplete streams](#incomplete-streams)
@@ -132,7 +133,7 @@ Whitespace Characters:
Some official formats **(non-exhaustive list)**: Some official formats **(non-exhaustive list)**:
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec) - [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec)
- [JSON lines (JSONL)](http://jsonlines.org/) - [JSON lines (JSONL)](http://jsonlines.org/)
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson! - [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming) - [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
API API
@@ -217,6 +218,86 @@ got full document at 29
``` ```
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most
efficient way to feed `parse_many` is to use `simdjson::padded_memory_map`.
It returns a `padded_string_view` with the right amount of trailing padding,
so you can pass it directly to `parse_many` without copying the file content
into your own buffer first.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.parse(...)`:
```cpp
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
simdjson::dom::element doc = parser.parse(view); // parse the JSON
```
Incomplete streams Incomplete streams
----------- -----------
@@ -239,3 +320,115 @@ Consider the following example where a truncated document (`{"key":"intentionall
Importantly, you should only call `truncated_bytes()` after iterating through all of the documents since the stream cannot tell whether there are truncated documents at the very end when it may not have accessed that part of the data yet. Importantly, you should only call `truncated_bytes()` after iterating through all of the documents since the stream cannot tell whether there are truncated documents at the very end when it may not have accessed that part of the data yet.
JSON Text Sequences (RFC 7464)
------------------------------
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
Example input:
```
<RS>{"name":"doc1"}<LF>
<RS>{"name":"doc2"}<LF>
<RS>{"name":"doc3"}<LF>
```
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
simdjson::padded_string input(input_str);
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(input, simdjson::dom::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
```
The `stream_format` enum has the following values:
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
- `stream_format::comma_delimited`: Comma-separated JSON documents
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
Comma-Separated Documents
-------------------------
Some systems produce JSON documents separated by commas, like `{"a":1},{"b":2},{"c":3}`. This is common when extracting elements from a JSON array or when APIs return comma-separated results.
To parse comma-separated documents, use the `stream_format::comma_delimited` parameter:
```cpp
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
Whitespace around the commas is allowed:
```cpp
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
```
Nested commas inside objects and arrays are preserved:
```cpp
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
// Correctly parses as 2 documents, not 6
```
Extra top-level separators are tolerated for compatibility with the legacy
On-Demand comma-separated mode. Leading commas, trailing commas, and repeated
commas are treated as empty separators rather than documents.
Unlike the legacy `allow_comma_separated` parameter, `stream_format::comma_delimited` supports multi-batch processing and threading for optimal performance on large files.
JSON Array As A Document Stream
-------------------------------
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
```cpp
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
```cpp
// All of these work:
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
auto c = R"([])"_padded; // empty array → 0 docs
```
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `parse_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
+26 -4
View File
@@ -204,9 +204,31 @@ but can be significantly larger. E.g., Apple systems favour pages spanning 16 ki
In effect, it means that you can almost always read a few bytes beyond your current buffer---without In effect, it means that you can almost always read a few bytes beyond your current buffer---without
allocating extra memory. However, tools such as valgrind or memory sanitizers will flag such behavior as unsafe. allocating extra memory. However, tools such as valgrind or memory sanitizers will flag such behavior as unsafe.
Nevertheless, you can still make sure of this capability in your code if you are an expert You can still make sure of this capability in your code if you are an expert
programmer and you are willing to silence sanitizer warnings. The following code provides programmer and you are willing to silence sanitizer warnings.
a portable example.
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
Example usage:
```cpp
std::string_view json = get_json_data();
simdjson::padded_input input(json); // Automatically pads if needed
auto result = parser.parse(input);
// Also works with std::string, considering capacity
std::string json_str = get_json_string();
json_str.reserve(json_str.size() + 100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
auto result2 = parser.parse(input2);
```
This simplifies padding management compared to manually checking and allocating.
More generally, the following code provides a portable example.
The conditional compilation checks for the `_MSC_VER` macro (indicating Microsoft Visual Studio) The conditional compilation checks for the `_MSC_VER` macro (indicating Microsoft Visual Studio)
@@ -250,7 +272,7 @@ long page_size() {
} }
// Returns true if the buffer + len + simdjson::SIMDJSON_PADDING crosses the // Returns true if the buffer + len + simdjson::SIMDJSON_PADDING crosses the
// page boundary. // page boundary. Assumes len != 0.
bool need_allocation(const char *buf, size_t len) { bool need_allocation(const char *buf, size_t len) {
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size()) return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size())
+ simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size())); + simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size()));
+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) {
+19
View File
@@ -21,6 +21,10 @@ SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS
/** The maximum document size supported by simdjson. */ /** The maximum document size supported by simdjson. */
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF; constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
/** The maximum depth of nested objects and arrays supported by simdjson.
A depth of SIMDJSON_MAXSIZE_BYTES/2 is not reasonable and would be
adversarial, but it serves as an upper bound for validation purposes. */
constexpr size_t SIMDJSON_MAX_DEPTH = SIMDJSON_MAXSIZE_BYTES/2;
/** /**
* The amount of padding needed in a buffer to parse JSON. * The amount of padding needed in a buffer to parse JSON.
@@ -46,6 +50,21 @@ struct padded_string;
class padded_string_view; class padded_string_view;
enum class stage1_mode; enum class stage1_mode;
/**
* Stream format for parse_many/iterate_many.
*/
enum class stream_format {
whitespace_delimited, ///< Whitespace-delimited JSON documents (default, includes NDJSON/JSONL)
json_sequence, ///< RFC 7464 JSON text sequences (RS-delimited)
comma_delimited, ///< Comma-separated JSON documents (e.g., `{...},{...},{...}`)
comma_delimited_array ///< A single JSON array whose elements are iterated as
///< comma-separated documents (e.g., `[{...},{...},{...}]`).
///< The parser strips the outer `[` / `]` plus any
///< surrounding JSON whitespace (space, tab, LF, CR)
///< and then behaves like `comma_delimited` over the
///< remaining bytes.
};
namespace internal { namespace internal {
template<typename T> template<typename T>
+15 -3
View File
@@ -30,6 +30,10 @@ double from_chars(const char *first, const char* end) noexcept;
#endif #endif
#endif #endif
#ifndef SIMDJSON_ENABLE_NAN_INF
#define SIMDJSON_ENABLE_NAN_INF 0
#endif
} // namespace simdjson } // namespace simdjson
#if defined(__GNUC__) #if defined(__GNUC__)
@@ -45,9 +49,6 @@ double from_chars(const char *first, const char* end) noexcept;
// Align to N-byte boundary // Align to N-byte boundary
#define SIMDJSON_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1)) #define SIMDJSON_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1))
#define SIMDJSON_ROUNDDOWN_N(a, n) ((a) & ~((n)-1))
#define SIMDJSON_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0)
#if SIMDJSON_REGULAR_VISUAL_STUDIO #if SIMDJSON_REGULAR_VISUAL_STUDIO
// We could use [[deprecated]] but it requires C++14 // We could use [[deprecated]] but it requires C++14
@@ -55,6 +56,7 @@ double from_chars(const char *first, const char* end) noexcept;
#define simdjson_really_inline __forceinline #define simdjson_really_inline __forceinline
#define simdjson_never_inline __declspec(noinline) #define simdjson_never_inline __declspec(noinline)
#define simdjson_really_flatten [[msvc::flatten]]
#define simdjson_unused #define simdjson_unused
#define simdjson_warn_unused #define simdjson_warn_unused
@@ -95,6 +97,7 @@ double from_chars(const char *first, const char* end) noexcept;
#define simdjson_really_inline inline __attribute__((always_inline)) #define simdjson_really_inline inline __attribute__((always_inline))
#define simdjson_never_inline inline __attribute__((noinline)) #define simdjson_never_inline inline __attribute__((noinline))
#define simdjson_really_flatten [[gnu::flatten]]
#define simdjson_unused __attribute__((unused)) #define simdjson_unused __attribute__((unused))
#define simdjson_warn_unused __attribute__((warn_unused_result)) #define simdjson_warn_unused __attribute__((warn_unused_result))
@@ -171,6 +174,15 @@ double from_chars(const char *first, const char* end) noexcept;
#define simdjson_inline simdjson_really_inline #define simdjson_inline simdjson_really_inline
#endif #endif
#if defined(simdjson_flatten)
// Prefer the user's definition of simdjson_flatten; don't define it ourselves.
#elif (defined(__GNUC__) && !defined(__OPTIMIZE__)) || (defined(_DEBUG) && _MSC_VER )
// Flattening can lead to significant code bloat and high compile times. Don't use it for unoptimized builds.
#define simdjson_flatten
#else
#define simdjson_flatten simdjson_really_flatten
#endif
#if SIMDJSON_VISUAL_STUDIO #if SIMDJSON_VISUAL_STUDIO
/** /**
* Windows users need to do some extra work when building * Windows users need to do some extra work when building
+34 -33
View File
@@ -266,6 +266,12 @@ consteval std::pair<double, size_t> parse_double(const char *src,
} }
} // namespace number_parsing } // namespace number_parsing
consteval auto make_data_member_options(auto&& name_str) {
std::meta::data_member_options options{};
options.name = std::forward<decltype(name_str)>(name_str);
return options;
}
// JSON string may contain embedded nulls, and C++26 reflection does not yet // JSON string may contain embedded nulls, and C++26 reflection does not yet
// support std::string_view as a data member type. As a workaround, we define // support std::string_view as a data member type. As a workaround, we define
// a custom type that holds a const char* and a size. // a custom type that holds a const char* and a size.
@@ -320,7 +326,8 @@ using class_type = type_builder<meta_info...>::constructed_type;
/** /**
* @brief Variable template for constructing instances with values * @brief Variable template for constructing instances with values
*/ */
template <typename T, auto... Vs> constexpr auto construct_from = T{Vs...}; template <typename T, auto... Vs> constexpr T construct_from = T{Vs...};
// in JSON, there are only a few whitespace characters that are allowed // in JSON, there are only a few whitespace characters that are allowed
// outside of objects, arrays, strings, and numbers. // outside of objects, arrays, strings, and numbers.
@@ -410,7 +417,7 @@ parse_number(std::string_view json,
std::from_chars(json.data(), json.data() + json.size(), int_value); std::from_chars(json.data(), json.data() + json.size(), int_value);
if (res.ec == std::errc()) { if (res.ec == std::errc()) {
out = int_value; out = int_value;
if ((res.ptr - json.data()) != scope) { if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
simdjson_consteval_error( simdjson_consteval_error(
"Internal error: cannot agree on the character range of the float"); "Internal error: cannot agree on the character range of the float");
} }
@@ -424,7 +431,7 @@ parse_number(std::string_view json,
std::from_chars(json.data(), json.data() + json.size(), uint_value); std::from_chars(json.data(), json.data() + json.size(), uint_value);
if (res.ec == std::errc()) { if (res.ec == std::errc()) {
out = uint_value; out = uint_value;
if ((res.ptr - json.data()) != scope) { if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
simdjson_consteval_error( simdjson_consteval_error(
"Internal error: cannot agree on the character range of the float"); "Internal error: cannot agree on the character range of the float");
} }
@@ -533,7 +540,7 @@ parse_string(std::string_view json) {
// present, we have an error (isolated high surrogate), which we // present, we have an error (isolated high surrogate), which we
// tolerate by substituting the substitution_code_point. // tolerate by substituting the substitution_code_point.
if (end - cursor < 6 || *cursor != '\\' || if (end - cursor < 6 || *cursor != '\\' ||
*(cursor + 1) != 'u' > 0xFFFF) { *(cursor + 1) != 'u') {
code_point = substitution_code_point; code_point = substitution_code_point;
} else { // we have \u following the high surrogate } else { // we have \u following the high surrogate
cursor += 2; // skip \u cursor += 2; // skip \u
@@ -887,10 +894,19 @@ parse_json_array_impl(const std::string_view json) {
std::size_t count = values.size() - 1; std::size_t count = values.size() - 1;
// We assume all elements have the same type as the first element. // We assume all elements have the same type as the first element.
// However, if the array is heterogeneous, we should use std::variant. // However, if the array is heterogeneous, we should use std::variant.
auto elem_type = std::meta::type_of(values[1]);
// String literals reflected via reflect_constant_string have type const
// char[N], but when passed as template auto parameters they decay to
// const char*. Use const char* as the element type so that
// construct_from can aggregate-initialize the array.
if (std::meta::is_array_type(elem_type) &&
std::meta::remove_all_extents(elem_type) == ^^const char) {
elem_type = ^^const char *;
}
auto array_type = std::meta::substitute( auto array_type = std::meta::substitute(
^^std::array, ^^std::array,
{ {
std::meta::type_of(values[1]), std::meta::reflect_constant(count)}); elem_type, std::meta::reflect_constant(count)});
// Create array instance with values // Create array instance with values
values[0] = array_type; values[0] = array_type;
@@ -957,8 +973,7 @@ parse_json_object_impl(std::string_view json) {
simdjson_consteval_error("Expected '}'"); simdjson_consteval_error("Expected '}'");
} }
cursor += object_size; cursor += object_size;
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
{.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(parsed); values.push_back(parsed);
@@ -967,8 +982,7 @@ parse_json_object_impl(std::string_view json) {
case '[': { case '[': {
std::string_view value(cursor, end); std::string_view value(cursor, end);
auto [parsed, array_size] = parse_json_array_impl(value); auto [parsed, array_size] = parse_json_array_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
{.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(parsed); values.push_back(parsed);
if (*(cursor + array_size - 1) != ']') { if (*(cursor + array_size - 1) != ']') {
@@ -989,8 +1003,7 @@ parse_json_object_impl(std::string_view json) {
} }
} }
auto dms = auto dms =
std::meta::data_member_spec(^^const char *, { std::meta::data_member_spec(^^const char *, make_data_member_options(field_name));
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant_string(value)); values.push_back(std::meta::reflect_constant_string(value));
break; break;
@@ -1001,8 +1014,7 @@ parse_json_object_impl(std::string_view json) {
} }
cursor += 4; cursor += 4;
auto dms = std::meta::data_member_spec(^^bool, { auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(true)); values.push_back(std::meta::reflect_constant(true));
break; break;
@@ -1013,8 +1025,7 @@ parse_json_object_impl(std::string_view json) {
} }
cursor += 5; cursor += 5;
auto dms = std::meta::data_member_spec(^^bool, { auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(false)); values.push_back(std::meta::reflect_constant(false));
break; break;
@@ -1025,9 +1036,7 @@ parse_json_object_impl(std::string_view json) {
} }
cursor += 4; cursor += 4;
auto dms = std::meta::data_member_spec(^^std::nullptr_t, auto dms = std::meta::data_member_spec(^^std::nullptr_t, make_data_member_options(field_name));
{
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(nullptr)); values.push_back(std::meta::reflect_constant(nullptr));
break; break;
@@ -1051,22 +1060,19 @@ parse_json_object_impl(std::string_view json) {
if (std::holds_alternative<int64_t>(out)) { if (std::holds_alternative<int64_t>(out)) {
int64_t int_value = std::get<int64_t>(out); int64_t int_value = std::get<int64_t>(out);
auto dms = auto dms =
std::meta::data_member_spec(^^int64_t, { std::meta::data_member_spec(^^int64_t, make_data_member_options(field_name));
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(int_value)); values.push_back(std::meta::reflect_constant(int_value));
} else if (std::holds_alternative<uint64_t>(out)) { } else if (std::holds_alternative<uint64_t>(out)) {
uint64_t uint_value = std::get<uint64_t>(out); uint64_t uint_value = std::get<uint64_t>(out);
auto dms = auto dms =
std::meta::data_member_spec(^^uint64_t, { std::meta::data_member_spec(^^uint64_t, make_data_member_options(field_name));
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(uint_value)); values.push_back(std::meta::reflect_constant(uint_value));
} else { } else {
double float_value = std::get<double>(out); double float_value = std::get<double>(out);
auto dms = auto dms =
std::meta::data_member_spec(^^double, { std::meta::data_member_spec(^^double, make_data_member_options(field_name));
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(float_value)); values.push_back(std::meta::reflect_constant(float_value));
} }
@@ -1111,16 +1117,11 @@ template <constevalutil::fixed_string json_str> consteval auto parse_json() {
"Only JSON objects and arrays are supported at the top level, this " "Only JSON objects and arrays are supported at the top level, this "
"limitation will be lifted in the future.");*/ "limitation will be lifted in the future.");*/
constexpr auto result = json.front() == '[' if constexpr (json.front() == '[') {
? parse_json_array_impl(json)
: parse_json_object_impl(json);
return [: result.first :];
/*
if(json.front() == '[') {
return [: parse_json_array_impl(json).first :]; return [: parse_json_array_impl(json).first :];
} else if(json.front() == '{') { } else {
// return [:parse_json_object_impl(json).first:]; return [: parse_json_object_impl(json).first :];
}*/ }
} }
} // namespace compile_time } // namespace compile_time
+4 -1
View File
@@ -63,13 +63,16 @@ namespace compile_time {
template <constevalutil::fixed_string json_str> consteval auto parse_json(); template <constevalutil::fixed_string json_str> consteval auto parse_json();
} // namespace compile_time } // namespace compile_time
} // namespace simdjson
inline namespace literals {
template <simdjson::constevalutil::fixed_string str> template <simdjson::constevalutil::fixed_string str>
consteval auto operator ""_json() { consteval auto operator ""_json() {
return simdjson::compile_time::parse_json<str>(); return simdjson::compile_time::parse_json<str>();
} }
} // namespace literals
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION #endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H #endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
-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
+5
View File
@@ -58,6 +58,11 @@ struct fixed_string {
data[i] = str[i]; data[i] = str[i];
} }
} }
constexpr fixed_string(const unsigned char (&str)[N]) {
for (std::size_t i = 0; i < N; ++i) {
data[i] = static_cast<char>(str[i]);
}
}
char data[N]; char data[N];
constexpr std::string_view view() const { return {data, N - 1}; } constexpr std::string_view view() const { return {data, N - 1}; }
constexpr size_t size() const { return N ; } constexpr size_t size() const { return N ; }
-13
View File
@@ -37,9 +37,6 @@ inline auto_parser<parser_type>::auto_parser(parser_type parser, ondemand::docum
: auto_parser{*parser, std::move(doc)} {} : auto_parser{*parser, std::move(doc)} {}
template <typename parser_type> template <typename parser_type>
inline std::remove_pointer_t<parser_type> &auto_parser<parser_type>::parser() noexcept { inline std::remove_pointer_t<parser_type> &auto_parser<parser_type>::parser() noexcept {
if constexpr (std::is_pointer_v<parser_type>) { if constexpr (std::is_pointer_v<parser_type>) {
@@ -118,16 +115,6 @@ template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_view const str) const noexcept { inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_view const str) const noexcept {
return auto_parser<ondemand::parser *>{parser, str}; return auto_parser<ondemand::parser *>{parser, str};
} }
template <typename T>
inline auto to_adaptor<T>::operator()(std::string str) const noexcept {
return auto_parser<ondemand::parser *>{pad_with_reserve(str)};
}
template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, std::string str) const noexcept {
return auto_parser<ondemand::parser *>{parser, pad_with_reserve(str)};
}
} // namespace internal } // namespace internal
} // namespace convert } // namespace convert
} // namespace simdjson } // namespace simdjson
+9 -9
View File
@@ -38,10 +38,10 @@ public:
explicit auto_parser(parser_type parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>); explicit auto_parser(parser_type parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>);
auto_parser(auto_parser const &) = delete; auto_parser(auto_parser const &) = delete;
auto_parser &operator=(auto_parser const &) = delete; auto_parser &operator=(auto_parser const &) = delete;
auto_parser(auto_parser &&) noexcept = default;
auto_parser &operator=(auto_parser &&) noexcept = default;
~auto_parser() = default; ~auto_parser() = default;
// Prevent moving
auto_parser(auto_parser&&) = delete;
auto_parser &operator=(auto_parser &&) noexcept = delete;
simdjson_warn_unused std::remove_pointer_t<parser_type> &parser() noexcept; simdjson_warn_unused std::remove_pointer_t<parser_type> &parser() noexcept;
template <typename T> template <typename T>
@@ -75,11 +75,6 @@ struct to_adaptor {
T operator()(simdjson_result<ondemand::value> &val) const noexcept; T operator()(simdjson_result<ondemand::value> &val) const noexcept;
auto operator()(padded_string_view const str) const noexcept; auto operator()(padded_string_view const str) const noexcept;
auto operator()(ondemand::parser &parser, padded_string_view const str) const noexcept; auto operator()(ondemand::parser &parser, padded_string_view const str) const noexcept;
// The std::string is padded with reserve to ensure there is enough space for padding.
// Some sanitizers may not like this, so you can use simdjson::pad instead.
// simdjson::from(simdjson::pad(str))
auto operator()(std::string str) const noexcept;
auto operator()(ondemand::parser &parser, std::string str) const noexcept;
}; };
// deduction guide // deduction guide
auto_parser(padded_string_view const str) -> auto_parser<ondemand::parser*>; auto_parser(padded_string_view const str) -> auto_parser<ondemand::parser*>;
@@ -90,7 +85,12 @@ auto_parser(padded_string_view const str) -> auto_parser<ondemand::parser*>;
* The simdjson::from instance is EXPERIMENTAL AND SUBJECT TO CHANGES. * The simdjson::from instance is EXPERIMENTAL AND SUBJECT TO CHANGES.
* *
* The `from` instance is a utility adaptor for parsing JSON strings into objects. * The `from` instance is a utility adaptor for parsing JSON strings into objects.
* It provides a convenient way to convert JSON data into C++ objects using the `auto_parser`. *
* The string must be a simdjson::padded_string_view, which can be created from a std::string
* with simdjson::pad(), from a simdjson::padded_string, or string literal using the `_padded`
* user-defined literal.
*
* The `from` instance provides a convenient way to convert JSON data into C++ objects using the `auto_parser`.
* *
* Example usage: * Example usage:
* *
+1 -13
View File
@@ -103,21 +103,9 @@ inline simdjson_result<element> array::at_pointer(std::string_view json_pointer)
// We don't support this, because we're returning a real element, not a position. // We don't support this, because we're returning a real element, not a position.
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
// Read the array index
size_t array_index = 0; size_t array_index = 0;
size_t i; size_t i;
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) { SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, i));
uint8_t digit = uint8_t(json_pointer[i] - '0');
// Check for non-digit in array index. If it's there, we're trying to get a field in an object
if (digit > 9) { return INCORRECT_TYPE; }
array_index = array_index*10 + digit;
}
// 0 followed by other digits is invalid
if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0"
// Empty string is invalid; so is a "/" with no digits before it
if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index"
// Get the child // Get the child
auto child = array(tape).at(array_index); auto child = array(tape).at(array_index);
+19 -1
View File
@@ -33,16 +33,25 @@ inline error_code document::allocate(size_t capacity) noexcept {
allocated_capacity = 0; allocated_capacity = 0;
return SUCCESS; return SUCCESS;
} }
if (capacity > SIMDJSON_MAXSIZE_BYTES) {
return CAPACITY;
}
// a pathological input like "[[[[..." would generate capacity tape elements, so // a pathological input like "[[[[..." would generate capacity tape elements, so
// need a capacity of at least capacity + 1, but it is also possible to do // need a capacity of at least capacity + 1, but it is also possible to do
// worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6" // worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6"
//where capacity + 1 tape elements are //where capacity + 1 tape elements are
// generated, see issue https://github.com/simdjson/simdjson/issues/345 // generated, see issue https://github.com/simdjson/simdjson/issues/345
if(capacity + 3 < capacity) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t tape_capacity = SIMDJSON_ROUNDUP_N(capacity + 3, 64); size_t tape_capacity = SIMDJSON_ROUNDUP_N(capacity + 3, 64);
// a document with only zero-length strings... could have capacity/3 string // a document with only zero-length strings... could have capacity/3 string
// and we would need capacity/3 * 5 bytes on the string buffer // and we would need capacity/3 * 5 bytes on the string buffer
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * capacity / 3 + SIMDJSON_PADDING, 64); if(5 * (capacity / 3) + SIMDJSON_PADDING < SIMDJSON_PADDING) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * (capacity / 3) + SIMDJSON_PADDING, 64);
string_buf.reset( new (std::nothrow) uint8_t[string_capacity]); string_buf.reset( new (std::nothrow) uint8_t[string_capacity]);
tape.reset(new (std::nothrow) uint64_t[tape_capacity]); tape.reset(new (std::nothrow) uint64_t[tape_capacity]);
if(!(string_buf && tape)) { if(!(string_buf && tape)) {
@@ -141,6 +150,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;
} }
+39 -4
View File
@@ -89,12 +89,14 @@ simdjson_inline document_stream::document_stream(
dom::parser &_parser, dom::parser &_parser,
const uint8_t *_buf, const uint8_t *_buf,
size_t _len, size_t _len,
size_t _batch_size size_t _batch_size,
stream_format _format
) noexcept ) noexcept
: parser{&_parser}, : parser{&_parser},
buf{_buf}, buf{_buf},
len{_len}, len{_len},
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
format{_format},
error{SUCCESS} error{SUCCESS}
#ifdef SIMDJSON_THREADS_ENABLED #ifdef SIMDJSON_THREADS_ENABLED
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
@@ -112,6 +114,7 @@ simdjson_inline document_stream::document_stream() noexcept
buf{nullptr}, buf{nullptr},
len{0}, len{0},
batch_size{0}, batch_size{0},
format{stream_format::whitespace_delimited},
error{UNINITIALIZED} error{UNINITIALIZED}
#ifdef SIMDJSON_THREADS_ENABLED #ifdef SIMDJSON_THREADS_ENABLED
, use_thread(false) , use_thread(false)
@@ -224,7 +227,14 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
} else { } else {
size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index]; size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index];
size_t svlen = next_doc_index - current_index(); size_t svlen = next_doc_index - current_index();
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) { // Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464 json_sequence
// mode the scanner classifies RS as a scalar character, so an RS-prefixed
// scalar document (number/true/false/null/string) has no closing structural
// index and the slice runs all the way up to the next document's RS. RS
// cannot legally appear in a JSON value at the source level (control
// characters in strings must be escaped as \u001E), so stripping it is
// safe in every stream_format.
while(svlen > 1 && (std::isspace(static_cast<unsigned char>(start[svlen-1])) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E || (stream->format == stream_format::comma_delimited && start[svlen-1] == ','))) {
svlen--; svlen--;
} }
return std::string_view(start, svlen); return std::string_view(start, svlen);
@@ -274,10 +284,35 @@ inline size_t document_stream::next_batch_start() const noexcept {
inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept { inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
size_t remaining = len - _batch_start; size_t remaining = len - _batch_start;
stage1_mode mode;
if (remaining <= batch_size) { if (remaining <= batch_size) {
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final); // Final batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_final;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_final;
break;
default:
mode = stage1_mode::streaming_final;
break;
}
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
} else { } else {
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial); // Partial batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_partial;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_partial;
break;
default:
mode = stage1_mode::streaming_partial;
break;
}
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
} }
} }
+5 -1
View File
@@ -206,12 +206,14 @@ private:
* @param buf is the raw byte buffer we need to process * @param buf is the raw byte buffer we need to process
* @param len is the length of the raw byte buffer in bytes * @param len is the length of the raw byte buffer in bytes
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document) * @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
* @param format is the stream format
*/ */
simdjson_inline document_stream( simdjson_inline document_stream(
dom::parser &parser, dom::parser &parser,
const uint8_t *buf, const uint8_t *buf,
size_t len, size_t len,
size_t batch_size size_t batch_size,
stream_format format = stream_format::whitespace_delimited
) noexcept; ) noexcept;
/** /**
@@ -261,6 +263,8 @@ private:
const uint8_t *buf; const uint8_t *buf;
size_t len; size_t len;
size_t batch_size; size_t batch_size;
/** The stream format. */
stream_format format;
/** The error (or lack thereof) from the current document. */ /** The error (or lack thereof) from the current document. */
error_code error; error_code error;
size_t batch_start{0}; size_t batch_start{0};
+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
} }
+19 -1
View File
@@ -21,7 +21,10 @@ 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
/// The BIGINT type is for integers that do not fit in 64 bits. It is only present
// if you set parser.number_as_string(true).
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
}; };
/** /**
@@ -120,6 +123,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 +186,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 +549,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 +560,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;
+19 -1
View File
@@ -227,8 +227,18 @@ inline size_t structure_analyzer::estimate_string_length(std::string_view s) con
} }
inline size_t structure_analyzer::estimate_number_length(double d) const { inline size_t structure_analyzer::estimate_number_length(double d) const {
if (std::isnan(d) || std::isinf(d)) { if (!std::isfinite(d)) {
#if SIMDJSON_ENABLE_NAN_INF
if (std::isnan(d)) {
return 3; // "NaN"
} else if (d < 0) {
return 9; // "-Infinity"
} else {
return 8; // "Infinity"
}
#else
return 4; // "null" for invalid numbers return 4; // "null" for invalid numbers
#endif
} }
// Rough estimate: up to 17 significant digits + sign + decimal point + exponent // Rough estimate: up to 17 significant digits + sign + decimal point + exponent
char buf[32]; char buf[32];
@@ -950,6 +960,14 @@ inline size_t fractured_string_builder::measure_value_length(const dom::element&
case dom::element_type::DOUBLE: { case dom::element_type::DOUBLE: {
double val; double val;
if (elem.get_double().get(val) == SUCCESS) { if (elem.get_double().get(val) == SUCCESS) {
#if SIMDJSON_ENABLE_NAN_INF
if (!std::isfinite(val)) {
if (std::isnan(val))
return 3; // "NaN"
// "-Infinity" (9) or "Infinity" (8)
return val < 0 ? 9 : 8;
}
#endif
char buf[32]; char buf[32];
int len = snprintf(buf, sizeof(buf), "%.17g", val); int len = snprintf(buf, sizeof(buf), "%.17g", val);
return len > 0 ? static_cast<size_t>(len) : 1; return len > 0 ? static_cast<size_t>(len) : 1;
+46 -7
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; }
@@ -168,12 +170,7 @@ simdjson_inline simdjson_result<element> parser::parse(const padded_string_view
} }
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept { inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } return parse_many(buf, len, batch_size, stream_format::whitespace_delimited);
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
return document_stream(*this, buf, len, batch_size);
} }
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept { inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept {
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size); return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
@@ -184,6 +181,48 @@ inline simdjson_result<document_stream> parser::parse_many(const std::string &s,
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size) noexcept { inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size) noexcept {
return parse_many(s.data(), s.length(), batch_size); return parse_many(s.data(), s.length(), batch_size);
} }
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size) noexcept {
return parse_many(v.data(), v.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
if (format == stream_format::comma_delimited_array) {
// Strip leading JSON whitespace.
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
buf++; len--;
}
// Expect the opening '['.
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
buf++; len--;
// Strip trailing JSON whitespace.
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
len--;
}
// Expect the closing ']'.
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
len--;
// Fall through to comma_delimited over the array contents.
format = stream_format::comma_delimited;
}
return document_stream(*this, buf, len, batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept {
return parse_many(v.data(), v.length(), batch_size, format);
}
simdjson_inline size_t parser::capacity() const noexcept { simdjson_inline size_t parser::capacity() const noexcept {
return implementation ? implementation->capacity() : 0; return implementation ? implementation->capacity() : 0;
+39
View File
@@ -490,10 +490,39 @@ public:
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
*
* Because padded_string_view guarantees SIMDJSON_PADDING trailing bytes, this
* overload is safe to use with buffers that the caller owns elsewhere (for
* example, a padded_memory_map), with no extra copy. Without this overload,
* passing a padded_string_view would silently bind to the padded_string
* overload via an implicit conversion, allocating and copying the input, and
* because that temporary is destroyed at the end of the full-expression
* leaving the returned document_stream pointing at freed memory. */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
/** @private We do not want to allow implicit conversion from C string to std::string. */ /** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete; simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
/**
* Parse a stream of JSON documents with explicit format specification.
*
* @param buf The concatenated JSON documents.
* @param len The length of the buffer.
* @param batch_size The batch size to use.
* @param format The stream format.
* @return A stream of documents, or an error.
*/
inline simdjson_result<document_stream> parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept;
/** /**
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
* and `max_depth` depth. * and `max_depth` depth.
@@ -610,6 +639,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 +654,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)
*/ */
+23
View File
@@ -11,6 +11,7 @@
#include "simdjson/dom/object-inl.h" #include "simdjson/dom/object-inl.h"
#include "simdjson/internal/tape_ref-inl.h" #include "simdjson/internal/tape_ref-inl.h"
#include <cmath>
#include <cstring> #include <cstring>
namespace simdjson { namespace simdjson {
@@ -181,6 +182,22 @@ simdjson_inline void base_formatter<formatter>::number(int64_t x) {
template <class formatter> template <class formatter>
simdjson_inline void base_formatter<formatter>::number(double x) { simdjson_inline void base_formatter<formatter>::number(double x) {
#if SIMDJSON_ENABLE_NAN_INF
if (simdjson_unlikely(!std::isfinite(x))) {
if (std::isnan(x)) {
char const *s = "NaN";
chars(s, s + 3);
} else {
if (x < 0) {
one_char('-');
}
char const *s = "Infinity";
chars(s, s + 8);
}
return;
}
#endif
char number_buffer[24]; char number_buffer[24];
// Currently, passing the nullptr to the second argument is // Currently, passing the nullptr to the second argument is
// safe because our implementation does not check the second // safe because our implementation does not check the second
@@ -460,6 +477,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
@@ -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
+90
View File
@@ -22,6 +22,9 @@ namespace atomparsing {
// to the compile-time constant 1936482662. // to the compile-time constant 1936482662.
simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; }
// Acts on the same principle as string_to_uint32, but on an 8-byte block of memory
simdjson_inline uint64_t string_to_uint64(const char* str) { uint64_t val; std::memcpy(&val, str, sizeof(uint64_t)); return val; }
// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. // Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive.
// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. // Yet all decent optimizing compilers will compile memcpy to a single instruction, just about.
@@ -33,6 +36,28 @@ simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) {
return srcval ^ string_to_uint32(atom); return srcval ^ string_to_uint32(atom);
} }
// Checks that the first 8 characters of the input string match the given atom in a case-insensitive manner.
//
// 'atom' must consist of only lowercase letters.
simdjson_warn_unused
simdjson_inline uint64_t str8ncmp_case_insensitive(const uint8_t *src, const char* atom) {
uint64_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++)
static_assert(sizeof(uint64_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 8 bytes");
std::memcpy(&srcval, src, sizeof(uint64_t));
return (srcval | 0x2020202020202020ull) ^ string_to_uint64(atom);
}
// Checks that the first 3 characters of 'src' match 'atom' in a case-insensitive way.
//
// 'atom' must consist of only lowercase letters.
simdjson_warn_unused
simdjson_inline uint32_t str3ncmp_case_insensitive(const uint8_t *src, const char* atom) {
return ((src[0] | 0x20) ^ atom[0]) //
| ((src[1] | 0x20) ^ atom[1]) //
| ((src[2] | 0x20) ^ atom[2]);
}
simdjson_warn_unused simdjson_warn_unused
simdjson_inline bool is_valid_true_atom(const uint8_t *src) { simdjson_inline bool is_valid_true_atom(const uint8_t *src) {
return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0;
@@ -69,6 +94,71 @@ simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) {
else { return false; } else { return false; }
} }
#if SIMDJSON_ENABLE_NAN_INF
// "nan" is 3 bytes; we check characters and then verify the next
// character is structural or whitespace. We accept both "nan" and "NaN".
simdjson_warn_unused
simdjson_inline bool is_valid_nan_atom(const uint8_t *src) {
return (str3ncmp_case_insensitive(src, "nan")
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
}
// checks that the next four characters of a string are 'nan"', where the 'nan'
// is checked in a case-insensitive way.
simdjson_warn_unused
simdjson_inline bool is_valid_nan_in_string(const uint8_t *src) {
return (str3ncmp_case_insensitive(src, "nan") | (src[3] ^ '"')) == 0;
}
simdjson_warn_unused
simdjson_inline bool is_valid_nan_atom(const uint8_t *src, size_t len) {
if (len > 3) { return is_valid_nan_atom(src); }
if (len == 3) { return str3ncmp_case_insensitive(src, "nan") == 0; }
return false;
}
// This function will accept any case-insensitive 3-character spelling of
// infinity: 'inf', 'INF', and 'Inf' are all accepted.
//
// Any capitalization of 'infinity' is also accepted.
simdjson_warn_unused
simdjson_inline bool is_valid_inf_atom(const uint8_t *src) {
bool is_short_inf = (str3ncmp_case_insensitive(src, "inf")
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
if(is_short_inf) return true;
// Check for 'infinity' (any capitalization)
return (str8ncmp_case_insensitive(src, "infinity") | jsoncharutils::is_not_structural_or_whitespace(src[8])) == 0;
}
simdjson_warn_unused
simdjson_inline bool is_valid_inf_in_string(const uint8_t *src) {
bool is_short_inf = (str3ncmp_case_insensitive(src, "inf") | (src[3] ^ '"')) == 0;
if(is_short_inf) return true;
return (str8ncmp_case_insensitive(src, "infinity") | (src[8] ^ '"')) == 0;
}
// This function will accept any case-insensitive 3-character spelling of
// infinity: 'inf', 'INF', and 'Inf' are all accepted.
//
// Any capitalization of 'infinity' is also accepted.
simdjson_warn_unused
simdjson_inline bool is_valid_inf_atom(const uint8_t *src, size_t len) {
if (len > 8) { return is_valid_inf_atom(src); }
if (len == 8 && str8ncmp_case_insensitive(src, "infinity") == 0) {
return true;
}
if (len > 3) {
return (str3ncmp_case_insensitive(src, "inf")
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
}
if (len == 3) { return str3ncmp_case_insensitive(src, "inf") == 0; }
return false;
}
#endif // SIMDJSON_ENABLE_NAN_INF
} // namespace atomparsing } // namespace atomparsing
} // unnamed namespace } // unnamed namespace
} // namespace SIMDJSON_IMPLEMENTATION } // namespace SIMDJSON_IMPLEMENTATION
+288 -113
View File
@@ -21,23 +21,113 @@ namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION { namespace SIMDJSON_IMPLEMENTATION {
namespace builder { namespace builder {
// Forward-declare helpers defined in json_string_builder-inl.h so the
// writer-based atom code below can call them (the -inl.h is not yet
// included at the point this header is parsed; without these forwards,
// name lookup falls back to the wrong outer namespace).
namespace internal {
simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept;
} // namespace internal
inline size_t write_string_escaped(const std::string_view input, char *out);
// =============================================================
// `writer`: position-as-local hot-path writer used by the reflection
// atom code below. Holds the buffer pointer, write position and
// capacity in three fields that, once `writer` itself is a stack-local
// in the caller and all atom() functions are inlined, become true
// register-resident locals after SROA. Glaze achieves the same effect
// by passing `B&& b, auto&& ix` through every helper. Holding `pos`
// in a register (rather than as a member of string_builder) is what
// breaks the strict-aliasing penalty on every char* write through the
// buffer, which forces a reload of `b.position` and `b.capacity`
// after every byte.
// =============================================================
struct writer {
char *ptr; // buffer pointer (refreshed after a grow)
size_t pos; // write position (local)
size_t cap; // capacity (refreshed after a grow)
string_builder &sb; // back-ref for grow / sync
// Snapshot string_builder state into a writer for the duration of
// a write chain.
simdjson_really_inline writer(string_builder &builder) noexcept
: ptr(builder.unsafe_data())
, pos(builder.unsafe_position())
, cap(builder.unsafe_capacity())
, sb(builder) {}
// Write the local position back to the underlying string_builder.
// Caller is responsible for invoking before the writer is dropped
// (otherwise data is lost). Idempotent.
simdjson_really_inline void sync() noexcept {
sb.unsafe_set_position(pos);
}
// Ensure at least `n` more bytes of free capacity. Grows the
// underlying buffer if needed (rare path). Returns false on
// allocation failure.
simdjson_really_inline bool ensure(size_t n) noexcept {
// Use subtraction (relying on the pos <= cap invariant) so a huge n
// cannot wrap pos + n to a small value that spuriously passes the test.
// This is pedantic except maybe on 32-bit targets.
if (simdjson_likely(n <= cap - pos)) return true;
return grow_slow(n);
}
// Slow path of ensure(). Out-of-line via simdjson_inline (not
// simdjson_really_inline) to keep the hot path short.
simdjson_inline bool grow_slow(size_t n) noexcept {
// Detect overflow.
// This is pedantic except maybe on 32-bit targets.
if (simdjson_unlikely(pos + n < pos)) return false;
sb.unsafe_set_position(pos);
// even if 2*capacity overflows, the (std::max) below will pick the needed value,
// so we do not need a separate overflow check here.
if (!sb.unsafe_grow((std::max)(cap * 2, pos + n))) {
return false;
}
ptr = sb.unsafe_data();
cap = sb.unsafe_capacity();
return true;
}
};
// === Helper: invoke a string_builder member that writes variable-length
// content (escape_and_append_with_quotes etc), syncing the writer's local
// state before the call and reloading after. Used for string fields where
// rewriting the entire SIMD escape path through the writer would be a much
// bigger refactor.
template <class F>
simdjson_really_inline void call_through_string_builder(writer &w, F &&f) noexcept {
w.sync();
f(w.sb);
w.ptr = w.sb.unsafe_data();
w.pos = w.sb.unsafe_position();
w.cap = w.sb.unsafe_capacity();
}
template <class T> template <class T>
requires(concepts::container_but_not_string<T> && !require_custom_serialization<T>) requires(concepts::container_but_not_string<T> && ! concepts::optional_type<T> && !require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &t) { simdjson_really_inline constexpr void atom(writer &w, const T &t) {
auto it = t.begin(); auto it = t.begin();
auto end = t.end(); auto end = t.end();
if (it == end) { if (it == end) {
b.append_raw("[]"); if (!w.ensure(2)) return;
std::memcpy(w.ptr + w.pos, "[]", 2);
w.pos += 2;
return; return;
} }
b.append('['); if (!w.ensure(1)) return;
atom(b, *it); w.ptr[w.pos++] = '[';
atom(w, *it);
++it; ++it;
for (; it != end; ++it) { for (; it != end; ++it) {
b.append(','); if (!w.ensure(1)) return;
atom(b, *it); w.ptr[w.pos++] = ',';
atom(w, *it);
} }
b.append(']'); if (!w.ensure(1)) return;
w.ptr[w.pos++] = ']';
} }
template <class T> template <class T>
@@ -45,37 +135,100 @@ template <class T>
std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> || std::is_same_v<T, const char *> ||
std::is_same_v<T, char>) std::is_same_v<T, char>)
constexpr void atom(string_builder &b, const T &t) { simdjson_really_inline constexpr void atom(writer &w, const T &t) {
b.escape_and_append_with_quotes(t); // Inline the escape path through the writer so we never round-trip
// pos through memory for string fields (Twitter is dominated by
// these — sync/reload around each string was a real cost).
std::string_view input;
if constexpr (std::is_same_v<T, char>) {
input = std::string_view(&t, 1);
} else {
input = std::string_view(t);
}
// Worst-case escape: every byte expands to \uXXXX (6 chars), plus 2 quotes.
// Guard against 2 + 6 * input.size() wrapping for huge inputs — if it
// wrapped to a small value, ensure() would spuriously succeed and the
// subsequent escape would overflow the buffer.
// Note that this is pedantic except maybe on 32-bit targets.
if (simdjson_unlikely(input.size() > ((std::numeric_limits<size_t>::max)() - 2) / 6)) { return; }
if (!w.ensure(2 + 6 * input.size())) { return; }
w.ptr[w.pos++] = '"';
w.pos += write_string_escaped(input, w.ptr + w.pos);
w.ptr[w.pos++] = '"';
} }
template <concepts::string_view_keyed_map T> template <concepts::string_view_keyed_map T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &m) { simdjson_really_inline constexpr void atom(writer &w, const T &m) {
if (m.empty()) { if (m.empty()) {
b.append_raw("{}"); if (!w.ensure(2)) return;
std::memcpy(w.ptr + w.pos, "{}", 2);
w.pos += 2;
return; return;
} }
b.append('{'); if (!w.ensure(1)) return;
w.ptr[w.pos++] = '{';
bool first = true; bool first = true;
for (const auto& [key, value] : m) { for (const auto& [key, value] : m) {
if (!first) { if (!first) {
b.append(','); if (!w.ensure(1)) return;
w.ptr[w.pos++] = ',';
} }
first = false; first = false;
// Keys must be convertible to string_view per the concept // Keys must be convertible to string_view per the concept.
b.escape_and_append_with_quotes(key); std::string_view key_sv(key);
b.append(':'); // Guard against 3 + 6 * key_sv.size() wrapping for huge keys, if it
atom(b, value); // wrapped to a small value, ensure() would spuriously succeed and the
// subsequent escape would overflow the buffer.
// Note that this is pedantic except maybe on 32-bit targets.
if (simdjson_unlikely(key_sv.size() > ((std::numeric_limits<size_t>::max)() - 3) / 6)) { return; }
if (!w.ensure(2 + 6 * key_sv.size() + 1)) { return; }
w.ptr[w.pos++] = '"';
w.pos += write_string_escaped(key_sv, w.ptr + w.pos);
w.ptr[w.pos++] = '"';
w.ptr[w.pos++] = ':';
atom(w, value);
} }
b.append('}'); if (!w.ensure(1)) return;
w.ptr[w.pos++] = '}';
} }
template<typename number_type, template<typename number_type,
typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type> typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type>
constexpr void atom(string_builder &b, const number_type t) { simdjson_really_inline constexpr void atom(writer &w, const number_type t) {
b.append(t); // Booleans / floats: defer to string_builder (rare path; keeps writer hot
// path free of float-formatter machinery). For integers, write directly
// via jeaiii using local pos.
if constexpr (std::is_same_v<number_type, bool>) {
if (t) {
if (!w.ensure(4)) return;
std::memcpy(w.ptr + w.pos, "true", 4);
w.pos += 4;
} else {
if (!w.ensure(5)) return;
std::memcpy(w.ptr + w.pos, "false", 5);
w.pos += 5;
}
} else if constexpr (std::is_floating_point_v<number_type>) {
call_through_string_builder(w, [&](string_builder &b) { b.append(t); });
} else if constexpr (std::is_unsigned_v<number_type>) {
if (!w.ensure(20)) return;
char *end = internal::write_uint_jeaiii(
w.ptr + w.pos, static_cast<uint64_t>(t));
w.pos = static_cast<size_t>(end - w.ptr);
} else {
// signed integral
if (!w.ensure(20)) return;
using U = typename std::make_unsigned<number_type>::type;
bool negative = t < 0;
U pv = negative ? U(0) - static_cast<U>(t) : static_cast<U>(t);
w.ptr[w.pos] = '-';
w.pos += negative;
char *end = internal::write_uint_jeaiii(
w.ptr + w.pos, static_cast<uint64_t>(pv));
w.pos = static_cast<size_t>(end - w.ptr);
}
} }
template <class T> template <class T>
@@ -88,61 +241,83 @@ template <class T>
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, std::string_view> &&
!std::is_same_v<T, const char*> && !std::is_same_v<T, const char*> &&
!std::is_same_v<T, char> && !require_custom_serialization<T>) !std::is_same_v<T, char> && !require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &t) { simdjson_really_inline constexpr void atom(writer &w, const T &t) {
// Per-field block: ensure key+value worst case, then write key + value
// through the writer's local pos. For arithmetic fields, the integer
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
// never round-trips through memory.
int i = 0; int i = 0;
b.append('{'); if (!w.ensure(1)) return;
w.ptr[w.pos++] = '{';
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if (i != 0) constexpr auto first_key = std::define_static_string(
b.append(','); constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm))); constexpr auto rest_key = std::define_static_string(
b.append_raw(key); std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
b.append(':'); constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
atom(b, t.[:dm:]); constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
if (!w.ensure(rest_key_len)) return;
if (i == 0) {
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
w.pos += first_key_len;
} else {
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
w.pos += rest_key_len;
}
atom(w, t.[:dm:]);
i++; i++;
}; };
b.append('}'); if (!w.ensure(1)) return;
w.ptr[w.pos++] = '}';
} }
// Support for optional types (std::optional, etc.) // Support for optional types (std::optional, etc.)
template <concepts::optional_type T> template <concepts::optional_type T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &opt) { simdjson_really_inline constexpr void atom(writer &w, const T &opt) {
if (opt) { if (opt) {
atom(b, opt.value()); atom(w, opt.value());
} else { } else {
b.append_raw("null"); if (!w.ensure(4)) return;
std::memcpy(w.ptr + w.pos, "null", 4);
w.pos += 4;
} }
} }
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.) // Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
template <concepts::smart_pointer T> template <concepts::smart_pointer T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &ptr) { simdjson_really_inline constexpr void atom(writer &w, const T &ptr) {
if (ptr) { if (ptr) {
atom(b, *ptr); atom(w, *ptr);
} else { } else {
b.append_raw("null"); if (!w.ensure(4)) return;
std::memcpy(w.ptr + w.pos, "null", 4);
w.pos += 4;
} }
} }
// Support for enums - serialize as string representation using expand approach from P2996R12 // Support for enums - serialize as string representation using expand approach from P2996R12
template <typename T> template <typename T>
requires(std::is_enum_v<T> && !require_custom_serialization<T>) requires(std::is_enum_v<T> && !require_custom_serialization<T>)
void atom(string_builder &b, const T &e) { simdjson_really_inline void atom(writer &w, const T &e) {
#if SIMDJSON_STATIC_REFLECTION #if SIMDJSON_STATIC_REFLECTION
constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T)); static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
template for (constexpr auto enum_val : enumerators) { template for (constexpr auto enum_val : enumerators) {
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val))); constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
constexpr size_t enum_str_len = std::char_traits<char>::length(enum_str);
if (e == [:enum_val:]) { if (e == [:enum_val:]) {
b.append_raw(enum_str); if (!w.ensure(enum_str_len)) return;
std::memcpy(w.ptr + w.pos, enum_str, enum_str_len);
w.pos += enum_str_len;
return; return;
} }
}; };
// Fallback to integer if enum value not found // Fallback to integer if enum value not found
atom(b, static_cast<std::underlying_type_t<T>>(e)); atom(w, static_cast<std::underlying_type_t<T>>(e));
#else #else
// Fallback: serialize as integer if reflection not available // Fallback: serialize as integer if reflection not available
atom(b, static_cast<std::underlying_type_t<T>>(e)); atom(w, static_cast<std::underlying_type_t<T>>(e));
#endif #endif
} }
@@ -152,28 +327,37 @@ template <concepts::appendable_containers T>
!concepts::optional_type<T> && !concepts::smart_pointer<T> && !concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!std::is_same_v<T, std::string> && !std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>) !std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &container) { simdjson_really_inline constexpr void atom(writer &w, const T &container) {
if (container.empty()) { if (container.empty()) {
b.append_raw("[]"); if (!w.ensure(2)) return;
std::memcpy(w.ptr + w.pos, "[]", 2);
w.pos += 2;
return; return;
} }
b.append('['); if (!w.ensure(1)) return;
w.ptr[w.pos++] = '[';
bool first = true; bool first = true;
for (const auto& item : container) { for (const auto& item : container) {
if (!first) { if (!first) {
b.append(','); if (!w.ensure(1)) return;
w.ptr[w.pos++] = ',';
} }
first = false; first = false;
atom(b, item); atom(w, item);
} }
b.append(']'); if (!w.ensure(1)) return;
w.ptr[w.pos++] = ']';
} }
// append functions that delegate to atom functions for primitive types // append() — top-level entry. Each overload constructs a stack-local
// writer, runs atom(w, t) through the inlined call chain, then syncs
// the local position back into the string_builder.
template <class T> template <class T>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>) requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
void append(string_builder &b, const T &t) { simdjson_inline void append(string_builder &b, const T &t) {
atom(b, t); writer w(b);
atom(w, t);
w.sync();
} }
template <class T> template <class T>
@@ -181,20 +365,26 @@ template <class T>
std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> || std::is_same_v<T, const char *> ||
std::is_same_v<T, char>) std::is_same_v<T, char>)
void append(string_builder &b, const T &t) { simdjson_inline void append(string_builder &b, const T &t) {
atom(b, t); writer w(b);
atom(w, t);
w.sync();
} }
template <concepts::optional_type T> template <concepts::optional_type T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
void append(string_builder &b, const T &t) { simdjson_inline void append(string_builder &b, const T &t) {
atom(b, t); writer w(b);
atom(w, t);
w.sync();
} }
template <concepts::smart_pointer T> template <concepts::smart_pointer T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
void append(string_builder &b, const T &t) { simdjson_inline void append(string_builder &b, const T &t) {
atom(b, t); writer w(b);
atom(w, t);
w.sync();
} }
template <concepts::appendable_containers T> template <concepts::appendable_containers T>
@@ -202,14 +392,18 @@ template <concepts::appendable_containers T>
!concepts::optional_type<T> && !concepts::smart_pointer<T> && !concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!std::is_same_v<T, std::string> && !std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>) !std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
void append(string_builder &b, const T &t) { simdjson_inline void append(string_builder &b, const T &t) {
atom(b, t); writer w(b);
atom(w, t);
w.sync();
} }
template <concepts::string_view_keyed_map T> template <concepts::string_view_keyed_map T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
void append(string_builder &b, const T &t) { simdjson_inline void append(string_builder &b, const T &t) {
atom(b, t); writer w(b);
atom(w, t);
w.sync();
} }
// works for struct // works for struct
@@ -223,39 +417,19 @@ template <class Z>
!std::is_same_v<Z, std::string_view> && !std::is_same_v<Z, std::string_view> &&
!std::is_same_v<Z, const char*> && !std::is_same_v<Z, const char*> &&
!std::is_same_v<Z, char> && !require_custom_serialization<Z>) !std::is_same_v<Z, char> && !require_custom_serialization<Z>)
void append(string_builder &b, const Z &z) { simdjson_inline void append(string_builder &b, const Z &z) {
int i = 0; writer w(b);
b.append('{'); atom(w, z);
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked()))) { w.sync();
if (i != 0)
b.append(',');
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
b.append_raw(key);
b.append(':');
atom(b, z.[:dm:]);
i++;
};
b.append('}');
} }
// works for container that have begin() and end() iterators // works for container that have begin() and end() iterators
template <class Z> template <class Z>
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>) requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
void append(string_builder &b, const Z &z) { simdjson_inline void append(string_builder &b, const Z &z) {
auto it = z.begin(); writer w(b);
auto end = z.end(); atom(w, z);
if (it == end) { w.sync();
b.append_raw("[]");
return;
}
b.append('[');
atom(b, *it);
++it;
for (; it != end; ++it) {
b.append(',');
atom(b, *it);
}
b.append(']');
} }
template <class Z> template <class Z>
@@ -275,7 +449,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;
@@ -294,40 +468,41 @@ string_builder& operator<<(string_builder& b, const Z& z) {
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0)) requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
void extract_from(string_builder &b, const T &obj) { void extract_from(string_builder &b, const T &obj) {
// Helper to check if a field name matches any of the requested fields writer w(b);
auto should_extract = [](std::string_view field_name) constexpr -> bool { if (!w.ensure(1)) { w.sync(); return; }
return ((FieldNames.view() == field_name) || ...); w.ptr[w.pos++] = '{';
};
b.append('{');
bool first = true; bool first = true;
// Iterate through all members of T using reflection // Iterate through all members of T using reflection
template for (constexpr auto mem : std::define_static_array( static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()));
std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { template for (constexpr auto mem : members) {
if constexpr (std::meta::is_public(mem)) { if constexpr (std::meta::is_public(mem)) {
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem)); static constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
// Only serialize this field if it's in our list of requested fields // Only serialize this field if it's in our list of requested fields
if constexpr (should_extract(key)) { if constexpr (((FieldNames.view() == key) || ...)) {
if (!first) { static constexpr auto first_key = std::define_static_string(
b.append(','); constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
static constexpr auto rest_key = std::define_static_string(
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
if (!w.ensure(rest_key_len)) { w.sync(); return; }
if (first) {
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
w.pos += first_key_len;
} else {
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
w.pos += rest_key_len;
} }
first = false; first = false;
atom(w, obj.[:mem:]);
// Serialize the key
constexpr auto quoted_key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)));
b.append_raw(quoted_key);
b.append(':');
// Serialize the value
atom(b, obj.[:mem:]);
} }
} }
}; };
b.append('}'); if (!w.ensure(1)) { w.sync(); return; }
w.ptr[w.pos++] = '}';
w.sync();
} }
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>
@@ -352,7 +527,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,5 +1,7 @@
#include <array> #include <array>
#include <cmath>
#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
@@ -27,12 +29,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 {
@@ -133,16 +173,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
find_next_json_quotable_character_scalar(const std::string_view view,
size_t location) noexcept { 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();
@@ -151,6 +220,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",
@@ -163,7 +439,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;
@@ -177,14 +453,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);
@@ -216,7 +498,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.
@@ -225,7 +507,7 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
return is_valid; return is_valid;
} }
simdjson_inline void string_builder::grow_buffer(size_t desired_capacity) { inline void string_builder::grow_buffer(size_t desired_capacity) {
if (!is_valid) { if (!is_valid) {
return; return;
} }
@@ -281,62 +563,6 @@ simdjson_inline void string_builder::clear() noexcept {
namespace internal { namespace internal {
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline int int_log2(number_type x) {
return 63 - leading_zeroes(uint64_t(x) | 1);
}
simdjson_really_inline int fast_digit_count_32(uint32_t x) {
static uint64_t table[] = {
4294967296, 8589934582, 8589934582, 8589934582, 12884901788,
12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
21474826480, 21474826480, 21474826480, 21474826480, 25769703776,
25769703776, 25769703776, 30063771072, 30063771072, 30063771072,
34349738368, 34349738368, 34349738368, 34349738368, 38554705664,
38554705664, 38554705664, 41949672960, 41949672960, 41949672960,
42949672960, 42949672960};
return uint32_t((x + table[int_log2(x)]) >> 32);
}
simdjson_really_inline int fast_digit_count_64(uint64_t x) {
static uint64_t table[] = {9,
99,
999,
9999,
99999,
999999,
9999999,
99999999,
999999999,
9999999999,
99999999999,
999999999999,
9999999999999,
99999999999999,
999999999999999ULL,
9999999999999999ULL,
99999999999999999ULL,
999999999999999999ULL,
9999999999999999999ULL};
int y = (19 * int_log2(x) >> 6);
y += x > table[y];
return y + 1;
}
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline size_t digit_count(number_type v) noexcept {
static_assert(sizeof(number_type) == 8 || sizeof(number_type) == 4 ||
sizeof(number_type) == 2 || sizeof(number_type) == 1,
"We only support 8-bit, 16-bit, 32-bit and 64-bit numbers");
SIMDJSON_IF_CONSTEXPR(sizeof(number_type) <= 4) {
return fast_digit_count_32(static_cast<uint32_t>(v));
}
else {
return fast_digit_count_64(static_cast<uint64_t>(v));
}
}
static const char decimal_table[200] = { static const char decimal_table[200] = {
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
@@ -356,6 +582,78 @@ static const char decimal_table[200] = {
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, 0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, 0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39,
}; };
// Forward unsigned-int writer (cascade-on-magnitude, no upfront digit_count).
// Built from a non-recursive DAG of always_inline helpers — gcc and MSVC
// refuse to inline recursive `always_inline`/`__forceinline` functions.
// Caller must guarantee at least 20 bytes available at p. All helpers
// return pointer past the last digit written.
// Caller guarantees v < 100. Writes 1-2 digits.
simdjson_really_inline char* write_lt100(char* p, uint64_t v) noexcept {
if (v < 10) { *p++ = char('0' + v); return p; }
std::memcpy(p, &decimal_table[v * 2], 2);
return p + 2;
}
// Caller guarantees v < 10000. Writes 1-4 digits.
simdjson_really_inline char* write_lt10000(char* p, uint64_t v) noexcept {
if (v < 100) return write_lt100(p, v);
uint64_t hi = v / 100, lo = v % 100;
if (v < 1000) {
*p++ = char('0' + hi);
} else {
std::memcpy(p, &decimal_table[hi * 2], 2);
p += 2;
}
std::memcpy(p, &decimal_table[lo * 2], 2);
return p + 2;
}
// Caller guarantees v < 10000. Always writes exactly 4 digits.
simdjson_really_inline void write_4_digits(char* p, uint64_t v) noexcept {
uint64_t hi = v / 100, lo = v % 100;
std::memcpy(p, &decimal_table[hi * 2], 2);
std::memcpy(p + 2, &decimal_table[lo * 2], 2);
}
// Caller guarantees v < 10^8. Writes 1-8 digits.
simdjson_really_inline char* write_lt1e8(char* p, uint64_t v) noexcept {
if (v < 10000) return write_lt10000(p, v);
uint64_t hi = v / 10000, lo = v % 10000;
p = write_lt10000(p, hi);
write_4_digits(p, lo);
return p + 4;
}
simdjson_really_inline char* write_uint_jeaiii(char* p, uint64_t v) noexcept {
if (v < 10000ULL) return write_lt10000(p, v);
if (v < 100000000ULL) { // 5-8 digits
uint64_t hi = v / 10000, lo = v % 10000;
p = write_lt10000(p, hi);
write_4_digits(p, lo);
return p + 4;
}
if (v < 10000000000000000ULL) { // 9-16 digits
uint64_t hi = v / 100000000ULL, lo = v % 100000000ULL;
p = write_lt1e8(p, hi);
uint64_t lo_hi = lo / 10000, lo_lo = lo % 10000;
write_4_digits(p, lo_hi);
write_4_digits(p + 4, lo_lo);
return p + 8;
}
// 17-20 digits
uint64_t hi = v / 10000000000000000ULL, lo = v % 10000000000000000ULL;
p = write_lt10000(p, hi);
uint64_t lo_a = lo / 100000000ULL, lo_b = lo % 100000000ULL;
uint64_t lo_a_hi = lo_a / 10000, lo_a_lo = lo_a % 10000;
uint64_t lo_b_hi = lo_b / 10000, lo_b_lo = lo_b % 10000;
write_4_digits(p, lo_a_hi);
write_4_digits(p + 4, lo_a_lo);
write_4_digits(p + 8, lo_b_hi);
write_4_digits(p + 12, lo_b_lo);
return p + 16;
}
} // namespace internal } // namespace internal
template <typename number_type, typename> template <typename number_type, typename>
@@ -386,52 +684,56 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
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); char* end = internal::write_uint_jeaiii(
size_t dc = internal::digit_count(pv); buffer.get() + position,
char *write_pointer = buffer.get() + position + dc - 1; static_cast<uint64_t>(static_cast<unsigned_type>(v)));
while (pv >= 100) { position = end - buffer.get();
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
write_pointer -= 2;
pv /= 100;
}
if (pv >= 10) {
*write_pointer-- = char('0' + (pv % 10));
pv /= 10;
}
*write_pointer = char('0' + pv);
position += dc;
} }
} }
else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) { else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) {
// 19 digits (max abs value of int64_t) + optional minus sign.
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;
bool negative = v < 0; bool negative = v < 0;
unsigned_type pv = static_cast<unsigned_type>(v); // 0 - pv (rather than -pv) avoids an MSVC unary-minus warning.
if (negative) { unsigned_type pv = negative
pv = 0 - pv; // the 0 is for Microsoft ? unsigned_type(0) - static_cast<unsigned_type>(v)
} : static_cast<unsigned_type>(v);
size_t dc = internal::digit_count(pv); // Branchless: always write '-', advance only if negative.
// by always writing the minus sign, we avoid the branch.
buffer.get()[position] = '-'; buffer.get()[position] = '-';
position += negative ? 1 : 0; position += negative;
char *write_pointer = buffer.get() + position + dc - 1; char* end = internal::write_uint_jeaiii(
while (pv >= 100) { buffer.get() + position, static_cast<uint64_t>(pv));
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2); position = end - buffer.get();
write_pointer -= 2;
pv /= 100;
}
if (pv >= 10) {
*write_pointer-- = char('0' + (pv % 10));
pv /= 10;
}
*write_pointer = char('0' + pv);
position += dc;
} }
} }
else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) { else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) {
constexpr size_t max_number_size = 24; constexpr size_t max_number_size = 24;
if (capacity_check(max_number_size)) { if (capacity_check(max_number_size)) {
#if SIMDJSON_ENABLE_NAN_INF
// Check if the input might be NaN or infinity
if (simdjson_unlikely(!std::isfinite(v))) {
if (std::isnan(v)) {
constexpr char nan_literal[] = "NaN";
constexpr size_t nan_len = sizeof(nan_literal) - 1;
std::memcpy(buffer.get() + position, nan_literal, nan_len);
position += nan_len;
} else {
constexpr char inf_literal[] = "Infinity";
constexpr size_t inf_len = sizeof(inf_literal) - 1;
if (v < 0) {
buffer.get()[position] = '-';
++position;
}
std::memcpy(buffer.get() + position, inf_literal, inf_len);
position += inf_len;
}
return;
}
#endif
// We could specialize for float. // We could specialize for float.
char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr,
double(v)); double(v));
@@ -443,6 +745,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);
} }
@@ -451,6 +758,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);
@@ -482,7 +794,9 @@ simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
#endif #endif
simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(const char *c) noexcept {
size_t len = std::strlen(c); // char_traits::length is constexpr; lets the compiler fold the length
// when called with a pointer to a compile-time-constant string.
size_t len = std::char_traits<char>::length(c);
append_raw(c, len); append_raw(c, len);
} }
@@ -501,6 +815,14 @@ simdjson_inline void string_builder::append_raw(const char *str,
position += len; position += len;
} }
} }
template <size_t N>
simdjson_inline void string_builder::append_raw_n(const char *str) noexcept {
if (capacity_check(N)) {
std::memcpy(buffer.get() + position, str, N);
position += N;
}
}
#if SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_CONCEPTS
// Support for optional types (std::optional, etc.) // Support for optional types (std::optional, etc.)
template <concepts::optional_type T> template <concepts::optional_type T>
@@ -530,7 +852,7 @@ simdjson_inline void string_builder::append(const T &value) {
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.) // Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R> template <std::ranges::range R>
requires(!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>) requires(!std::is_convertible<R, std::string_view>::value && !concepts::optional_type<R> && !require_custom_serialization<R>)
simdjson_inline void string_builder::append(const R &range) noexcept { simdjson_inline void string_builder::append(const R &range) noexcept {
auto it = std::ranges::begin(range); auto it = std::ranges::begin(range);
auto end = std::ranges::end(range); auto end = std::ranges::end(range);
@@ -171,7 +171,7 @@ public:
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.) // Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R> template <std::ranges::range R>
requires (!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>) requires (!std::is_convertible<R, std::string_view>::value && !concepts::optional_type<R> && !require_custom_serialization<R>)
simdjson_inline void append(const R &range) noexcept; simdjson_inline void append(const R &range) noexcept;
#endif #endif
/** /**
@@ -185,6 +185,15 @@ requires (!std::is_convertible<R, std::string_view>::value && !require_custom_se
* There is no UTF-8 validation. * There is no UTF-8 validation.
*/ */
simdjson_inline void append_raw(const char *str, size_t len) noexcept; simdjson_inline void append_raw(const char *str, size_t len) noexcept;
/**
* Append exactly N characters from str. The length is a template parameter
* so the compiler can fully inline the memcpy with a compile-time-constant
* size, avoiding the libc call. Used for compile-time-constant keys in the
* reflection struct atom.
*/
template <size_t N>
simdjson_inline void append_raw_n(const char *str) noexcept;
#if SIMDJSON_EXCEPTIONS #if SIMDJSON_EXCEPTIONS
/** /**
* Creates an std::string from the written JSON buffer. * Creates an std::string from the written JSON buffer.
@@ -236,6 +245,26 @@ requires (!std::is_convertible<R, std::string_view>::value && !require_custom_se
*/ */
simdjson_inline size_t size() const noexcept; simdjson_inline size_t size() const noexcept;
// ============================================================
// Internal hooks for the position-as-local writer in json_builder.h.
// These exist so the reflection atom code can hold buffer pointer,
// position and capacity in registers across long write chains rather
// than reloading them after every char* write (strict aliasing
// forces those reloads when accessed via members of *this). User
// code should NOT call these directly.
// ============================================================
simdjson_inline char *unsafe_data() noexcept { return buffer.get(); }
simdjson_inline size_t unsafe_position() const noexcept { return position; }
simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; }
simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; }
/// Make capacity available for at least `n` more bytes after the current
/// position. Returns false if the allocation failed.
simdjson_inline bool unsafe_grow(size_t needed_total_capacity) noexcept {
grow_buffer(needed_total_capacity);
return is_valid;
}
simdjson_inline bool unsafe_is_valid() const noexcept { return is_valid; }
private: private:
/** /**
* Returns true if we can write at least upcoming_bytes bytes. * Returns true if we can write at least upcoming_bytes bytes.
@@ -249,7 +278,7 @@ private:
* If the allocation fails, is_valid is set to false. We expect * If the allocation fails, is_valid is set to false. We expect
* that this function would not be repeatedly called. * that this function would not be repeatedly called.
*/ */
simdjson_inline void grow_buffer(size_t desired_capacity); inline void grow_buffer(size_t desired_capacity);
/** /**
* We use this helper function to make sure that is_valid is kept consistent. * We use this helper function to make sure that is_valid is kept consistent.
@@ -280,7 +309,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;
@@ -63,7 +63,11 @@ inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parse
inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept {
if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; }
// Stage 1 index output // Stage 1 index output
size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; size_t rounded_capacity = SIMDJSON_ROUNDUP_N(capacity, 64);
if(rounded_capacity + 9 < rounded_capacity) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t max_structures = rounded_capacity + 9;
structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] );
if (!structural_indexes) { _capacity = 0; return MEMALLOC; } if (!structural_indexes) { _capacity = 0; return MEMALLOC; }
structural_indexes[0] = 0; structural_indexes[0] = 0;
@@ -74,6 +78,7 @@ inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(s
} }
inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept {
if(max_depth > SIMDJSON_MAX_DEPTH) { return CAPACITY; }
// Stage 2 stacks // Stage 2 stacks
open_containers.reset(new (std::nothrow) open_container[max_depth]); open_containers.reset(new (std::nothrow) open_container[max_depth]);
is_array.reset(new (std::nothrow) bool[max_depth]); is_array.reset(new (std::nothrow) bool[max_depth]);
+62 -4
View File
@@ -4,6 +4,7 @@
#define SIMDJSON_GENERIC_NUMBERPARSING_H #define SIMDJSON_GENERIC_NUMBERPARSING_H
#include "simdjson/generic/base.h" #include "simdjson/generic/base.h"
#include "simdjson/generic/jsoncharutils.h" #include "simdjson/generic/jsoncharutils.h"
#include "simdjson/generic/atomparsing.h"
#include "simdjson/internal/numberparsing_tables.h" #include "simdjson/internal/numberparsing_tables.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE #endif // SIMDJSON_CONDITIONAL_INCLUDE
@@ -299,6 +300,24 @@ simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative,
return true; return true;
} }
#if SIMDJSON_ENABLE_NAN_INF
// Parses a nan or infinity. Returns true on success, false on failure.
simdjson_unused simdjson_inline bool compute_nan_inf(const uint8_t* src, bool negative, double& d) noexcept {
if (atomparsing::is_valid_inf_atom(src)) {
double inf = std::numeric_limits<double>::infinity();
d = negative ? -inf : inf;
return true;
}
if (atomparsing::is_valid_nan_atom(src)) {
d = std::numeric_limits<double>::quiet_NaN();
return true;
}
return false;
}
#endif
// We call a fallback floating-point parser that might be slow. Note // We call a fallback floating-point parser that might be slow. Note
// it will accept JSON numbers, but the JSON spec. is more restrictive so // it will accept JSON numbers, but the JSON spec. is more restrictive so
// before you call parse_float_fallback, you need to have validated the input // before you call parse_float_fallback, you need to have validated the input
@@ -478,7 +497,7 @@ simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t d
} // unnamed namespace } // unnamed namespace
/** @private */ /** @private */
static error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) { inline error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
if (parse_float_fallback(src, answer)) { if (parse_float_fallback(src, answer)) {
return SUCCESS; return SUCCESS;
} }
@@ -600,7 +619,21 @@ simdjson_warn_unused simdjson_inline error_code parse_number(const uint8_t *cons
// If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error.
// Optimization note: size_t is expected to be unsigned. // Optimization note: size_t is expected to be unsigned.
size_t digit_count = size_t(p - start_digits); size_t digit_count = size_t(p - start_digits);
if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) {
#if SIMDJSON_ENABLE_NAN_INF
// By this point, we know that our input does not begin with a digit. We will attempt
// to handle NaN/Infinity.
double d;
if (compute_nan_inf(p, negative, d)) {
writer.append_double(d);
return SUCCESS;
}
#endif
return INVALID_NUMBER(src);
}
// //
// Handle floats if there is a . or e (or both) // Handle floats if there is a . or e (or both)
@@ -1031,7 +1064,17 @@ simdjson_unused simdjson_inline simdjson_result<double> parse_double(const uint8
bool leading_zero = (i == 0); bool leading_zero = (i == 0);
while (parse_digit(*p, i)) { p++; } while (parse_digit(*p, i)) { p++; }
// no integer digits, or 0123 (zero must be solo) // no integer digits, or 0123 (zero must be solo)
if ( p == src ) { return INCORRECT_TYPE; } if ( p == src ) {
#if SIMDJSON_ENABLE_NAN_INF
// If there are no loading digits, the number may be nan or infinity.
// Attempt to compute those, and return on success.
double d;
if (compute_nan_inf(p, negative, d)) { return d; }
#endif
return INCORRECT_TYPE;
}
if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; }
// //
@@ -1249,7 +1292,22 @@ simdjson_unused simdjson_inline simdjson_result<double> parse_double_in_string(c
bool leading_zero = (i == 0); bool leading_zero = (i == 0);
while (parse_digit(*p, i)) { p++; } while (parse_digit(*p, i)) { p++; }
// no integer digits, or 0123 (zero must be solo) // no integer digits, or 0123 (zero must be solo)
if ( p == src ) { return INCORRECT_TYPE; } if ( p == src ) {
#if SIMDJSON_ENABLE_NAN_INF
// If there are no leading digits, attempt to parse numbers that are either
// NaN or Infinity
if (atomparsing::is_valid_inf_in_string(src)) {
double inf = std::numeric_limits<double>::infinity();
return negative ? -inf : inf;
}
if (atomparsing::is_valid_nan_in_string(src)) {
return std::numeric_limits<double>::quiet_NaN();
}
#endif
return INCORRECT_TYPE;
}
if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; }
// //
@@ -22,6 +22,7 @@
#include "simdjson/generic/ondemand/field.h" #include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/object.h" #include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h" #include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/ranges.h"
#include "simdjson/generic/ondemand/serialization.h" #include "simdjson/generic/ondemand/serialization.h"
// Deserialization for standard types // Deserialization for standard types
@@ -39,6 +40,7 @@
#include "simdjson/generic/ondemand/logger-inl.h" #include "simdjson/generic/ondemand/logger-inl.h"
#include "simdjson/generic/ondemand/object-inl.h" #include "simdjson/generic/ondemand/object-inl.h"
#include "simdjson/generic/ondemand/object_iterator-inl.h" #include "simdjson/generic/ondemand/object_iterator-inl.h"
#include "simdjson/generic/ondemand/ranges-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/token_iterator-inl.h" #include "simdjson/generic/ondemand/token_iterator-inl.h"
+27 -48
View File
@@ -135,21 +135,9 @@ inline simdjson_result<value> array::at_pointer(std::string_view json_pointer) n
// We don't support this, because we're returning a real element, not a position. // We don't support this, because we're returning a real element, not a position.
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
// Read the array index
size_t array_index = 0; size_t array_index = 0;
size_t i; size_t i;
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) { SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, i));
uint8_t digit = uint8_t(json_pointer[i] - '0');
// Check for non-digit in array index. If it's there, we're trying to get a field in an object
if (digit > 9) { return INCORRECT_TYPE; }
array_index = array_index*10 + digit;
}
// 0 followed by other digits is invalid
if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0"
// Empty string is invalid; so is a "/" with no digits before it
if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index"
// Get the child // Get the child
auto child = at(array_index); auto child = at(array_index);
// If there is an error, it ends here // If there is an error, it ends here
@@ -170,41 +158,29 @@ 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()) { if (remaining_path.empty()) {
// Use value_unsafe() because we've already checked for errors above. callback(val);
// 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 { } else {
auto nested_result = element.at_path_with_wildcard(remaining_path); error_code err = element.for_each_at_path_with_wildcard(remaining_path, callback);
if(err) { return err; }
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;
@@ -217,16 +193,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()){ if (remaining_path.empty()){
result.push_back(std::move(element).value_unsafe()); callback(val);
return result; return SUCCESS;
} else { } else {
return element.at_path_with_wildcard(remaining_path); return element.for_each_at_path_with_wildcard(remaining_path, callback);
} }
} }
} }
@@ -289,9 +262,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
+7
View File
@@ -40,6 +40,13 @@ class token_iterator;
class value; class value;
class value_iterator; class value_iterator;
#if SIMDJSON_SUPPORTS_RANGES
class array_range;
class array_range_iterator;
class object_range;
class object_range_iterator;
#endif // SIMDJSON_SUPPORTS_RANGES
} // namespace ondemand } // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION } // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson } // namespace simdjson
@@ -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();
@@ -627,27 +659,27 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::op
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator uint64_t() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator uint64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_uint64();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator int64_t() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator int64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_int64();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator double() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator double() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_double();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator std::string_view() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator std::string_view() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_string();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_raw_json_string();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator bool() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator bool() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_bool();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
@@ -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();
@@ -994,27 +1060,27 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_refe
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator uint64_t() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator uint64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_uint64();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator int64_t() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator int64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_int64();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator double() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator double() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_double();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator std::string_view() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator std::string_view() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_string();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_raw_json_string();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator bool() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator bool() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
return first; return first.get_bool();
} }
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) { simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) {
if (error()) { throw simdjson_error(error()); } if (error()) { throw simdjson_error(error()); }
@@ -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>
+82 -35
View File
@@ -76,6 +76,26 @@ public:
* @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer.
*/ */
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept; simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
/**
* Cast this JSON value to a 32-bit unsigned integer.
*
* Calls get_uint64() and checks that the result fits in a uint32_t.
*
* @returns A 32-bit unsigned integer.
* @returns INCORRECT_TYPE If the JSON value is not an unsigned integer.
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in a uint32_t.
*/
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
/**
* Cast this JSON value to a 32-bit signed integer.
*
* Calls get_int64() and checks that the result fits in an int32_t.
*
* @returns A 32-bit signed integer.
* @returns INCORRECT_TYPE If the JSON value is not an integer.
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in an int32_t.
*/
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
/** /**
* Cast this JSON value to a double. * Cast this JSON value to a double.
* *
@@ -299,21 +319,21 @@ public:
* @returns A signed 64-bit integer. * @returns A signed 64-bit integer.
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer. * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer.
*/ */
simdjson_inline operator uint64_t() noexcept(false); explicit simdjson_inline operator uint64_t() noexcept(false);
/** /**
* Cast this JSON value to a signed integer. * Cast this JSON value to a signed integer.
* *
* @returns A signed 64-bit integer. * @returns A signed 64-bit integer.
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer. * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer.
*/ */
simdjson_inline operator int64_t() noexcept(false); explicit simdjson_inline operator int64_t() noexcept(false);
/** /**
* Cast this JSON value to a double. * Cast this JSON value to a double.
* *
* @returns A double. * @returns A double.
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number. * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number.
*/ */
simdjson_inline operator double() noexcept(false); explicit simdjson_inline operator double() noexcept(false);
/** /**
* Cast this JSON value to a string. * Cast this JSON value to a string.
* *
@@ -323,7 +343,7 @@ public:
* time it parses a document or when it is destroyed. * time it parses a document or when it is destroyed.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
*/ */
simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound; explicit simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound;
/** /**
* Cast this JSON value to a raw_json_string. * Cast this JSON value to a raw_json_string.
* *
@@ -332,14 +352,14 @@ public:
* @returns A pointer to the raw JSON for the given string. * @returns A pointer to the raw JSON for the given string.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
*/ */
simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound; explicit simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound;
/** /**
* Cast this JSON value to a bool. * Cast this JSON value to a bool.
* *
* @returns A bool value. * @returns A bool value.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false. * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false.
*/ */
simdjson_inline operator bool() noexcept(false); explicit simdjson_inline operator bool() noexcept(false);
/** /**
* Cast this JSON value to a value when the document is an object or an array. * Cast this JSON value to a value when the document is an object or an array.
* *
@@ -724,21 +744,24 @@ public:
simdjson_inline simdjson_result<value> at_path(std::string_view json_path) noexcept; simdjson_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 "$.array[*]" or "$.object.*" to match multiple elements. * Supports wildcard patterns like "$.array[*]" or "$.object.*" to match multiple elements.
* *
* This method materializes all matching values into a vector.
* The document will be consumed after this call. * The document will be consumed after this call.
* *
* @param json_path JSONPath expression with wildcards * @param json_path JSONPath expression with wildcards
* @return Vector of values matching the wildcard pattern, or: * @param callback Function called for each matching value
* - INVALID_JSON_POINTER if the JSONPath cannot be parsed * @return error_code indicating success or failure
* - NO_SUCH_FIELD if a field does not exist
* - INDEX_OUT_OF_BOUNDS if an array index is out of bounds
* - INCORRECT_TYPE if path traversal encounters wrong type
*/ */
simdjson_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
simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept;
/** /**
* Consumes the document and returns a string_view instance corresponding to the * Consumes the document and returns a string_view instance corresponding to the
@@ -829,6 +852,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept; simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept; simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept; simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept; simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept; simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept; simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -923,12 +948,12 @@ public:
explicit simdjson_inline operator T() noexcept(false); explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator array() & noexcept(false); simdjson_inline operator array() & noexcept(false);
simdjson_inline operator object() & noexcept(false); simdjson_inline operator object() & noexcept(false);
simdjson_inline operator uint64_t() noexcept(false); explicit simdjson_inline operator uint64_t() noexcept(false);
simdjson_inline operator int64_t() noexcept(false); explicit simdjson_inline operator int64_t() noexcept(false);
simdjson_inline operator double() noexcept(false); explicit simdjson_inline operator double() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false); explicit simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator raw_json_string() noexcept(false); explicit simdjson_inline operator raw_json_string() noexcept(false);
simdjson_inline operator bool() noexcept(false); explicit simdjson_inline operator bool() noexcept(false);
simdjson_inline operator value() noexcept(false); simdjson_inline operator value() noexcept(false);
#endif #endif
simdjson_inline simdjson_result<size_t> count_elements() & noexcept; simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
@@ -957,7 +982,13 @@ public:
simdjson_inline simdjson_result<std::string_view> raw_json_token() noexcept; simdjson_inline simdjson_result<std::string_view> raw_json_token() noexcept;
simdjson_inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result<value> at_pointer(std::string_view json_pointer) noexcept;
simdjson_inline simdjson_result<value> at_path(std::string_view json_path) noexcept; simdjson_inline simdjson_result<value> at_path(std::string_view json_path) noexcept;
simdjson_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
simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept;
private: private:
document *doc{nullptr}; document *doc{nullptr};
@@ -982,6 +1013,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept; simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept; simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept; simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept; simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept; simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept; simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -1006,12 +1039,12 @@ public:
explicit simdjson_inline operator T() noexcept(false); explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false); simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false); simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false);
simdjson_inline operator uint64_t() noexcept(false); explicit simdjson_inline operator uint64_t() noexcept(false);
simdjson_inline operator int64_t() noexcept(false); explicit simdjson_inline operator int64_t() noexcept(false);
simdjson_inline operator double() noexcept(false); explicit simdjson_inline operator double() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false); explicit simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); explicit simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
simdjson_inline operator bool() noexcept(false); explicit simdjson_inline operator bool() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false); simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false);
#endif #endif
simdjson_inline simdjson_result<size_t> count_elements() & noexcept; simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
@@ -1041,7 +1074,13 @@ public:
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;
#if SIMDJSON_STATIC_REFLECTION #if SIMDJSON_STATIC_REFLECTION
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0)) requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
@@ -1069,6 +1108,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept; simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept; simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept; simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept; simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept; simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept; simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -1090,12 +1131,12 @@ public:
explicit simdjson_inline operator T() noexcept(false); explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false); simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false); simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false);
simdjson_inline operator uint64_t() noexcept(false); explicit simdjson_inline operator uint64_t() noexcept(false);
simdjson_inline operator int64_t() noexcept(false); explicit simdjson_inline operator int64_t() noexcept(false);
simdjson_inline operator double() noexcept(false); explicit simdjson_inline operator double() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false); explicit simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); explicit simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
simdjson_inline operator bool() noexcept(false); explicit simdjson_inline operator bool() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false); simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false);
#endif #endif
simdjson_inline simdjson_result<size_t> count_elements() & noexcept; simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
@@ -1124,7 +1165,13 @@ public:
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;
#if SIMDJSON_STATIC_REFLECTION #if SIMDJSON_STATIC_REFLECTION
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0)) requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
@@ -95,13 +95,15 @@ simdjson_inline document_stream::document_stream(
const uint8_t *_buf, const uint8_t *_buf,
size_t _len, size_t _len,
size_t _batch_size, size_t _batch_size,
bool _allow_comma_separated bool _allow_comma_separated,
stream_format _format
) noexcept ) noexcept
: parser{&_parser}, : parser{&_parser},
buf{_buf}, buf{_buf},
len{_len}, len{_len},
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
allow_comma_separated{_allow_comma_separated}, allow_comma_separated{_allow_comma_separated},
format{_format},
error{SUCCESS} error{SUCCESS}
#ifdef SIMDJSON_THREADS_ENABLED #ifdef SIMDJSON_THREADS_ENABLED
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
@@ -120,6 +122,7 @@ simdjson_inline document_stream::document_stream() noexcept
len{0}, len{0},
batch_size{0}, batch_size{0},
allow_comma_separated{false}, allow_comma_separated{false},
format{stream_format::whitespace_delimited},
error{UNINITIALIZED} error{UNINITIALIZED}
#ifdef SIMDJSON_THREADS_ENABLED #ifdef SIMDJSON_THREADS_ENABLED
, use_thread(false) , use_thread(false)
@@ -219,7 +222,10 @@ inline void document_stream::start() noexcept {
error = run_stage1(*parser, batch_start); error = run_stage1(*parser, batch_start);
} }
if (error) { return; } if (error) { return; }
doc_index = batch_start; // For json_sequence mode, structural_indexes[0] points to the actual JSON value
// after the RS delimiter and any following whitespace. For regular mode, it is
// the offset from batch_start to the first document in the batch.
doc_index = batch_start + parser->implementation->structural_indexes[0];
doc = document(json_iterator(&buf[batch_start], parser)); doc = document(json_iterator(&buf[batch_start], parser));
doc.iter._streaming = true; doc.iter._streaming = true;
@@ -300,7 +306,7 @@ inline void document_stream::next() noexcept {
*/ */
if (error) { continue; } // If the error was EMPTY, we may want to load another batch. if (error) { continue; } // If the error was EMPTY, we may want to load another batch.
doc_index = batch_start; doc_index = batch_start + parser->implementation->structural_indexes[0];
} }
} }
} }
@@ -329,10 +335,35 @@ inline error_code document_stream::run_stage1(ondemand::parser &p, size_t _batch
// This code only updates the structural index in the parser, it does not update any json_iterator // This code only updates the structural index in the parser, it does not update any json_iterator
// instance. // instance.
size_t remaining = len - _batch_start; size_t remaining = len - _batch_start;
stage1_mode mode;
if (remaining <= batch_size) { if (remaining <= batch_size) {
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final); // Final batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_final;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_final;
break;
default:
mode = stage1_mode::streaming_final;
break;
}
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
} else { } else {
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial); // Partial batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_partial;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_partial;
break;
default:
mode = stage1_mode::streaming_partial;
break;
}
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
} }
} }
@@ -353,14 +384,21 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
depth--; depth--;
break; break;
default: // Scalar value document default: // Scalar value document
// TODO: We could remove trailing whitespaces
// This returns a string spanning from start of value to the beginning of the next document (excluded) // This returns a string spanning from start of value to the beginning of the next document (excluded)
{ {
auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index]; auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index];
// normally the length would be next_index - current_index() - 1, except for the last document // normally the length would be next_index - current_index() - 1, except for the last document
size_t svlen = next_index - current_index(); size_t svlen = next_index - current_index();
const char *start = reinterpret_cast<const char*>(stream->buf) + current_index(); const char *start = reinterpret_cast<const char*>(stream->buf) + current_index();
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) { // Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464
// json_sequence mode the scanner classifies RS as a scalar
// character, so an RS-prefixed scalar document (number / true /
// false / null / string) has no closing structural index and the
// slice runs all the way up to the next document's RS. RS cannot
// legally appear in a JSON value at the source level (control
// characters in strings must be escaped as \u001E), so stripping
// it is safe in every stream_format.
while(svlen > 1 && (std::isspace(static_cast<unsigned char>(start[svlen-1])) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E || (stream->format == stream_format::comma_delimited && start[svlen-1] == ','))) {
svlen--; svlen--;
} }
return std::string_view(start, svlen); return std::string_view(start, svlen);

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