Compare commits

..

62 Commits

Author SHA1 Message Date
Daniel Lemire 12062bc5bc simplifying. 2026-06-05 19:24:19 -04:00
Daniel Lemire 6e0abb7f9c lower level. 2026-06-03 00:32:41 -04:00
Daniel Lemire 686c9bff54 experimental 2026-06-02 23:39:47 -04:00
Daniel Lemire e1654be9b3 removing useless comment. 2026-06-01 20:57:12 -04:00
Daniel Lemire de1b38c459 better documentation. 2026-05-31 18:22:12 -04:00
Daniel Lemire 3d08f995d0 minor optimization. 2026-05-31 15:07:01 -04:00
Daniel Lemire 135159012f update 2026-05-30 21:38:40 -04:00
Daniel Lemire 936afe9aa1 missing file 2026-05-30 19:39:14 -04:00
Daniel Lemire 1cc03882de minor extension. 2026-05-30 19:19:30 -04:00
Daniel Lemire 9cd389ae84 extra documentation. 2026-05-29 22:05:07 -04:00
Daniel Lemire 64d5ee550f maybe the final update. 2026-05-29 21:37:02 -04:00
Daniel Lemire 18cdecaf8e update 2026-05-29 20:08:39 -04:00
Daniel Lemire c33e4441a0 safety fix for yyjson benchmark 2026-05-29 18:12:27 -04:00
Daniel Lemire 732ca8cf70 new file 2026-05-29 17:47:47 -04:00
Daniel Lemire 2143ebe538 update 2026-05-29 17:47:10 -04:00
Daniel Lemire df770af450 better 2026-05-29 17:46:03 -04:00
Daniel Lemire 1985584de7 inlining. 2026-05-29 17:46:03 -04:00
Daniel Lemire 6a74cd41d6 proto 2026-05-29 17:46:03 -04:00
Daniel Lemire dcaad94b53 init 2026-05-29 17:46:03 -04:00
Daniel Lemire 98fde69253 adding clang 20 to our CI coverage. (#2739) 2026-05-29 17:43:48 -04:00
Daniel Lemire dc485f845d let us use lower-case words for layout_mode (#2740) 2026-05-29 17:43:33 -04:00
Daniel Lemire 79bbba3e3e saving. 2026-05-27 10:01:05 -04:00
吴杨帆 1a29a5e53a docs: clarify raw_json() consumption and rewind option (#2734)
* docs: clarify raw_json() consumption and rewind option

Document that raw_json() consumes objects/arrays and point readers to
reset() or document::rewind() when they need to parse again.

Related to #1943

* ci: retrigger workflow
2026-05-27 08:12:00 -04:00
Daniel Lemire 57561f64f4 adding warning that this is experimental. 2026-05-26 15:19:32 -04:00
Daniel Lemire 00563f9133 documenting annotation. 2026-05-26 13:34:37 -04:00
Makkar 5db72ab9d3 add: C++26 annotations support for rename and skip (#2730) 2026-05-26 12:58:46 -04:00
Daniel Lemire d3d99b8fc5 we omit ondemand_cacheline under riscv64 2026-05-25 11:19:05 -04:00
Daniel Lemire 168ef58075 simplify the tests for memory mapping under Windows. (#2729) 2026-05-20 14:37:52 -04:00
Daniel Lemire fb8c575121 more verbose error messages 2026-05-20 14:37:05 -04:00
Daniel Lemire 7732480b25 we need to require rvv for the risc64 kernel 2026-05-18 17:24:56 -04:00
wankun 1ae93a70d4 Bug fix for get_string() crash when parse incomplete json string (in option disabled my default) (#2720)
* get_string() crash when parse incomplete json string

* get_string() crash when parse incomplete json string

* Bug fix for RISC-V fail

* Update tests/ondemand/CMakeLists.txt

Co-authored-by: Daniel Lemire <daniel@lemire.me>

* Remove unnecessary changes

---------

Co-authored-by: Daniel Lemire <daniel@lemire.me>
2026-05-18 17:18:58 -04:00
吴杨帆 396ca141fd perf(builder): add LSX fast_needs_escaping for LoongArch (#2727)
Use LSX intrinsics for the string-builder escape scan on LoongArch,
matching the existing find_next_json_quotable_character LSX path.

Fixes #2603
2026-05-18 17:13:43 -04:00
Daniel Lemire 8e2e9b8e02 adding silent flag to amalgamation. (#2721) 2026-05-18 11:20:02 -04:00
吴杨帆 599895e54d docs: use HTTPS for YouTube embed links in README (#2724) 2026-05-18 11:19:46 -04:00
吴杨帆 77a53d19a2 docs: use HTTPS for Boost license link in README (#2725) 2026-05-18 11:16:31 -04:00
吴杨帆 8c7cc2f963 docs: use HTTPS for arXiv link in README (#2726) 2026-05-18 11:16:11 -04:00
xaldarof 43766fc7ef add simdjson-dart binding to docs (#2719)
* Add simdjson-dart binding description

Fix typo in simdjson-dart description.

* Update simdjson-dart description in README
2026-05-12 11:42:16 -04:00
Aylin Dmello c2f25ab455 Add section to basics.md documenting the SIMDJSON_ENABLE_NAN_INF flag (#2718)
Co-authored-by: Aylin Dmello <admello@mathworks.com>
2026-05-11 17:11:47 -04: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
89 changed files with 5483 additions and 1146 deletions
+1 -1
View File
@@ -58,7 +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
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.
* 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.
* 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. As of 2026, Windows 10 is no longer supported.
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).
+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 &&
cd ../tests/installation_tests/find &&
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
@@ -26,4 +26,4 @@ jobs:
run: |
cmake -DCMAKE_BUILD_TYPE=Release -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
cmake --build build -j=2
ctest --output-on-failure --test-dir build
ctest --output-on-failure --test-dir build -E ondemand_cacheline
+27
View File
@@ -0,0 +1,27 @@
name: Ubuntu 24.04 CI (CLANG 20)
on: [push, pull_request]
jobs:
ubuntu-build:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
- name: Install clang-20
run: |
sudo apt-get update -q -y
sudo apt-get install -y clang-20
- name: Use cmake
run: |
mkdir build &&
cd build &&
CXX=clang++-20 cmake -DSIMDJSON_DEVELOPER_MODE=ON .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j
+14
View File
@@ -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 --build . &&
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:
if: >-
! 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 --build . &&
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]
cxx: [g++-13, clang++-16]
sanitizer: [ON, OFF]
nan_inf: [ON, OFF]
build_type: [RelWithDebInfo, Debug, Release]
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- 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:
CXX: ${{matrix.cxx}}
- name: Build
+10 -10
View File
@@ -13,20 +13,20 @@ jobs:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF}
# 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}
- {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, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: ON, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: ON, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: ON}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: ON}
steps:
- name: checkout
uses: actions/checkout@v4
- name: Configure
run: |
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}} -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
run: cmake --build build --config ${{matrix.build_type}} --verbose
- name: Run tests
@@ -39,4 +39,4 @@ jobs:
- name: Test Installation
run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -B build_install_test tests/installation_tests/find
cmake --build build_install_test --config ${{matrix.build_type}}
cmake --build build_install_test --config ${{matrix.build_type}}
+6
View File
@@ -254,6 +254,12 @@ if(SIMDJSON_MINUS_ZERO_AS_FLOAT)
simdjson_add_props(target_compile_definitions PRIVATE SIMDJSON_MINUS_ZERO_AS_FLOAT=1)
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.
# A common fix is to use '-Og' instead.
# bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412
+5 -4
View File
@@ -190,6 +190,7 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
- [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.
- [simdjson-dart](https://github.com/xaldarof/simdjson-dart): Dart bindings for the simdjson project.
About simdjson
--------------
@@ -200,7 +201,7 @@ CPU's multiple execution cores.
Our default front-end is called On-Demand, and we wrote a paper about it:
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](http://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](https://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
Some people [enjoy reading the first (2019) simdjson paper](https://arxiv.org/abs/1902.08318): A description of the design
and implementation of simdjson is in our research article:
@@ -213,12 +214,12 @@ 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/).
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](https://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](https://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
(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 />
[![simdjson at CppCon 2025](https://img.youtube.com/vi/Mcgk3CxHYMs/0.jpg)](https://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
@@ -271,7 +272,7 @@ This code is made available under the [Apache License 2.0](https://www.apache.or
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it is under the liberal (business-friendly) MIT license.
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](http://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](https://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
For efficient number serialization, we bundle Florian Loitsch's implementation of the Grisu2 algorithm for binary to decimal floating-point numbers. The implementation was slightly modified by JSON for Modern C++ library. Both Florian Loitsch's implementation and JSON for Modern C++ are provided under the MIT license.
+1
View File
@@ -125,6 +125,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
#include "kostya/boostjson.h"
#include "large_random/simdjson_ondemand.h"
#include "large_random/simdjson_ondemand_ranges.h"
#if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
#include "large_random/simdjson_ondemand_unordered.h"
#endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
@@ -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) {
// 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]);
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
if (capacity == 0) { // set max depth the first time only
@@ -0,0 +1,64 @@
#pragma once
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
#include "partial_tweets.h"
namespace partial_tweets {
using namespace simdjson;
struct simdjson_ondemand_key_selector {
using StringType = std::string_view;
ondemand::parser parser{};
// Compile-time selectors — all PHF tables are static constexpr, so every
// call to match_raw fully inlines.
using tweet_sel_t = ondemand::key_selector<
"created_at", "id", "text", "in_reply_to_status_id",
"user", "retweet_count", "favorite_count">;
using user_sel_t = ondemand::key_selector<"id", "screen_name">;
simdjson_inline uint64_t nullable_int(ondemand::value value) {
if (value.is_null()) { return 0; }
return value;
}
simdjson_inline twitter_user<std::string_view> read_user(ondemand::object user) {
twitter_user<std::string_view> out{};
user.for_each<user_sel_t>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: out.id = uint64_t(v); break; // "id"
case 1: out.screen_name = std::string_view(v); break; // "screen_name"
}
});
return out;
}
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
auto doc = parser.iterate(json);
for (ondemand::object tw : doc.find_field("statuses")) {
tweet<std::string_view> t{};
tw.for_each<tweet_sel_t>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: t.created_at = std::string_view(v); break; // "created_at"
case 1: t.id = uint64_t(v); break; // "id"
case 2: t.result = std::string_view(v); break; // "text"
case 3: t.in_reply_to_status_id = nullable_int(v); break; // "in_reply_to_status_id"
case 4: t.user = read_user(v); break; // "user"
case 5: t.retweet_count = uint64_t(v); break; // "retweet_count"
case 6: t.favorite_count = uint64_t(v); break; // "favorite_count"
}
});
result.emplace_back(std::move(t));
}
return true;
}
};
BENCHMARK_TEMPLATE(partial_tweets, simdjson_ondemand_key_selector)->UseManualTime();
} // namespace partial_tweets
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
+15 -8
View File
@@ -61,22 +61,29 @@ struct yyjson_base {
};
struct yyjson : yyjson_base {
// The document owns the string memory that result's string_views point into,
// so it must outlive each run() (the verification diff happens after run()
// returns). Free it on the next run() / at destruction, not before the views
// are read.
yyjson_doc *doc{};
~yyjson() { if (doc != nullptr) { yyjson_doc_free(doc); } }
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
doc = yyjson_read(json.data(), json.size(), 0);
return yyjson_base::run(doc, result);
}
};
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
// See the note on yyjson above: the document must outlive result's views.
yyjson_doc *doc{};
~yyjson_insitu() { if (doc != nullptr) { yyjson_doc_free(doc); } }
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
return yyjson_base::run(doc, result);
}
};
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
+422 -34
View File
@@ -23,11 +23,17 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
* [2. Use `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20)
* [3. Using static reflection (C++26)](#3-using-static-reflection-c26)
+ [Special cases](#special-cases)
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
* [The simdjson::from shortcut (experimental, C++20)](#the-simdjsonfrom-shortcut-experimental-c20)
* [Order-independent reflective deserialization (experimental)](#order-independent-reflective-deserialization-experimental)
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
- [UTF-8 validation (alone)](#utf-8-validation-alone)
- [JSON Pointer](#json-pointer)
- [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)
- [Key selectors](#key-selectors)
- [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection)
- [Error handling](#error-handling)
* [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
@@ -39,6 +45,7 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
- [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
- [Parsing numbers inside strings](#parsing-numbers-inside-strings)
- [Dynamic Number Types](#dynamic-number-types)
- [Infinity and NaN support](#infinity-and-nan-support)
- [Raw strings from keys](#raw-strings-from-keys)
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
* [Raw JSON string for objects and arrays](#raw-json-string-for-objects-and-arrays)
@@ -56,7 +63,7 @@ Requirements
The simdjson library is widely deployed in popular systems such as the Node.js runtime
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).
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.
@@ -452,7 +459,7 @@ We also have a generic ephemeral type (`simdjson::ondemand::value`) which repres
array or object, or scalar type (`double`, `uint64_t`, `int64_t`, `bool`, `null`, string) inside
an array or an object. Both generic types (`simdjson::ondemand::document` and
`simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing indicating the type (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`,
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens such as the `NaN` token in `{"key":NaN}`. A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens (such as the `NaN` token in `{"key":NaN}` when using the default strict parsing behavior). A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
is only valid temporarily, as soon as you access other values, other keys in objects, etc.
it becomes invalid: you should therefore consume the value immediately by converting it to a
scalar type, an array or an object.
@@ -1441,7 +1448,9 @@ 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.
in the order they appear in the JSON document. Alternatively, [key selectors](#key-selectors)
let you extract a fixed, known set of fields in a single pass, independently of the
order in which they appear in the document.
### 3. Using static reflection (C++26)
@@ -1520,7 +1529,9 @@ 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.
in the order they appear in the JSON document. Alternatively, [key selectors](#key-selectors)
let you extract a fixed, known set of fields in a single pass, independently of the
order in which they appear in the document.
#### Special cases
@@ -1588,6 +1599,51 @@ You can also automatically serialize the `Car` instance to a JSON string, see
our [Builder documentation](builder.md).
#### Renaming and skipping fields with annotations
**This is experimental: the syntax may change slightly in the future.**
C++26 annotations provide a convenient way to customize (de)serialization
without writing `tag_invoke` functions. You can rename the JSON key that
corresponds to a C++ data member, or skip a member entirely.
The syntax is:
```cpp
// rename cppFieldName (in C++) to json_key_name (in JSON)
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
// do not serialize or deserialize this field
[[= simdjson::skip]] int internalState;
```
Full examples:
```cpp
struct RenamedFields {
[[= simdjson::rename<"first_name">]] std::string firstName = "";
[[= simdjson::rename<"last_name">]] std::string lastName = "";
int age = 0;
};
struct SkippedField {
std::string name = "";
[[= simdjson::skip]] int internalCache = 0;
};
struct MixedAnnotations {
[[= simdjson::rename<"user_name">]] std::string userName = "";
[[= simdjson::skip]] int sessionToken = 0;
int age = 0;
};
```
- Serialization via `simdjson::to_json(r)` or `builder << r` will use the renamed
keys and omit skipped fields.
- Deserialization via `doc.get<RenamedFields>()` will map the JSON keys back
to the C++ fields. Skipped fields are never written during deserialization
(they keep their default-initialized value), and keys matching skipped fields
in the JSON input are ignored.
### The simdjson::from shortcut (experimental, C++20)
@@ -1599,6 +1655,10 @@ type without a document instance like so:
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:
```cpp
Car car;
@@ -1624,6 +1684,56 @@ std::map<std::string, std::string> obj =
The `simdjson::from` construction is EXPERIMENTAL and subject to changes.
### Order-independent reflective deserialization (experimental)
> **Experimental and opt-in.** This feature is disabled by default. Enable it by
> defining the macro `SIMDJSON_USE_KEY_SELECTOR_REFLECTION=1` before including
> simdjson (e.g. as a compiler flag `-DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1`).
> It requires C++26 static reflection (`SIMDJSON_STATIC_REFLECTION`).
By default, reflective deserialization (`doc.get<T>()` / `simdjson::from`) reads
each struct member with an ordered field lookup. This is fastest when the JSON
keys appear in the same order as the struct's members, which is the common case.
When you genuinely cannot rely on the JSON key order — for example when the data
comes from a producer that emits members in an arbitrary or varying order — the
ordered lookups degrade, because each out-of-order key forces a rescan of the
object. For that situation simdjson offers an optional, order-independent
strategy: when `SIMDJSON_USE_KEY_SELECTOR_REFLECTION=1` is defined, the
reflective deserializer builds a compile-time [key selector](#key-selectors)
from the struct's members and walks each object **once** with
`object::for_each`, classifying every key through a perfect hash regardless of
its position.
```cpp
// Compile this translation unit with -DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1
#include "simdjson.h"
using namespace simdjson;
struct Tweet {
uint64_t id;
std::string text;
uint64_t retweet_count;
};
// Keys here are NOT in declaration order, yet deserialization succeeds.
auto json = R"({ "retweet_count": 7, "id": 12345, "text": "hello" })"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
Tweet t;
auto error = doc.get(t); // uses the key-selector path under the macro
```
**This is not a universal speedup — run your own benchmarks before adopting it.**
In our measurements the key-selector path is roughly on par with the default on
small structs (e.g. the Twitter user/tweet objects) but **slower** on documents
dominated by many small nested objects (e.g. the CITM catalog), because walking
every field of every object and hashing each key costs more than ordered,
short-circuiting lookups when the keys *are* in order. Only consider enabling the
macro when (a) your inputs really do present keys out of order, and (b) your own
benchmarks on your own data show a win. Otherwise, leave it off.
Minifying JSON strings without parsing
----------------------
@@ -1839,15 +1949,15 @@ int64_t x = obj.at_path("$.c.foo.a[1]"); // 20
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
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
simdjson::padded_string json_string = R"(
@@ -1876,31 +1986,248 @@ ondemand::parser parser;
ondemand::document doc = parser.iterate(json_string);
// Fetch all fields in the address object
std::vector<ondemand::value> values;
auto error = doc.at_path_with_wildcard("$.address.*").get(values);
if (!error) {
for (auto value : values) {
std::string_view field;
if (value.get(field) == SUCCESS) {
std::cout << field << std::endl;
}
}
}
auto error = doc.for_each_at_path_with_wildcard("$.address.*",
[](ondemand::value value) {
std::string_view field;
if (value.get(field) == SUCCESS) {
std::cout << field << std::endl;
}
});
// Fetch all phone numbers
error = doc.at_path_with_wildcard("$.phoneNumbers[*].numbers[*]").get(values);
if (!error) {
for (auto value : values) {
std::string_view number;
if (value.get(number) == SUCCESS) {
std::cout << number << std::endl;
}
}
}
doc.for_each_at_path_with_wildcard("$.phoneNumbers[*].numbers[*]",
[](ondemand::value value) {
std::string_view number;
if (value.get(number) == SUCCESS) {
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.
## 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.
## Key selectors
> **Experimental.** Key selectors are an experimental feature: the API may change
> in a future release.
When you need to extract a known, fixed set of fields from a JSON object and you
do not care about the order in which they appear in the document, *key selectors*
let you do it in a single pass: each selected key is mapped to a small integer
index, and you dispatch on that index (typically with a `switch`) instead of
repeatedly looking up keys by string.
**Requirements.** Key selectors rely on C++20 features (concepts and class-type
non-type template parameters), so they are only available when simdjson is
compiled in C++20 mode or later. When that support is present, the macro
`SIMDJSON_SUPPORTS_CONCEPTS` is defined.
Key selection works with hashing.
A *hash function* maps keys (here, JSON field names such as `"id"` or `"name"`)
to small integers. A *perfect* hash function is one that, for a fixed and known
set of keys, maps each key to a distinct slot with no collisions, so a single
hash computation plus one comparison suffices to identify a key, there is no
probing and no collision chains.
Because the set of keys is known at compile time, the simdjson library builds the perfect
hash function during compilation (using `consteval`). All of its lookup tables
become `static constexpr` data, which the compiler treats as constants at every
call site and can fully inline. At run time, recognizing a field name reduces to:
scan its length, compute a hash from a couple of bytes, and perform one
length-and-bytes comparison, branch-light and SIMD-accelerated.
You declare a selector type from a list of string literals:
```cpp
using sel = simdjson::ondemand::key_selector<"id", "name", "email">;
```
`sel` is a stateless type. Each key is assigned a fixed index, in declaration
order: `"id"` is 0, `"name"` is 1, `"email"` is 2. The type exposes a couple of
compile-time helpers:
- `sel::size()` — the number of keys in the selector (here, 3);
- `sel::key_at(i)` — the key text at index `i`.
You then walk an object with `object::for_each<sel>(callback)`. The callback is
invoked once for each field whose key belongs to the selector, **in JSON order**,
with two arguments: the selector index of the matched key, and the field's value
(an `ondemand::value` that you must consume inside the callback, before the next
field is visited, as usual with On Demand). Fields whose keys are not in the
selector are skipped without being parsed; duplicate keys are ignored after the
first match; and iteration stops as soon as every selector key has matched or the
object ends. The `for_each` call returns a `simdjson::error_code` (`SUCCESS`, or the first
error encountered while walking the object). Your callback may itself return a
`simdjson::error_code`: when it does, the walk stops at the first non-`SUCCESS`
result and `for_each` returns it. That is the recommended way to report a
value-parsing error (e.g. a type mismatch) from inside the callback.
Because the selector index is a small integer, a `switch` is the natural way to
dispatch on the matched field.
Key selectors are subject to a few compile-time restrictions:
- Key length. We currently limit keys to at most 31 characters long.
A longer key produces a compile-time error. This limitations could be
eased in the future but we expect longer keys to be unusual.
- Number of keys. The hard limit is 255 keys, but the compile-time perfect-hash
construction may fail (again, a compile-time error) for large or awkward key sets,
and compilation time grows with the number of keys. For compilation speed,
you may use precompiled headers if you have dozens of keys.
- Key contents. Keys must be distinct, non-empty, and must not contain a
backslash, a double quote, or a null byte. Matching is performed against the
raw, unescaped JSON key bytes, so a selector key has to equal the key exactly
as it appears in the document (no JSON escape processing is applied).
This example uses exceptions (see [Disabling exceptions](#disabling-exceptions)
for the error-code style). The `"age"` field is not selected, so it is skipped.
```cpp
using namespace simdjson;
auto json = R"({ "name": "Daniel", "age": 42, "city": "Montreal" })"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
ondemand::object obj = doc.get_object();
using fields = ondemand::key_selector<"name", "city">;
std::string_view name, city;
obj.for_each<fields>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: name = std::string_view(v); break; // "name"
case 1: city = std::string_view(v); break; // "city"
}
});
// name == "Daniel", city == "Montreal"
```
Key selectors work on any object and we do not have
to include all keys. Here the `"verified"` field is skipped.
```cpp
using namespace simdjson;
auto json = R"({ "user": { "id": 1186275104, "screen_name": "ayuu0123", "verified": false } })"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
ondemand::object user = doc["user"].get_object();
using user_fields = ondemand::key_selector<"id", "screen_name">;
uint64_t id = 0;
std::string_view handle;
user.for_each<user_fields>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: id = uint64_t(v); break; // "id"
case 1: handle = std::string_view(v); break; // "screen_name"
}
});
// id == 1186275104, handle == "ayuu0123"
```
A selected value can be any JSON value, including a nested object. Because the
value is consumed inside the callback, you can simply turn it into an
`ondemand::object` and call `for_each` again with another selector. As always
with On Demand, the inner object must be fully consumed before the outer
iteration continues, which the nested `for_each` does for you.
```cpp
using namespace simdjson;
auto json = R"({
"id": 42,
"author": { "name": "Daniel", "handle": "lemire" },
"title": "On Demand"
})"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
ondemand::object obj = doc.get_object();
using post_fields = ondemand::key_selector<"id", "author", "title">;
using author_fields = ondemand::key_selector<"name", "handle">;
uint64_t id = 0;
std::string_view title, author_name, author_handle;
obj.for_each<post_fields>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: id = uint64_t(v); break; // "id"
case 1: { // "author" is itself an object
ondemand::object author = v.get_object();
author.for_each<author_fields>([&](std::size_t j, ondemand::value av) {
switch (j) {
case 0: author_name = std::string_view(av); break; // "name"
case 1: author_handle = std::string_view(av); break; // "handle"
}
});
break;
}
case 2: title = std::string_view(v); break; // "title"
}
});
// id == 42, title == "On Demand", author_name == "Daniel", author_handle == "lemire"
```
## 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
@@ -2585,7 +2912,7 @@ Market: btce Price: 432.89 Volume: 8561.06
*/
```
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value.
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value when using the default strict parsing behavior:
```cpp
ondemand::parser parser;
@@ -2600,7 +2927,7 @@ if (error) {
error = value.get_string().get(view);
if (error) { /* Handle error */ }
else if (view == "Infinity") {
d = std::numeric_limits::infinity();
d = std::numeric_limits<double>::infinity();
}
else { /* Handle wrong value */ }
}
@@ -2608,6 +2935,15 @@ if (error) {
It is also important to note that when dealing an invalid number inside a string, simdjson will report a `NUMBER_ERROR` error if the string begins with a number whereas simdjson
will report an `INCORRECT_TYPE` error otherwise.
When `SIMDJSON_ENABLE_NAN_INF` is enabled, simdjson can parse `"Infinity"`, `"-Infinity"`, and `"NaN"` from a string using `get_double_in_string` without needing extra error handling:
```cpp
ondemand::parser parser;
auto doc = parser.iterate(json);
// Get "change"/"Infinity" key/value pair as a double.
double d = doc["ticker"]["change"].get_double_in_string();
```
The `*_in_string` methods can also be called on a single document instance:
e.g., when your document consist solely of a quoted number.
@@ -2747,6 +3083,53 @@ This code prints the following:
'99999999999999999999999 '
```
Infinity and NaN support
------------------------------
The JSON specification does not support `Infinity` and `NaN` literals. However, some engineers use literal `Infinity` and `NaN` tokens when serializing floating-point values to JSON.
The simdjson library achieves maximum JSON parsing performance by adhering to a strict interpretation of the JSON specification. Therefore strict parsing is enabled by default - `Infinity` and `NaN` literals are not parsed as valid JSON.
Users can opt-in to parsing `Infinity`, `-Infinity`, and `NaN` as `double` values by setting the `SIMDJSON_ENABLE_NAN_INF` flag to `ON` when building simdjson: `cmake -B build -D SIMDJSON_ENABLE_NAN_INF=ON` and setting `SIMDJSON_ENABLE_NAN_INF` to 1 before including `"simdjson.h"`. When enabled, `Infinity`, `-Infinity`, `Inf`, `-Inf`, and `NaN` literals are case-insensitively matched and parsed as `double`:
```cpp
// The SIMDJSON_ENABLE_NAN_INF flag also needs to be set before including simdjson.h
#define SIMDJSON_ENABLE_NAN_INF 1
#include "simdjson.h"
using namespace simdjson;
// ...
ondemand::parser parser;
auto inf_nan_literals = "[Infinity, -Infinity, NaN, inf, -inf, nan]"_padded;
ondemand::document doc = parser.iterate(inf_nan_literals);
// Parse and iterate through the array of literal inf/nan values.
for (ondemand::value val: doc.get_array()) {
ondemand::number num = val.get_number();
ondemand::number_type t = num.get_number_type();
if (t == ondemand::number_type::floating_point_number) {
std::cout << "Parsed floating-point number: " << num.get_double() << std::endl;
} else {
std::cout << "Failed to parse." << std::endl;
}
}
```
produces the following output:
```
Parsed floating-point number: inf
Parsed floating-point number: -inf
Parsed floating-point number: nan
Parsed floating-point number: inf
Parsed floating-point number: -inf
Parsed floating-point number: nan
```
Raw strings from keys
-----------
@@ -2845,7 +3228,7 @@ std::string_view noquote(std::string_view v) { return {v.data()+1, v.find_last_o
The `raw_json_token()` method can enable you to provide fallbacks when parsing fails.
Consider the following example.
Consider the following example under the default strict parsing behavior (`NaN` parsing is disabled):
```cpp
padded_string json = "{\"key\": NaN}"_padded;
@@ -2867,6 +3250,9 @@ Consider the following example.
The NaN is not supported in JSON. However, in the On-Demand API, you can check
the string corresponding to the JSON token and determine how to handle it.
In this particular case, `SIMDJSON_ENABLE_NAN_INF` can be enabled to parse `Infinity` and `NaN` tokens.
However other tokens will still need to be handled via the `raw_json_token()` method.
### Raw JSON string for objects and arrays
If your value is an array or an object, `raw_json_token()` returns effectively a single
@@ -2890,8 +3276,10 @@ simdjson::ondemand::array arr = doc.get_array();
string_view token = arr.raw_json(); // gives you `[1,2,3]`
```
Because `raw_json()` consumes to object or the array, if you want to both have
access to the raw string, and also use the array or object, you should call `reset()`.
Because `raw_json()` consumes the object or the array, if you want both the raw
substring and later field access on the same instance, call `reset()` on that
object or array (as below). To re-parse the whole document from the start,
use `document::rewind()` instead (see [Rewinding](#rewinding)).
```cpp
simdjson::ondemand::parser parser;
@@ -2899,7 +3287,7 @@ simdjson::padded_string docdata = R"({"value":123})"_padded;
simdjson::ondemand::document doc = parser.iterate(docdata);
simdjson::ondemand::object obj = doc.get_object();
string_view token = obj.raw_json(); // gives you `{"value":123}`
obj.reset(); // revise the object
obj.reset(); // re-open the same object for further iteration
uint64_t x = obj["value"]; // gives me 123
```
@@ -3440,7 +3828,7 @@ Performance tips
std::string_view year = data["year"];
std::string_view rating = data["rating"];
```
- You will get better performance if you seek the keys in the order in which they appear in the document. So if processing `{"a":1, "b":2, "c":3}`, do `value1 = data["a"]; value2 = data["b"]; value3 data["c"];` and not `value2 = data["b"]; value1 = data["a"]; value3 data["c"];`. Of course, it is not always possible to know for sure in which order the keys appear.
- You will get better performance if you seek the keys in the order in which they appear in the document. So if processing `{"a":1, "b":2, "c":3}`, do `value1 = data["a"]; value2 = data["b"]; value3 data["c"];` and not `value2 = data["b"]; value1 = data["a"]; value3 data["c"];`. Of course, it is not always possible to know for sure in which order the keys appear. See also [key selectors](#key-selectors), which extract a fixed, known set of fields in a single pass regardless of their order in the document.
+43
View File
@@ -12,6 +12,7 @@ speed and high convenience.
* [Overview: string_builder](#overview--string-builder)
* [Example: string_builder](#example--string-builder)
* [C++26 static reflection](#c--26-static-reflection)
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
+ [Without `string_buffer` instance](#without--string-buffer--instance)
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
+ [Pretty formatted (fractured JSON)](#pretty-formatted-fractured-json)
@@ -260,6 +261,48 @@ automatically. In most cases, it should work automatically:
```
#### Renaming and skipping fields with annotations
**This is experimental: the syntax may change slightly in the future.**
When using C++26 static reflection for automatic serialization (and deserialization),
you can annotate your struct members to rename the corresponding JSON keys or to
exclude fields from the JSON representation.
The syntax is:
```cpp
// rename cppFieldName (in C++) to json_key_name (in JSON)
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
// do not serialize or deserialize this field
[[= simdjson::skip]] int internalState;
```
For example:
```cpp
struct Person {
[[= simdjson::rename<"first_name">]] std::string firstName = "";
[[= simdjson::rename<"last_name">]] std::string lastName = "";
[[= simdjson::skip]] int internalCache = 0;
int age = 0;
};
```
Serialization then produces:
```cpp
Person p{"Alice", "Smith", 999, 30};
std::string json = simdjson::to_json(p);
// json == R"({"first_name":"Alice","last_name":"Smith","age":30})"
// Note: internalCache is omitted entirely.
```
The `skip` annotation also affects deserialization: the field keeps its default value
and any corresponding key in the input JSON is ignored.
### Without `string_buffer` instance
In some instances, you might want to create a string directly from your own data type.
+71
View File
@@ -0,0 +1,71 @@
#ifndef SIMDJSON_ANNOTATIONS_H
#define SIMDJSON_ANNOTATIONS_H
/**
* @file annotations.h
* @brief Provides compile-time annotations for simdjson structures.
* This header defines annotations that can be applied to data members of structures
* to control how they are serialized/deserialized with simdjson.
* This is currently experimental and subject to change (syntax and semantics may evolve).
*/
#if SIMDJSON_STATIC_REFLECTION
#include <meta>
#include <string_view>
namespace simdjson {
// Structural compile-time string — char array avoids the pointer-based
// 'reflect_constant failed' that occurs with const char* / string_view members.
template <size_t N>
struct fixed_string {
char data[N];
consteval fixed_string(const char (&s)[N]) noexcept {
for (size_t i = 0; i < N; ++i) { data[i] = s[i]; }
}
consteval std::string_view view() const noexcept { return {data, N - 1}; }
consteval bool operator==(const fixed_string&) const noexcept = default;
};
// Usage: [[= simdjson::rename<"first_name">]] std::string firstName;
namespace detail {
template <fixed_string Name>
struct rename_t {
static constexpr auto name = Name;
};
} // namespace detail
template <fixed_string Name>
inline constexpr detail::rename_t<Name> rename{};
// Usage: [[= simdjson::skip]] int internalCache;
namespace detail {
struct skip_tag {};
} // namespace detail
inline constexpr detail::skip_tag skip{};
// Returns the JSON key for a reflected data member.
template <auto dm>
consteval const char* get_json_key_name() {
template for (constexpr auto ann :
std::define_static_array(std::meta::annotations_of(dm))) {
constexpr auto ann_type = std::meta::type_of(ann);
if constexpr (std::meta::has_template_arguments(ann_type) &&
std::meta::template_of(ann_type) == ^^detail::rename_t) {
constexpr auto args =
std::define_static_array(std::meta::template_arguments_of(ann_type));
return std::define_static_string([:args[0]:].view());
}
}
return std::define_static_string(std::meta::identifier_of(dm));
}
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_ANNOTATIONS_H
+15 -3
View File
@@ -30,6 +30,10 @@ double from_chars(const char *first, const char* end) noexcept;
#endif
#endif
#ifndef SIMDJSON_ENABLE_NAN_INF
#define SIMDJSON_ENABLE_NAN_INF 0
#endif
} // namespace simdjson
#if defined(__GNUC__)
@@ -45,9 +49,6 @@ double from_chars(const char *first, const char* end) noexcept;
// Align to N-byte boundary
#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
// 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_never_inline __declspec(noinline)
#define simdjson_really_flatten [[msvc::flatten]]
#define simdjson_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_never_inline inline __attribute__((noinline))
#define simdjson_really_flatten [[gnu::flatten]]
#define simdjson_unused __attribute__((unused))
#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
#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
/**
* Windows users need to do some extra work when building
-9
View File
@@ -59,15 +59,6 @@ concept appendable_containers =
details::supports_add<T> || details::supports_append<T> ||
details::supports_insert<T>) && !string_view_keyed_map<T>;
/// Check if T is a key_selector type for efficient JSON field lookup.
/// T must expose a compile-time N (number of keys) and static match_raw that
/// returns [0, N) on hit or N on miss.
template <typename T>
concept key_selector_type = requires {
{ T::size() } -> std::same_as<std::size_t>;
{ T::N } -> std::convertible_to<std::size_t>;
};
/// Insert into the container however possible
template <appendable_containers T, typename... Args>
constexpr decltype(auto) emplace_one(T &vec, Args &&...args) {
-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)} {}
template <typename parser_type>
inline std::remove_pointer_t<parser_type> &auto_parser<parser_type>::parser() noexcept {
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 {
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 convert
} // namespace simdjson
+12 -12
View File
@@ -36,12 +36,12 @@ public:
explicit auto_parser(std::remove_pointer_t<parser_type> &parser, padded_string_view const str) noexcept requires(std::is_pointer_v<parser_type>);
explicit auto_parser(padded_string_view const str) 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 &operator=(auto_parser const &) = delete;
auto_parser(auto_parser &&) noexcept = default;
auto_parser &operator=(auto_parser &&) noexcept = default;
~auto_parser() = default;
auto_parser(auto_parser const &) = delete;
auto_parser &operator=(auto_parser const &) = delete;
~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;
template <typename T>
@@ -75,11 +75,6 @@ struct to_adaptor {
T operator()(simdjson_result<ondemand::value> &val) const noexcept;
auto operator()(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
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 `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:
*
+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.
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
// Read the array index
size_t array_index = 0;
size_t i;
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; 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"
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, i));
// Get the child
auto child = array(tape).at(array_index);
+7 -1
View File
@@ -42,10 +42,16 @@ inline error_code document::allocate(size_t capacity) noexcept {
// 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
// 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);
// a document with only zero-length strings... could have capacity/3 string
// 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]);
tape.reset(new (std::nothrow) uint64_t[tape_capacity]);
if(!(string_buf && tape)) {
+1 -1
View File
@@ -234,7 +234,7 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
// 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(start[svlen-1]) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E)) {
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--;
}
return std::string_view(start, svlen);
+43 -25
View File
@@ -72,7 +72,7 @@ inline element_metrics structure_analyzer::analyze_scalar(const dom::element& el
metrics.complexity = 0;
metrics.child_count = 0;
metrics.can_inline = true;
metrics.recommended_layout = layout_mode::INLINE;
metrics.recommended_layout = layout_mode::single_line;
switch (elem.type()) {
case dom::element_type::STRING: {
@@ -158,16 +158,16 @@ inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
// Decide layout
if (metrics.child_count == 0) {
metrics.recommended_layout = layout_mode::INLINE;
metrics.recommended_layout = layout_mode::single_line;
} else if (metrics.can_inline) {
metrics.recommended_layout = layout_mode::INLINE;
metrics.recommended_layout = layout_mode::single_line;
} else if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
metrics.recommended_layout = layout_mode::TABLE;
metrics.recommended_layout = layout_mode::table;
} else if (current_opts_->enable_compact_multiline &&
max_child_complexity <= current_opts_->max_compact_array_complexity) {
metrics.recommended_layout = layout_mode::COMPACT_MULTILINE;
metrics.recommended_layout = layout_mode::compact_multiline;
} else {
metrics.recommended_layout = layout_mode::EXPANDED;
metrics.recommended_layout = layout_mode::expanded;
}
return metrics;
@@ -206,9 +206,9 @@ inline element_metrics structure_analyzer::analyze_object(const dom::object& obj
// Objects use inline or expanded (no table/compact for objects)
if (metrics.child_count == 0 || metrics.can_inline) {
metrics.recommended_layout = layout_mode::INLINE;
metrics.recommended_layout = layout_mode::single_line;
} else {
metrics.recommended_layout = layout_mode::EXPANDED;
metrics.recommended_layout = layout_mode::expanded;
}
return metrics;
@@ -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 {
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
#endif
}
// Rough estimate: up to 17 significant digits + sign + decimal point + exponent
char buf[32];
@@ -346,28 +356,28 @@ inline layout_mode structure_analyzer::decide_layout(const element_metrics& metr
size_t depth,
size_t available_width) const {
if (metrics.child_count == 0) {
return layout_mode::INLINE;
return layout_mode::single_line;
}
// Check inline feasibility
size_t indent_width = depth * current_opts_->indent_spaces;
if (metrics.can_inline &&
metrics.estimated_inline_len + indent_width <= available_width) {
return layout_mode::INLINE;
return layout_mode::single_line;
}
// Check table mode
if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
return layout_mode::TABLE;
return layout_mode::table;
}
// Check compact multiline
if (current_opts_->enable_compact_multiline &&
metrics.complexity <= current_opts_->max_compact_array_complexity + 1) {
return layout_mode::COMPACT_MULTILINE;
return layout_mode::compact_multiline;
}
return layout_mode::EXPANDED;
return layout_mode::expanded;
}
//
@@ -378,7 +388,7 @@ inline fractured_formatter::fractured_formatter(const fractured_json_options& op
: options_(opts), column_widths_{} {}
simdjson_inline void fractured_formatter::print_newline() {
if (current_layout_ == layout_mode::INLINE) {
if (current_layout_ == layout_mode::single_line) {
return; // No newlines in inline mode
}
one_char('\n');
@@ -386,7 +396,7 @@ simdjson_inline void fractured_formatter::print_newline() {
}
simdjson_inline void fractured_formatter::print_indents(size_t depth) {
if (current_layout_ == layout_mode::INLINE) {
if (current_layout_ == layout_mode::single_line) {
return; // No indentation in inline mode
}
for (size_t i = 0; i < depth * options_.indent_spaces; i++) {
@@ -533,16 +543,16 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
const element_metrics& metrics,
size_t depth) {
switch (metrics.recommended_layout) {
case layout_mode::INLINE:
case layout_mode::single_line:
format_array_inline(arr, metrics);
break;
case layout_mode::COMPACT_MULTILINE:
case layout_mode::compact_multiline:
format_array_compact_multiline(arr, metrics, depth);
break;
case layout_mode::TABLE:
case layout_mode::table:
format_array_as_table(arr, metrics, depth);
break;
case layout_mode::EXPANDED:
case layout_mode::expanded:
default:
format_array_expanded(arr, metrics, depth);
break;
@@ -552,7 +562,7 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
inline void fractured_string_builder::format_array_inline(const dom::array& arr,
const element_metrics& metrics) {
layout_mode prev_layout = format_.get_layout_mode();
format_.set_layout_mode(layout_mode::INLINE);
format_.set_layout_mode(layout_mode::single_line);
format_.start_array();
@@ -613,7 +623,7 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom::
// Format element inline
layout_mode prev_layout = format_.get_layout_mode();
format_.set_layout_mode(layout_mode::INLINE);
format_.set_layout_mode(layout_mode::single_line);
const element_metrics& child_metrics = (child_idx < metrics.children.size())
? metrics.children[child_idx] : element_metrics{};
format_element(elem, child_metrics, depth + 1);
@@ -709,7 +719,7 @@ inline void fractured_string_builder::format_array_as_table(const dom::array& ar
// Write value
if (found) {
layout_mode prev_layout = format_.get_layout_mode();
format_.set_layout_mode(layout_mode::INLINE);
format_.set_layout_mode(layout_mode::single_line);
const element_metrics& value_metrics = (field_idx < row_metrics.children.size())
? row_metrics.children[field_idx] : element_metrics{};
format_element(value, value_metrics, depth + 1);
@@ -778,7 +788,7 @@ inline void fractured_string_builder::format_array_expanded(const dom::array& ar
inline void fractured_string_builder::format_object(const dom::object& obj,
const element_metrics& metrics,
size_t depth) {
if (metrics.recommended_layout == layout_mode::INLINE || metrics.can_inline) {
if (metrics.recommended_layout == layout_mode::single_line || metrics.can_inline) {
format_object_inline(obj, metrics);
} else {
format_object_expanded(obj, metrics, depth);
@@ -788,7 +798,7 @@ inline void fractured_string_builder::format_object(const dom::object& obj,
inline void fractured_string_builder::format_object_inline(const dom::object& obj,
const element_metrics& metrics) {
layout_mode prev_layout = format_.get_layout_mode();
format_.set_layout_mode(layout_mode::INLINE);
format_.set_layout_mode(layout_mode::single_line);
format_.start_object();
@@ -950,6 +960,14 @@ inline size_t fractured_string_builder::measure_value_length(const dom::element&
case dom::element_type::DOUBLE: {
double val;
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];
int len = snprintf(buf, sizeof(buf), "%.17g", val);
return len > 0 ? static_cast<size_t>(len) : 1;
+17
View File
@@ -11,6 +11,7 @@
#include "simdjson/dom/object-inl.h"
#include "simdjson/internal/tape_ref-inl.h"
#include <cmath>
#include <cstring>
namespace simdjson {
@@ -181,6 +182,22 @@ simdjson_inline void base_formatter<formatter>::number(int64_t x) {
template <class formatter>
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];
// Currently, passing the nullptr to the second argument is
// safe because our implementation does not check the second
+91 -1
View File
@@ -22,6 +22,9 @@ namespace atomparsing {
// 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; }
// 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.
// 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);
}
// 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_inline bool is_valid_true_atom(const uint8_t *src) {
return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0;
@@ -69,9 +94,74 @@ simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) {
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
} // unnamed namespace
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ATOMPARSING_H
#endif // SIMDJSON_GENERIC_ATOMPARSING_H
@@ -10,5 +10,6 @@
// Otherwise, amalgamation will fail.
#include "simdjson/concepts.h"
#include "simdjson/dom/fractured_json.h"
#include "simdjson/annotations.h"
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
+286 -100
View File
@@ -4,6 +4,7 @@
#define SIMDJSON_GENERIC_STRING_BUILDER_H
#include "simdjson/generic/builder/json_string_builder.h"
#include "simdjson/concepts.h"
#include "simdjson/annotations.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
@@ -21,23 +22,113 @@ namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
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>
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 end = t.end();
if (it == end) {
b.append_raw("[]");
if (!w.ensure(2)) return;
std::memcpy(w.ptr + w.pos, "[]", 2);
w.pos += 2;
return;
}
b.append('[');
atom(b, *it);
if (!w.ensure(1)) return;
w.ptr[w.pos++] = '[';
atom(w, *it);
++it;
for (; it != end; ++it) {
b.append(',');
atom(b, *it);
if (!w.ensure(1)) return;
w.ptr[w.pos++] = ',';
atom(w, *it);
}
b.append(']');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = ']';
}
template <class T>
@@ -45,37 +136,100 @@ template <class T>
std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> ||
std::is_same_v<T, char>)
constexpr void atom(string_builder &b, const T &t) {
b.escape_and_append_with_quotes(t);
simdjson_really_inline constexpr void atom(writer &w, const T &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>
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()) {
b.append_raw("{}");
if (!w.ensure(2)) return;
std::memcpy(w.ptr + w.pos, "{}", 2);
w.pos += 2;
return;
}
b.append('{');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = '{';
bool first = true;
for (const auto& [key, value] : m) {
if (!first) {
b.append(',');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = ',';
}
first = false;
// Keys must be convertible to string_view per the concept
b.escape_and_append_with_quotes(key);
b.append(':');
atom(b, value);
// Keys must be convertible to string_view per the concept.
std::string_view key_sv(key);
// Guard against 3 + 6 * key_sv.size() wrapping for huge keys, 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(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,
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) {
b.append(t);
simdjson_really_inline constexpr void atom(writer &w, const number_type 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>
@@ -88,61 +242,86 @@ template <class T>
!std::is_same_v<T, std::string_view> &&
!std::is_same_v<T, const char*> &&
!std::is_same_v<T, char> && !require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &t) {
int i = 0;
b.append('{');
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.
bool first = true;
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()))) {
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, t.[:dm:]);
i++;
if constexpr (std::meta::annotations_of_with_type(dm, ^^simdjson::detail::skip_tag).empty()) {
constexpr const char* key_name = simdjson::get_json_key_name<dm>();
constexpr auto first_key = std::define_static_string(
constevalutil::consteval_to_quoted_escaped(key_name) + ":");
constexpr auto rest_key = std::define_static_string(
std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":");
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)) 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;
atom(w, t.[:dm:]);
}
};
b.append('}');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = '}';
}
// Support for optional types (std::optional, etc.)
template <concepts::optional_type 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) {
atom(b, opt.value());
atom(w, opt.value());
} 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.)
template <concepts::smart_pointer 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) {
atom(b, *ptr);
atom(w, *ptr);
} 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
template <typename 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
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
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 size_t enum_str_len = std::char_traits<char>::length(enum_str);
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;
}
};
// 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
// 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
}
@@ -152,28 +331,37 @@ template <concepts::appendable_containers T>
!concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!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>)
constexpr void atom(string_builder &b, const T &container) {
simdjson_really_inline constexpr void atom(writer &w, const T &container) {
if (container.empty()) {
b.append_raw("[]");
if (!w.ensure(2)) return;
std::memcpy(w.ptr + w.pos, "[]", 2);
w.pos += 2;
return;
}
b.append('[');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = '[';
bool first = true;
for (const auto& item : container) {
if (!first) {
b.append(',');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = ',';
}
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>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
void append(string_builder &b, const T &t) {
atom(b, t);
simdjson_inline void append(string_builder &b, const T &t) {
writer w(b);
atom(w, t);
w.sync();
}
template <class T>
@@ -181,20 +369,26 @@ template <class T>
std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> ||
std::is_same_v<T, char>)
void append(string_builder &b, const T &t) {
atom(b, t);
simdjson_inline void append(string_builder &b, const T &t) {
writer w(b);
atom(w, t);
w.sync();
}
template <concepts::optional_type T>
requires(!require_custom_serialization<T>)
void append(string_builder &b, const T &t) {
atom(b, t);
simdjson_inline void append(string_builder &b, const T &t) {
writer w(b);
atom(w, t);
w.sync();
}
template <concepts::smart_pointer T>
requires(!require_custom_serialization<T>)
void append(string_builder &b, const T &t) {
atom(b, t);
simdjson_inline void append(string_builder &b, const T &t) {
writer w(b);
atom(w, t);
w.sync();
}
template <concepts::appendable_containers T>
@@ -202,14 +396,18 @@ template <concepts::appendable_containers T>
!concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!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>)
void append(string_builder &b, const T &t) {
atom(b, t);
simdjson_inline void append(string_builder &b, const T &t) {
writer w(b);
atom(w, t);
w.sync();
}
template <concepts::string_view_keyed_map T>
requires(!require_custom_serialization<T>)
void append(string_builder &b, const T &t) {
atom(b, t);
simdjson_inline void append(string_builder &b, const T &t) {
writer w(b);
atom(w, t);
w.sync();
}
// works for struct
@@ -223,39 +421,19 @@ template <class Z>
!std::is_same_v<Z, std::string_view> &&
!std::is_same_v<Z, const char*> &&
!std::is_same_v<Z, char> && !require_custom_serialization<Z>)
void append(string_builder &b, const Z &z) {
int i = 0;
b.append('{');
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked()))) {
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('}');
simdjson_inline void append(string_builder &b, const Z &z) {
writer w(b);
atom(w, z);
w.sync();
}
// works for container that have begin() and end() iterators
template <class Z>
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
void append(string_builder &b, const Z &z) {
auto it = z.begin();
auto end = z.end();
if (it == end) {
b.append_raw("[]");
return;
}
b.append('[');
atom(b, *it);
++it;
for (; it != end; ++it) {
b.append(',');
atom(b, *it);
}
b.append(']');
simdjson_inline void append(string_builder &b, const Z &z) {
writer w(b);
atom(w, z);
w.sync();
}
template <class Z>
@@ -294,7 +472,9 @@ string_builder& operator<<(string_builder& b, const Z& z) {
template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
void extract_from(string_builder &b, const T &obj) {
b.append('{');
writer w(b);
if (!w.ensure(1)) { w.sync(); return; }
w.ptr[w.pos++] = '{';
bool first = true;
// Iterate through all members of T using reflection
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()));
@@ -304,23 +484,29 @@ void extract_from(string_builder &b, const T &obj) {
// Only serialize this field if it's in our list of requested fields
if constexpr (((FieldNames.view() == key) || ...)) {
if (!first) {
b.append(',');
static constexpr auto first_key = std::define_static_string(
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;
// Serialize the key
static 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:]);
atom(w, obj.[:mem:]);
}
}
};
b.append('}');
if (!w.ensure(1)) { w.sync(); return; }
w.ptr[w.pos++] = '}';
w.sync();
}
template<constevalutil::fixed_string... FieldNames, typename T>
@@ -1,5 +1,7 @@
#include <array>
#include <cmath>
#include <cstring>
#include <limits>
#include <type_traits>
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
@@ -171,6 +173,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
}
return _mm_movemask_epi8(running) != 0;
}
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) {
return simple_needs_escaping(view);
}
size_t i = 0;
__m128i running = __lsx_vreplgr2vr_b(0);
__m128i v34 = __lsx_vreplgr2vr_b(34);
__m128i v92 = __lsx_vreplgr2vr_b(92);
__m128i v32 = __lsx_vreplgr2vr_b(32);
for (; i + 15 < view.size(); i += 16) {
__m128i word =
__lsx_vld(reinterpret_cast<const char *>(view.data() + i), 0);
__m128i chunk = __lsx_vseq_b(word, v34);
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
running = __lsx_vor_v(running, chunk);
}
if (i < view.size()) {
__m128i word = __lsx_vld(
reinterpret_cast<const char *>(view.data() + view.length() - 16), 0);
__m128i chunk = __lsx_vseq_b(word, v34);
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
running = __lsx_vor_v(running, chunk);
}
return !__lsx_bz_v(running);
}
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) {
@@ -561,62 +592,6 @@ simdjson_inline void string_builder::clear() noexcept {
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] = {
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
@@ -636,6 +611,78 @@ static const char decimal_table[200] = {
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
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
template <typename number_type, typename>
@@ -663,87 +710,59 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
}
}
else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) {
// Process 4 digits at a time instead of 2, reducing store operations
// and divisions by approximately half for large numbers.
constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type;
unsigned_type pv = static_cast<unsigned_type>(v);
size_t dc = internal::digit_count(pv);
char *write_pointer = buffer.get() + position + dc - 1;
// Process 4 digits per iteration for large numbers
while (pv >= 10000) {
unsigned_type q = pv / 10000;
unsigned_type r = pv % 10000;
unsigned_type r_hi = r / 100; // High 2 digits of remainder
unsigned_type r_lo = r % 100; // Low 2 digits of remainder
// Write low 2 digits first (rightmost), then high 2 digits
memcpy(write_pointer - 1, &internal::decimal_table[r_lo * 2], 2);
memcpy(write_pointer - 3, &internal::decimal_table[r_hi * 2], 2);
write_pointer -= 4;
pv = q;
}
// Handle remaining 1-4 digits with original 2-digit loop
while (pv >= 100) {
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;
char* end = internal::write_uint_jeaiii(
buffer.get() + position,
static_cast<uint64_t>(static_cast<unsigned_type>(v)));
position = end - buffer.get();
}
}
else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) {
// Same 4-digit batching as unsigned path for signed integers
// 19 digits (max abs value of int64_t) + optional minus sign.
constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type;
bool negative = v < 0;
unsigned_type pv = static_cast<unsigned_type>(v);
if (negative) {
pv = 0 - pv; // the 0 is for Microsoft
}
size_t dc = internal::digit_count(pv);
// by always writing the minus sign, we avoid the branch.
// 0 - pv (rather than -pv) avoids an MSVC unary-minus warning.
unsigned_type pv = negative
? unsigned_type(0) - static_cast<unsigned_type>(v)
: static_cast<unsigned_type>(v);
// Branchless: always write '-', advance only if negative.
buffer.get()[position] = '-';
position += negative ? 1 : 0;
char *write_pointer = buffer.get() + position + dc - 1;
// Process 4 digits per iteration for large numbers
while (pv >= 10000) {
unsigned_type q = pv / 10000;
unsigned_type r = pv % 10000;
unsigned_type r_hi = r / 100;
unsigned_type r_lo = r % 100;
memcpy(write_pointer - 1, &internal::decimal_table[r_lo * 2], 2);
memcpy(write_pointer - 3, &internal::decimal_table[r_hi * 2], 2);
write_pointer -= 4;
pv = q;
}
// Handle remaining 1-4 digits
while (pv >= 100) {
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;
position += negative;
char* end = internal::write_uint_jeaiii(
buffer.get() + position, static_cast<uint64_t>(pv));
position = end - buffer.get();
}
}
else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) {
constexpr size_t max_number_size = 24;
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.
char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr,
double(v));
@@ -755,6 +774,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
simdjson_inline void
string_builder::escape_and_append(std::string_view input) noexcept {
// 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())) {
position += write_string_escaped(input, buffer.get() + position);
}
@@ -763,6 +787,11 @@ string_builder::escape_and_append(std::string_view input) noexcept {
simdjson_inline void
string_builder::escape_and_append_with_quotes(std::string_view input) noexcept {
// 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())) {
buffer.get()[position++] = '"';
position += write_string_escaped(input, buffer.get() + position);
@@ -794,7 +823,9 @@ simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
#endif
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);
}
@@ -813,6 +844,14 @@ simdjson_inline void string_builder::append_raw(const char *str,
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
// Support for optional types (std::optional, etc.)
template <concepts::optional_type T>
@@ -185,6 +185,15 @@ requires (!std::is_convertible<R, std::string_view>::value && !concepts::optiona
* There is no UTF-8 validation.
*/
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
/**
* Creates an std::string from the written JSON buffer.
@@ -236,6 +245,26 @@ requires (!std::is_convertible<R, std::string_view>::value && !concepts::optiona
*/
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:
/**
* Returns true if we can write at least upcoming_bytes bytes.
@@ -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 {
if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; }
// 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] );
if (!structural_indexes) { _capacity = 0; return MEMALLOC; }
structural_indexes[0] = 0;
+62 -4
View File
@@ -4,6 +4,7 @@
#define SIMDJSON_GENERIC_NUMBERPARSING_H
#include "simdjson/generic/base.h"
#include "simdjson/generic/jsoncharutils.h"
#include "simdjson/generic/atomparsing.h"
#include "simdjson/internal/numberparsing_tables.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
@@ -299,6 +300,24 @@ simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative,
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
// 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
@@ -478,7 +497,7 @@ simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t d
} // unnamed namespace
/** @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)) {
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.
// Optimization note: size_t is expected to be unsigned.
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)
@@ -1031,7 +1064,17 @@ simdjson_unused simdjson_inline simdjson_result<double> parse_double(const uint8
bool leading_zero = (i == 0);
while (parse_digit(*p, i)) { p++; }
// 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; }
//
@@ -1249,7 +1292,22 @@ simdjson_unused simdjson_inline simdjson_result<double> parse_double_in_string(c
bool leading_zero = (i == 0);
while (parse_digit(*p, i)) { p++; }
// 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; }
//
@@ -23,6 +23,7 @@
#include "simdjson/generic/ondemand/key_selector.h"
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/ranges.h"
#include "simdjson/generic/ondemand/serialization.h"
// Deserialization for standard types
@@ -40,7 +41,7 @@
#include "simdjson/generic/ondemand/logger-inl.h"
#include "simdjson/generic/ondemand/object-inl.h"
#include "simdjson/generic/ondemand/object_iterator-inl.h"
#include "simdjson/generic/ondemand/key_selector_iterator.h"
#include "simdjson/generic/ondemand/ranges-inl.h"
#include "simdjson/generic/ondemand/parser-inl.h"
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
#include "simdjson/generic/ondemand/token_iterator-inl.h"
+36 -57
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.
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
// Read the array index
size_t array_index = 0;
size_t i;
for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; 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"
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, i));
// Get the child
auto child = at(array_index);
// If there is an error, it ends here
@@ -170,46 +158,34 @@ inline simdjson_result<value> array::at_path(std::string_view json_path) noexcep
return at_pointer(json_pointer);
}
inline simdjson_result<std::vector<value>> array::at_path_with_wildcard(std::string_view json_path) noexcept {
std::vector<value> result;
#if SIMDJSON_SUPPORTS_CONCEPTS
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);
std::string_view key = result_pair.first;
std::string_view remaining_path = result_pair.second;
// Wildcard case
if(key=="*"){
for(auto element: *this){
if(element.error()){
return element.error();
}
if(remaining_path.empty()){
// Use value_unsafe() because we've already checked for errors above.
// The 'element' is a simdjson_result<value> wrapper, and we need to extract
// the underlying value. value_unsafe() is safe here because error() returned false.
result.push_back(std::move(element).value_unsafe());
}else{
auto nested_result = element.at_path_with_wildcard(remaining_path);
if(nested_result.error()){
return nested_result.error();
}
// Same logic as above.
std::vector<value> nested_matches = std::move(nested_result).value_unsafe();
result.insert(result.end(),
std::make_move_iterator(nested_matches.begin()),
std::make_move_iterator(nested_matches.end()));
if (key=="*"){
for(auto element: *this) {
value val;
SIMDJSON_TRY(element.get(val));
if (remaining_path.empty()) {
callback(val);
} else {
error_code err = element.for_each_at_path_with_wildcard(remaining_path, callback);
if(err) { return err; }
}
}
return result;
}else{
return SUCCESS;
} else {
// Specific index case in which we access the element at the given index
size_t idx=0;
size_t idx = 0;
for(char c:key){
for (char c : key) {
if(c < '0' || c > '9'){
return INVALID_JSON_POINTER;
}
@@ -217,16 +193,13 @@ inline simdjson_result<std::vector<value>> array::at_path_with_wildcard(std::str
}
auto element = at(idx);
if(element.error()){
return element.error();
}
if(remaining_path.empty()){
result.push_back(std::move(element).value_unsafe());
return result;
}else{
return element.at_path_with_wildcard(remaining_path);
value val;
SIMDJSON_TRY(element.get(val));
if (remaining_path.empty()){
callback(val);
return SUCCESS;
} else {
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(); }
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(); }
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 {
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;
/**
* 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.
*
* @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
@@ -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_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<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;
#if SIMDJSON_SUPPORTS_CONCEPTS
// TODO: move this code into object-inl.h
+7 -4
View File
@@ -39,10 +39,13 @@ class raw_json_string;
class token_iterator;
class value;
class value_iterator;
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector> class selector_iterator;
template <typename Selector> class selector_range;
#endif
#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 SIMDJSON_IMPLEMENTATION
@@ -10,9 +10,13 @@
// Otherwise, amalgamation will fail.
#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY
#include "simdjson/implementation.h"
#include "simdjson/base.h"
#include "simdjson/common_defs.h"
#include "simdjson/constevalutil.h"
#include "simdjson/padded_string.h"
#include "simdjson/padded_string_view.h"
#include "simdjson/internal/dom_parser_implementation.h"
#include "simdjson/jsonpathutil.h"
#include "simdjson/annotations.h"
#endif // SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H
@@ -365,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 {
rewind(); // Rewind the document each time at_path_with_wildcard is called
#if SIMDJSON_SUPPORTS_CONCEPTS
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()) {
return INVALID_JSON_POINTER;
}
@@ -374,9 +380,9 @@ simdjson_inline simdjson_result<std::vector<value>> document::at_path_with_wildc
SIMDJSON_TRY(type().get(t));
switch (t) {
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:
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:
return INVALID_JSON_POINTER;
}
@@ -713,9 +719,15 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
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(); }
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
@@ -826,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<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<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 document_reference::operator document&() const noexcept { return *doc; }
#if SIMDJSON_SUPPORTS_CONCEPTS && SIMDJSON_STATIC_REFLECTION
@@ -1091,11 +1109,17 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
}
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()) {
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
template<constevalutil::fixed_string... FieldNames, typename T>
+32 -11
View File
@@ -744,21 +744,24 @@ public:
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.
*
* This method materializes all matching values into a vector.
* The document will be consumed after this call.
*
* @param json_path JSONPath expression with wildcards
* @return Vector of values matching the wildcard pattern, or:
* - INVALID_JSON_POINTER if the JSONPath cannot be parsed
* - 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
* @param callback Function called for each matching value
* @return error_code indicating success or failure
*/
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
@@ -979,7 +982,13 @@ public:
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_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:
document *doc{nullptr};
@@ -1065,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_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
template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
@@ -1150,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_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
template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
@@ -398,7 +398,7 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
// 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(start[svlen-1]) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E)) {
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--;
}
return std::string_view(start, svlen);
@@ -24,6 +24,9 @@ simdjson_inline json_iterator::json_iterator(json_iterator &&other) noexcept
_depth{other._depth},
_root{other._root},
_streaming{other._streaming}
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
, _allow_incomplete_json{other._allow_incomplete_json}
#endif
{
other.parser = nullptr;
}
@@ -35,6 +38,9 @@ simdjson_inline json_iterator &json_iterator::operator=(json_iterator &&other) n
_depth = other._depth;
_root = other._root;
_streaming = other._streaming;
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
_allow_incomplete_json = other._allow_incomplete_json;
#endif
other.parser = nullptr;
return *this;
}
@@ -61,7 +67,8 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse
_string_buf_loc{parser->string_buf.get()},
_depth{1},
_root{parser->implementation->structural_indexes.get()},
_streaming{streaming}
_streaming{streaming},
_allow_incomplete_json{true}
{
logger::log_headers();
@@ -193,6 +200,17 @@ simdjson_inline bool json_iterator::streaming() const noexcept {
return _streaming;
}
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
simdjson_inline bool json_iterator::allow_incomplete_json() const noexcept {
return _allow_incomplete_json;
}
simdjson_inline size_t json_iterator::remaining_input_length(const uint8_t *json) const noexcept {
const uint8_t *end = token.buf + parser->_document_len;
return json < end ? size_t(end - json) : 0;
}
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
simdjson_inline token_position json_iterator::root_position() const noexcept {
return _root;
}
@@ -450,4 +468,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::json_iterator
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
@@ -60,6 +60,9 @@ protected:
* value of this attribute.
*/
bool _streaming{false};
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
bool _allow_incomplete_json{false};
#endif
public:
simdjson_inline json_iterator() noexcept = default;
@@ -84,6 +87,10 @@ public:
* start_root_array() and start_root_object().
*/
simdjson_inline bool streaming() const noexcept;
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
simdjson_inline bool allow_incomplete_json() const noexcept;
simdjson_inline size_t remaining_input_length(const uint8_t *json) const noexcept;
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
/**
* Get the root value iterator
@@ -335,4 +342,4 @@ public:
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
File diff suppressed because it is too large Load Diff
@@ -1,131 +0,0 @@
#ifndef SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_ITERATOR_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_ITERATOR_H
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/ondemand/key_selector.h"
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/value.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <array>
#include <cstddef>
#include <utility>
#if SIMDJSON_SUPPORTS_CONCEPTS
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
/**
* Forward iterator over selector matches in an object.
*
* Walks the JSON object once, yielding each (selector_index, value) pair whose
* key matches one of the selector's keys, in JSON order. Duplicate matches of
* the same key are silently skipped; iteration ends when every selector key
* has matched OR the object ends.
*
* for (auto [i, val] : obj.select<sel_t>()) {
* switch (i) { case 0: ...; case 1: ...; }
* }
*/
template <typename Selector>
class selector_iterator {
public:
// Yield ondemand::value directly (not simdjson_result<value>); the caller
// uses iterator state (error()) to check for errors after iteration.
using value_type = std::pair<std::size_t, value>;
struct end_sentinel {};
simdjson_inline selector_iterator() noexcept = default;
simdjson_inline explicit selector_iterator(object obj) noexcept
: obj_{std::move(obj)} {
auto begin_res = obj_.begin();
if (begin_res.error()) { done_ = true; last_error_ = begin_res.error(); return; }
it_ = begin_res.value();
advance();
}
simdjson_inline value_type operator*() noexcept {
return { current_index_, std::move(current_value_) };
}
simdjson_inline selector_iterator& operator++() noexcept { advance(); return *this; }
simdjson_inline bool operator==(end_sentinel) const noexcept { return done_; }
simdjson_inline bool operator!=(end_sentinel) const noexcept { return !done_; }
/** Error code set if iteration was terminated by an error. */
simdjson_inline error_code error() const noexcept { return last_error_; }
/** Number of unique selector-key matches produced so far. */
simdjson_inline std::size_t matched_count() const noexcept { return matched_; }
private:
object obj_{};
object_iterator it_{};
std::array<bool, Selector::size()> seen_{};
std::size_t matched_{0};
std::size_t current_index_{Selector::size()};
value current_value_{};
error_code last_error_{SUCCESS};
bool done_{false};
bool primed_{false};
simdjson_inline void advance() noexcept {
if (done_) return;
if (primed_) { ++it_; primed_ = false; }
if (matched_ >= Selector::size()) { done_ = true; return; }
object_iterator end{};
while (it_ != end) {
auto f_res = *it_;
if (f_res.error()) { last_error_ = f_res.error(); done_ = true; return; }
field f = f_res.value_unsafe();
std::size_t idx = Selector::match_raw(f.key());
if (idx < Selector::size() && !seen_[idx]) {
seen_[idx] = true;
++matched_;
current_index_ = idx;
current_value_ = std::move(f.value());
primed_ = true;
return;
}
++it_;
}
done_ = true;
}
};
/**
* Range adapter returned by object::select<Selector>(). Satisfies the range-for
* loop requirements (begin() / end()).
*/
template <typename Selector>
class selector_range {
public:
simdjson_inline explicit selector_range(object obj) noexcept
: obj_{std::move(obj)} {}
simdjson_inline selector_iterator<Selector> begin() noexcept {
return selector_iterator<Selector>{std::move(obj_)};
}
simdjson_inline typename selector_iterator<Selector>::end_sentinel end() const noexcept {
return {};
}
private:
object obj_;
};
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_ITERATOR_H
+60 -45
View File
@@ -64,13 +64,45 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector>
simdjson_inline selector_range<Selector> object::select() & noexcept {
return selector_range<Selector>{*this};
}
template <typename Selector>
simdjson_inline selector_range<Selector> object::select() && noexcept {
return selector_range<Selector>{std::move(*this)};
template <typename Selector, typename Func>
simdjson_flatten simdjson_inline for_each_result object::for_each(Func&& on_match) noexcept {
// Single pass driven directly by the value_iterator, mirroring
// find_field_unordered_raw + value(iter.child()). Compared to walking via
// object_iterator/field, this avoids constructing a simdjson_result<field> and
// a field (key + value) for every field -- and the development-check bookkeeping
// in object_iterator -- building a value only for the fields that actually match.
// We operate on a copy of the iterator, as object::begin() would.
value_iterator it = iter;
std::array<bool, Selector::size()> seen{};
std::size_t matched = 0;
while (it.is_open()) {
raw_json_string key;
error_code error = it.field_key().get(key);
if (error) { it.abandon(); return {error, matched}; }
// Advance past the ':' and descend onto the value.
if ((error = it.field_value())) { it.abandon(); return {error, matched}; }
std::size_t idx = Selector::match_raw(key);
if (idx < Selector::size() && !seen[idx]) {
seen[idx] = true;
value matched_value(it.child());
// The callback may return either void or an error_code. When it returns an
// error_code, we stop at the first non-SUCCESS result and propagate it so
// the caller can surface value-parse errors (for example, a type mismatch
// on a matched field). A void-returning callback is responsible for
// handling its own errors.
if constexpr (std::is_same_v<decltype(on_match(idx, matched_value)), error_code>) {
if ((error = on_match(idx, matched_value))) { return {error, matched}; }
} else {
on_match(idx, matched_value);
}
if (++matched >= Selector::size()) { break; }
}
// Skip the value (a no-op if the callback consumed it) and step to the next
// field; has_next_field() ends the container on '}', which closes the loop.
if ((error = it.skip_child())) { it.abandon(); return {error, matched}; }
if ((error = it.has_next_field().error())) { return {error, matched}; }
}
return {SUCCESS, matched};
}
#endif
@@ -188,9 +220,13 @@ inline simdjson_result<value> object::at_path(std::string_view json_path) noexce
return at_pointer(json_pointer);
}
inline simdjson_result<std::vector<value>> object::at_path_with_wildcard(std::string_view json_path) noexcept {
std::vector<value> result;
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Func>
requires std::invocable<Func, value>
#else
template <typename Func>
#endif
inline error_code object::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);
std::string_view key = result_pair.first;
std::string_view remaining_path = result_pair.second;
@@ -200,34 +236,22 @@ inline simdjson_result<std::vector<value>> object::at_path_with_wildcard(std::st
for (auto field : *this) {
value val;
SIMDJSON_TRY(field.value().get(val));
if (remaining_path.empty()) {
result.push_back(std::move(val));
callback(val);
} else {
auto nested_result = val.at_path_with_wildcard(remaining_path);
if (nested_result.error()) {
return nested_result.error();
}
// Extract and append all nested matches to our result
std::vector<value> nested_vec;
SIMDJSON_TRY(std::move(nested_result).get(nested_vec));
result.insert(result.end(),
std::make_move_iterator(nested_vec.begin()),
std::make_move_iterator(nested_vec.end()));
SIMDJSON_TRY(val.for_each_at_path_with_wildcard(remaining_path, callback));
}
}
return result;
return SUCCESS;
} else {
value val;
SIMDJSON_TRY(find_field(key).get(val));
if (remaining_path.empty()) {
result.push_back(std::move(val));
return result;
callback(val);
return SUCCESS;
} else {
return val.at_path_with_wildcard(remaining_path);
return val.for_each_at_path_with_wildcard(remaining_path, callback);
}
}
}
@@ -345,21 +369,6 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(key);
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector>
simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::select() & noexcept {
// On error, construct a range over a default (invalid) object; iteration will
// yield the stored error at first dereference via the underlying iterator path.
return first.template select<Selector>();
}
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector>
simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::select() && noexcept {
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).template select<Selector>();
}
#endif
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
@@ -374,9 +383,15 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
return first.at_path(json_path);
}
simdjson_inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::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::object>::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
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));
}
inline simdjson_result<bool> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::reset() noexcept {
+61 -23
View File
@@ -16,6 +16,22 @@ namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
#if SIMDJSON_SUPPORTS_CONCEPTS
/**
* Result of object::for_each: the first error encountered (SUCCESS if none) and
* the number of distinct selector keys that matched during the walk. A
* matched_count equal to Selector::size() means every selected key was present
* in the object. Implicitly converts to error_code so existing callers that only
* care about the error (including SIMDJSON_TRY and the test ASSERT_* macros) keep
* working unchanged.
*/
struct for_each_result {
error_code error{SUCCESS};
std::size_t matched_count{0};
constexpr operator error_code() const noexcept { return error; }
};
#endif
/**
* A forward-only JSON object field iterator.
*/
@@ -125,23 +141,37 @@ public:
#if SIMDJSON_SUPPORTS_CONCEPTS
/**
* Iterate over this object, yielding every field whose key is in the compile-time
* key_selector Selector. Yields std::pair<std::size_t, simdjson_result<value>>
* (selector_index, value) in JSON order. Duplicate keys in the JSON are skipped
* (first occurrence wins). Iteration ends when all Selector::size() keys have
* matched or the object ends.
* Walk this object once and invoke on_match(selector_index, value) for each
* field whose key is in the compile-time key_selector Selector, in JSON order
* (first occurrence of a duplicate key wins). Iteration stops once all
* Selector::size() keys have matched or the object ends. The value is consumed
* in place, so this is a low-overhead way to extract a known set of fields
* regardless of their order in the JSON.
*
* Usage:
* using sel_t = decltype(make_key_selector<"id", "text", "user">());
* for (auto [i, v] : obj.select<sel_t>()) { ... }
* using sel_t = ondemand::key_selector<"id", "text", "user">;
* obj.for_each<sel_t>([&](std::size_t i, ondemand::value v) {
* switch (i) { case 0: ...; case 1: ...; }
* });
*
* @tparam Selector A stateless key_selector type (see key_selector.h).
* Limitations (see key_selector): each key must be at most 31 characters long,
* and the number of keys should be moderate (hard limit 255; a handful is
* best, as the compile-time perfect hash may fail or slow compilation for
* large key sets). They keys must be distinct, non-empty, and free of backslash, double-quote and
* null bytes.
*
* The callback may return either void or an error_code. When it returns an
* error_code, the walk stops at the first non-SUCCESS result and that error is
* returned, which lets the callback surface value-parse errors.
*
* @returns a for_each_result holding the first error encountered while walking
* the object (including any error returned by the callback, SUCCESS if
* none) and the number of distinct selector keys that matched. The
* result converts implicitly to error_code, so callers that only need
* the error can ignore the count.
*/
template <typename Selector>
simdjson_inline selector_range<Selector> select() & noexcept;
/** @overload */
template <typename Selector>
simdjson_inline selector_range<Selector> select() && noexcept;
template <typename Selector, typename Func>
simdjson_inline for_each_result for_each(Func&& on_match) noexcept;
#endif
/**
@@ -194,13 +224,21 @@ public:
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 object fields.
*
* @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;
/**
* Reset the iterator so that we are pointing back at the
@@ -346,17 +384,17 @@ public:
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field(std::string_view key) && noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) & noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) && noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector> select() & noexcept;
template <typename Selector>
simdjson_inline SIMDJSON_IMPLEMENTATION::ondemand::selector_range<Selector> select() && noexcept;
#endif
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) & noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) && 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<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;
inline simdjson_result<bool> reset() noexcept;
inline simdjson_result<bool> is_empty() noexcept;
inline simdjson_result<size_t> count_fields() & noexcept;
@@ -27,7 +27,10 @@ simdjson_warn_unused simdjson_inline error_code parser::allocate(size_t new_capa
// string_capacity copied from document::allocate
_capacity = 0;
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
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]);
#if SIMDJSON_DEVELOPMENT_CHECKS
start_positions.reset(new (std::nothrow) token_position[new_max_depth]);
@@ -52,6 +55,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(p
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
json.remove_utf8_bom();
_document_len = json.length();
// Allocate if needed
if (capacity() < json.length() || !string_buf) {
@@ -68,6 +72,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate_a
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
json.remove_utf8_bom();
_document_len = json.length();
// Allocate if needed
if (capacity() < json.length() || !string_buf) {
@@ -272,7 +277,7 @@ simdjson_inline simdjson_warn_unused ondemand::parser& parser::get_parser() {
return *parser::get_parser_instance();
}
simdjson_inline bool release_parser() {
simdjson_inline bool parser::release_parser() {
auto &parser_instance = parser::get_threadlocal_parser_if_exists();
if (parser_instance) {
parser_instance.reset();
@@ -309,4 +314,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::parser>::simd
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
+1 -2
View File
@@ -422,8 +422,6 @@ public:
static simdjson_inline bool release_parser();
private:
friend bool release_parser();
friend ondemand::parser& get_parser();
/** Get the thread-local parser instance, allocates it if needed */
static simdjson_inline simdjson_warn_unused std::unique_ptr<ondemand::parser>& get_parser_instance();
/** Get the thread-local parser instance, it might be null */
@@ -433,6 +431,7 @@ private:
size_t _capacity{0};
size_t _max_capacity;
size_t _max_depth{DEFAULT_MAX_DEPTH};
size_t _document_len{0};
std::unique_ptr<uint8_t[]> string_buf{};
#if SIMDJSON_DEVELOPMENT_CHECKS
@@ -0,0 +1,138 @@
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/ondemand/ranges.h"
#include "simdjson/generic/ondemand/array-inl.h"
#include "simdjson/generic/ondemand/array_iterator-inl.h"
#include "simdjson/generic/ondemand/object-inl.h"
#include "simdjson/generic/ondemand/object_iterator-inl.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_SUPPORTS_RANGES
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
//
// array_range_iterator
//
simdjson_inline array_range_iterator::array_range_iterator(array_iterator iter) noexcept
: iter_{iter} {}
simdjson_inline simdjson_result<value> array_range_iterator::operator*() const noexcept {
return *iter_;
}
simdjson_inline array_range_iterator& array_range_iterator::operator++() noexcept {
++iter_;
return *this;
}
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
simdjson_inline void array_range_iterator::operator++(int) noexcept {
++*this;
}
SIMDJSON_POP_DISABLE_WARNINGS
//
// array_range
//
simdjson_inline array_range::array_range(array& arr) noexcept {
auto b = arr.begin();
if (b.error()) { error_ = b.error(); return; }
begin_ = b.value_unsafe();
end_ = arr.end().value_unsafe();
}
simdjson_inline array_range_iterator array_range::begin() noexcept {
return array_range_iterator(begin_);
}
simdjson_inline array_range_iterator array_range::end() noexcept {
return array_range_iterator(end_);
}
//
// object_range_iterator
//
simdjson_inline object_range_iterator::object_range_iterator(object_iterator iter) noexcept
: iter_{iter} {}
simdjson_inline simdjson_result<field> object_range_iterator::operator*() const noexcept {
return *iter_;
}
simdjson_inline object_range_iterator& object_range_iterator::operator++() noexcept {
++iter_;
return *this;
}
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
simdjson_inline void object_range_iterator::operator++(int) noexcept {
++*this;
}
SIMDJSON_POP_DISABLE_WARNINGS
//
// object_range
//
simdjson_inline object_range::object_range(object& obj) noexcept {
auto b = obj.begin();
if (b.error()) { error_ = b.error(); return; }
begin_ = b.value_unsafe();
end_ = obj.end().value_unsafe();
}
simdjson_inline object_range_iterator object_range::begin() noexcept {
return object_range_iterator(begin_);
}
simdjson_inline object_range_iterator object_range::end() noexcept {
return object_range_iterator(end_);
}
//
// Free functions
//
simdjson_inline array_range get_range(array& arr) noexcept {
return array_range(arr);
}
simdjson_inline object_range get_key_value_range(object& obj) noexcept {
return object_range(obj);
}
#if SIMDJSON_EXCEPTIONS
simdjson_inline array_range get_range(simdjson_result<array> result) {
return array_range(result.value());
}
simdjson_inline object_range get_key_value_range(simdjson_result<object> result) {
return object_range(result.value());
}
#endif // SIMDJSON_EXCEPTIONS
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
// Verify the range wrapper types satisfy the expected C++20 concepts.
static_assert(std::input_iterator<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range_iterator>);
static_assert(std::input_iterator<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range_iterator>);
static_assert(std::ranges::input_range<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range>);
static_assert(std::ranges::input_range<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range>);
static_assert(std::ranges::view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range>);
static_assert(std::ranges::view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range>);
#endif // SIMDJSON_SUPPORTS_RANGES
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
+182
View File
@@ -0,0 +1,182 @@
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_H
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/ondemand/array.h"
#include "simdjson/generic/ondemand/array_iterator.h"
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/value.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_SUPPORTS_RANGES
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
/**
* A ranges-compatible iterator adapter for JSON arrays.
*
* Wraps array_iterator to satisfy std::input_iterator by providing:
* - const operator* (via mutable internal state)
* - post-increment operator
* - iterator_concept tag
*
* The mutable approach is standard for single-pass input iterators that
* read from external sources (similar to std::istream_iterator).
*/
class array_range_iterator {
public:
using iterator_concept = std::input_iterator_tag;
using value_type = simdjson_result<value>;
using reference = simdjson_result<value>;
using difference_type = std::ptrdiff_t;
simdjson_inline array_range_iterator() noexcept = default;
simdjson_inline explicit array_range_iterator(array_iterator iter) noexcept;
/**
* Get the current element. Const-qualified for std::indirectly_readable;
* internally delegates to the mutable wrapped iterator.
*/
simdjson_inline simdjson_result<value> operator*() const noexcept;
simdjson_inline array_range_iterator& operator++() noexcept;
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
simdjson_inline void operator++(int) noexcept;
SIMDJSON_POP_DISABLE_WARNINGS
/**
* Comparison delegates to array_iterator::operator==, which checks
* whether the underlying parser has finished the array (depth-based).
*/
simdjson_inline friend bool operator==(const array_range_iterator& a,
const array_range_iterator& b) noexcept {
return a.iter_ == b.iter_;
}
private:
mutable array_iterator iter_{};
};
/**
* A std::ranges::view over a JSON array.
*
* Wraps an ondemand::array and exposes begin()/end() that return
* array_range_iterator (satisfying std::input_iterator), enabling
* use with std::views::transform and other range adaptors.
*
* If the array's begin() returns an error (only possible under
* SIMDJSON_DEVELOPMENT_CHECKS), the range will be empty and error()
* will return the error code.
*
* Usage:
* ondemand::parser parser;
* auto doc = parser.iterate(json);
* auto arr = doc.get_array().value();
* for (auto elem : ondemand::get_range(arr)) { ... }
*/
class array_range {
public:
simdjson_inline array_range() noexcept = default;
simdjson_inline explicit array_range(array& arr) noexcept;
simdjson_inline array_range_iterator begin() noexcept;
simdjson_inline array_range_iterator end() noexcept;
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
simdjson_inline error_code error() const noexcept { return error_; }
private:
array_iterator begin_{};
array_iterator end_{};
error_code error_{SUCCESS};
};
/**
* A ranges-compatible iterator adapter for JSON objects.
*
* Wraps object_iterator to satisfy std::input_iterator, yielding
* simdjson_result<field> elements (key-value pairs).
*/
class object_range_iterator {
public:
using iterator_concept = std::input_iterator_tag;
using value_type = simdjson_result<field>;
using reference = simdjson_result<field>;
using difference_type = std::ptrdiff_t;
simdjson_inline object_range_iterator() noexcept = default;
simdjson_inline explicit object_range_iterator(object_iterator iter) noexcept;
simdjson_inline simdjson_result<field> operator*() const noexcept;
simdjson_inline object_range_iterator& operator++() noexcept;
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
simdjson_inline void operator++(int) noexcept;
SIMDJSON_POP_DISABLE_WARNINGS
simdjson_inline friend bool operator==(const object_range_iterator& a,
const object_range_iterator& b) noexcept {
return a.iter_ == b.iter_;
}
private:
mutable object_iterator iter_{};
};
/**
* A std::ranges::view over a JSON object.
*
* Wraps an ondemand::object and exposes begin()/end() that return
* object_range_iterator, enabling use with range adaptors.
*
* If the object's begin() returns an error, the range will be empty
* and error() will return the error code.
*/
class object_range {
public:
simdjson_inline object_range() noexcept = default;
simdjson_inline explicit object_range(object& obj) noexcept;
simdjson_inline object_range_iterator begin() noexcept;
simdjson_inline object_range_iterator end() noexcept;
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
simdjson_inline error_code error() const noexcept { return error_; }
private:
object_iterator begin_{};
object_iterator end_{};
error_code error_{SUCCESS};
};
/** Get a std::ranges compatible view over a JSON array. */
simdjson_inline array_range get_range(array& arr) noexcept;
/** Get a std::ranges compatible view over a JSON object (key-value pairs). */
simdjson_inline object_range get_key_value_range(object& obj) noexcept;
#if SIMDJSON_EXCEPTIONS
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
simdjson_inline array_range get_range(simdjson_result<array> result);
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
simdjson_inline object_range get_key_value_range(simdjson_result<object> result);
#endif // SIMDJSON_EXCEPTIONS
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
namespace std {
namespace ranges {
template<>
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range> = true;
template<>
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range> = true;
} // namespace ranges
} // namespace std
#endif // SIMDJSON_SUPPORTS_RANGES
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_H
@@ -6,12 +6,14 @@
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/array.h"
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/annotations.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <concepts>
#include <limits>
#if SIMDJSON_STATIC_REFLECTION
#include <meta>
#include <vector>
// #include <static_reflection> // for std::define_static_string - header not available yet
#endif
@@ -267,6 +269,135 @@ constexpr bool user_defined_type = (std::is_class_v<T>
!concepts::appendable_containers<T>);
#if defined(SIMDJSON_USE_KEY_SELECTOR_REFLECTION) && SIMDJSON_USE_KEY_SELECTOR_REFLECTION
// Experimental: deserialize a reflected struct using a compile-time key_selector
// and a single object::for_each pass (perfect-hash key matching) instead of one
// obj[key] lookup per member. Enable with -DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1.
namespace key_selector_reflection_detail {
// A member participates if it is public, non-const, and not annotated to skip.
consteval bool is_eligible_member(std::meta::info mem) {
return !std::meta::is_const(mem) && std::meta::is_public(mem)
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty();
}
// JSON key name for `mem`, as a constevalutil::fixed_string usable as an NTTP.
template <auto mem>
consteval auto member_key_fixed_string() {
constexpr std::string_view key{ simdjson::get_json_key_name<mem>() };
char buffer[key.size() + 1] = {};
for (std::size_t i = 0; i < key.size(); ++i) { buffer[i] = key[i]; }
return constevalutil::fixed_string<key.size() + 1>(buffer);
}
// key_selector template arguments (one fixed_string per eligible member), in
// declaration order.
template <typename T>
consteval std::vector<std::meta::info> selector_key_args() {
std::vector<std::meta::info> args;
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (is_eligible_member(mem)) {
args.push_back(std::meta::reflect_constant(member_key_fixed_string<mem>()));
}
}
return args;
}
// key_selector whose keys are exactly T's eligible members (index i <-> i-th).
template <typename T>
using selector_for = typename [: std::meta::substitute(
^^SIMDJSON_IMPLEMENTATION::ondemand::key_selector, selector_key_args<T>()) :];
// True when none of T's eligible members is an optional type, i.e. every member
// is required. In that case presence can be checked with a single match count
// instead of a per-member "seen" array.
template <typename T>
consteval bool all_eligible_members_required() {
bool all_required = true;
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (is_eligible_member(mem)) {
if constexpr (concepts::optional_type<typename [: std::meta::type_of(mem) :]>) {
all_required = false;
}
}
}
return all_required;
}
} // namespace key_selector_reflection_detail
template <typename T, typename ValT>
requires(user_defined_type<T> && std::is_class_v<T>)
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
SIMDJSON_IMPLEMENTATION::ondemand::object obj;
if constexpr (std::is_same_v<std::remove_cvref_t<ValT>, SIMDJSON_IMPLEMENTATION::ondemand::object>) {
obj = val;
} else {
SIMDJSON_TRY(val.get_object().get(obj));
}
using selector = key_selector_reflection_detail::selector_for<T>;
if constexpr (key_selector_reflection_detail::all_eligible_members_required<T>()) {
// Fast path: every member is required. A single for_each pass parses each
// matched field; the returned match count then tells us whether every member
// was present (matched_count == selector::size()) without a per-member "seen"
// array. A value-parse error (e.g. a type mismatch) is propagated by for_each.
auto walk = obj.template for_each<selector>(
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
std::size_t counter = 0;
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
if (matched_index == counter) { return field_value.get(out.[:mem:]); }
++counter;
}
}
return SUCCESS;
});
if (walk.error) { return walk.error; }
// A missing required member shows up as a short match count and is reported as
// NO_SUCH_FIELD, mirroring the ordered obj[key] path.
if (walk.matched_count != selector::size()) { return NO_SUCH_FIELD; }
return SUCCESS;
} else {
std::array<bool, selector::size()> seen_member{};
// Single pass over the object: each field whose key matches a member yields its
// selector index, which we map back to the corresponding member. The callback
// returns an error_code so that a value-parse error (e.g. a type mismatch on a
// matched field) is propagated by for_each instead of being silently dropped.
error_code walk_error = obj.template for_each<selector>(
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
std::size_t counter = 0;
error_code field_error = SUCCESS;
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
if (matched_index == counter) {
seen_member[counter] = true;
field_error = field_value.get(out.[:mem:]);
}
++counter;
}
}
return field_error;
});
if (walk_error) { return walk_error; }
// Required (non-optional) members must be present: a missing one is reported as
// NO_SUCH_FIELD, mirroring the ordered obj[key] path. Optional members may be
// absent.
std::size_t check_counter = 0;
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
if constexpr (!concepts::optional_type<decltype(out.[:mem:])>) {
if (!seen_member[check_counter]) { return NO_SUCH_FIELD; }
}
++check_counter;
}
}
return SUCCESS;
}
}
#else
template <typename T, typename ValT>
requires(user_defined_type<T> && std::is_class_v<T>)
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
@@ -277,8 +408,9 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
SIMDJSON_TRY(val.get_object().get(obj));
}
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty()) {
constexpr std::string_view key = simdjson::get_json_key_name<mem>();
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
// for optional members, it's ok if the key is missing
auto error = obj[key].get(out.[:mem:]);
@@ -298,6 +430,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
return simdjson::SUCCESS;
}
#endif // SIMDJSON_USE_KEY_SELECTOR_REFLECTION
// Support for enum deserialization - deserialize from string representation using expand approach from P2996R12
template <typename T, typename ValT>
requires(std::is_enum_v<T>)
+18 -6
View File
@@ -314,14 +314,20 @@ simdjson_inline simdjson_result<value> value::at_path(std::string_view json_path
}
}
inline simdjson_result<std::vector<value>> 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 value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept {
json_type t;
SIMDJSON_TRY(type().get(t));
switch (t) {
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:
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:
return INVALID_JSON_POINTER;
}
@@ -585,12 +591,18 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
return first.at_path(json_path);
}
inline simdjson_result<std::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<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
inline error_code simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::for_each_at_path_with_wildcard(
std::string_view json_path, Func&& callback) noexcept {
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));
}
} // namespace simdjson
+26 -4
View File
@@ -698,13 +698,21 @@ public:
simdjson_inline simdjson_result<value> at_path(std::string_view at_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 character (*) for arrays or ".*" for objects.
*
* @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
*/
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;
protected:
/**
@@ -897,9 +905,23 @@ public:
simdjson_inline simdjson_result<int32_t> current_depth() const 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<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;
};
// Forward-declare explicit specializations so MSVC /permissive- sees them before
// any template instantiation that would resolve element.get(val) to the primary.
template<> simdjson_inline error_code
simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get<SIMDJSON_IMPLEMENTATION::ondemand::value>(
SIMDJSON_IMPLEMENTATION::ondemand::value &out) noexcept;
template<> simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>
simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get<SIMDJSON_IMPLEMENTATION::ondemand::value>() noexcept;
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_H
@@ -15,6 +15,27 @@ namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
simdjson_inline bool raw_json_string_is_quote_terminated(const uint8_t *json, uint32_t max_len) noexcept {
bool escaping{false};
for (uint32_t i = 1; i < max_len; i++) {
switch (json[i]) {
case '"':
if (!escaping) { return true; }
escaping = false;
break;
case '\\':
escaping = !escaping;
break;
default:
escaping = false;
break;
}
}
return false;
}
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
simdjson_inline value_iterator::value_iterator(
json_iterator *json_iter,
depth_t depth,
@@ -530,6 +551,15 @@ simdjson_warn_unused simdjson_inline simdjson_result<std::string_view> value_ite
simdjson_warn_unused simdjson_inline simdjson_result<raw_json_string> value_iterator::get_raw_json_string() noexcept {
auto json = peek_scalar("string");
if (*json != '"') { return incorrect_type_error("Not a string"); }
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
if (_json_iter->allow_incomplete_json()) {
const size_t remaining_input_length = _json_iter->remaining_input_length(json);
const uint32_t max_len = remaining_input_length < peek_start_length() ? uint32_t(remaining_input_length) : peek_start_length();
if (!raw_json_string_is_quote_terminated(json, max_len)) {
return STRING_ERROR;
}
}
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
advance_scalar("string");
return raw_json_string(json+1);
}
@@ -677,6 +707,15 @@ simdjson_warn_unused simdjson_inline simdjson_result<raw_json_string> value_iter
auto json = peek_scalar("string");
if (*json != '"') { return incorrect_type_error("Not a string"); }
if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; }
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
if (_json_iter->allow_incomplete_json()) {
const size_t remaining_input_length = _json_iter->remaining_input_length(json);
const uint32_t max_len = remaining_input_length < peek_root_length() ? uint32_t(remaining_input_length) : peek_root_length();
if (!raw_json_string_is_quote_terminated(json, max_len)) {
return STRING_ERROR;
}
}
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
advance_scalar("string");
return raw_json_string(json+1);
}
@@ -1104,4 +1143,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value_iterato
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H
@@ -76,7 +76,7 @@ public:
private:
fractured_json_options options_;
layout_mode current_layout_ = layout_mode::EXPANDED;
layout_mode current_layout_ = layout_mode::expanded;
size_t current_depth_ = 0;
size_t current_line_length_ = 0;
@@ -20,10 +20,10 @@ namespace internal {
* Layout mode for fractured JSON formatting.
*/
enum class layout_mode {
INLINE, // Single line: [1, 2, 3] or {"a": 1}
COMPACT_MULTILINE, // Multiple items per line with breaks
TABLE, // Tabular format for arrays of similar objects
EXPANDED // Traditional multi-line with indentation
single_line, // Single line: [1, 2, 3] or {"a": 1}
compact_multiline, // Multiple items per line with breaks
table, // Tabular format for arrays of similar objects
expanded // Traditional multi-line with indentation
};
/**
@@ -50,7 +50,7 @@ struct element_metrics {
std::vector<std::string> common_keys{};
/** Recommended layout mode based on analysis */
layout_mode recommended_layout = layout_mode::EXPANDED;
layout_mode recommended_layout = layout_mode::expanded;
/** Child metrics for arrays and objects (in order of iteration) */
std::vector<element_metrics> children{};
+43
View File
@@ -1,12 +1,55 @@
#ifndef SIMDJSON_JSONPATHUTIL_H
#define SIMDJSON_JSONPATHUTIL_H
#include "simdjson/error.h"
#include <string>
#include "simdjson/common_defs.h"
#include <limits>
#include <utility>
namespace simdjson {
namespace internal {
/**
* Parses the next JSON Pointer array index token.
*
* The caller passes a pointer fragment with no leading '/', such as "123/foo".
* On success, array_index receives the parsed index and token_length receives
* the number of bytes consumed before the next '/' or the end of the fragment.
*/
simdjson_inline error_code parse_json_pointer_array_index(std::string_view json_pointer,
size_t &array_index,
size_t &token_length) noexcept {
array_index = 0;
token_length = 0;
for (; token_length < json_pointer.length() && json_pointer[token_length] != '/';
token_length++) {
uint8_t digit = uint8_t(json_pointer[token_length] - '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;
}
// 0 followed by other digits is invalid.
if (token_length > 0 && json_pointer[0] == '0') {
return INVALID_JSON_POINTER;
}
if (array_index >
(((std::numeric_limits<size_t>::max)() - digit) / 10)) {
return INDEX_OUT_OF_BOUNDS;
}
array_index = array_index * 10 + digit;
}
// Empty string is invalid; so is a "/" with no digits before it.
if (token_length == 0) {
return INVALID_JSON_POINTER;
}
return SUCCESS;
}
} // namespace internal
/**
* Converts JSONPath to JSON Pointer.
* @param json_path The JSONPath string to be converted.
+1 -1
View File
@@ -17,7 +17,7 @@ public:
simdjson_inline implementation() : simdjson::implementation(
"rvv_vls",
"RISC-V V extension",
0
internal::instruction_set::RVV_VLS
) {}
simdjson_warn_unused error_code create_dom_parser_implementation(
size_t capacity,
+1 -1
View File
@@ -25,7 +25,7 @@ if (Python3_Interpreter_FOUND AND (NOT WIN32))
AMALGAMATE_SOURCE_PATH=${PROJECT_SOURCE_DIR}/src
AMALGAMATE_INPUT_PATH=${PROJECT_SOURCE_DIR}/include
AMALGAMATE_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py --quiet
#
# This is the best way I could find to make amalgamation trigger whenever source files or
# header files change: since the "simdjson" library has to get rebuilt when that happens, we
+161 -37
View File
@@ -11,6 +11,7 @@ import re
import shutil
import datetime
import json
import argparse
from typing import Dict, List, Optional, Set, TextIO, Union, cast
# Pre-compile regex patterns for performance
@@ -30,6 +31,18 @@ if sys.version_info < (3, 0):
sys.stdout.write("Sorry, requires Python 3.x or better\n")
sys.exit(1)
# Parse command-line arguments.
arg_parser = argparse.ArgumentParser(description="Creates the amalgamated source files.")
arg_parser.add_argument("--quiet", action="store_true",
help="Suppress informational output (errors and warnings are still printed).")
args = arg_parser.parse_args()
QUIET = args.quiet
def log(*log_args, **log_kwargs):
"""Print informational output unless --quiet was passed."""
if not QUIET:
print(*log_args, **log_kwargs)
rules = """
Amalgamation Rules for simdjson
@@ -99,11 +112,11 @@ If adding a new implementation, edit the IMPLEMENTATIONS list in this script.
SCRIPTPATH = os.path.dirname(os.path.abspath(sys.argv[0]))
PROJECTPATH = os.path.dirname(SCRIPTPATH)
print(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
log(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
print("We are about to amalgamate all simdjson files into one source file.")
print("See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale.")
log("We are about to amalgamate all simdjson files into one source file.")
log("See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale.")
if "AMALGAMATE_SOURCE_PATH" not in os.environ:
AMALGAMATE_SOURCE_PATH = os.path.join(PROJECTPATH, "src")
else:
@@ -265,37 +278,93 @@ class SimdjsonFile:
return self.filename == 'dependencies.h'
def add_include(self, include: 'SimdjsonFile'):
print(f" Adding include: {self} includes {include}")
log(f" Adding include: {self} includes {include}")
if self.is_conditional_include:
# If I have a dependency file, I can only include something that has a dependency file.
if not include.is_conditional_include:
dep = self.dependency_file
dep_hint = f" and add it to the dependency file '{dep}'" if dep else ""
if dep:
step2 = (
f" 2. Add '{include}' to the dependency manifest so the amalgamator\n"
f" emits it before any file that needs it:\n\n"
f" // in '{dep}'\n"
f" #include \"{include}\"\n\n"
)
else:
step2 = (
f" 2. '{self}' has no associated dependencies.h file, so the\n"
f" amalgamator cannot be told to emit '{include}' earlier.\n"
f" Either give '{self}' a dependencies.h, or restructure so\n"
f" the include is not needed here.\n\n"
)
raise AssertionError(
f"Error: Amalgamated file '{self}' is trying to include '{include}', "
f"but '{include}' is not an amalgamated file.\n\n"
f"FIX: Wrap the #include \"{include}\" in a conditional block:\n\n"
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
f" #include \"{include}\"\n"
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
f"This makes the include editor-only (skipped during amalgamation){dep_hint}.\n\n"
f"During amalgamation, '{include}' is already included earlier in the "
f"amalgamated output, so it does not need to be included again.\n\n"
f"{rules}"
f"Error: amalgamated file '{self}' includes non-amalgamated header '{include}'.\n\n"
f" Why: '{self}' is stitched into the single-header output. Top-level\n"
f" headers like '{include}' are already emitted earlier in that\n"
f" output, so re-including them inside an amalgamated file would\n"
f" either duplicate content or break include ordering.\n\n"
f"FIX (two steps):\n\n"
f" 1. In '{self}', wrap the include so it is skipped during amalgamation\n"
f" but still visible to your editor/IDE:\n\n"
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
f" #include \"{include}\"\n"
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
f" Place it alongside the existing '#ifndef SIMDJSON_CONDITIONAL_INCLUDE'\n"
f" block near the top of the file, if there is one.\n\n"
f"{step2}"
f"After both edits, re-run:\n\n"
f" python3 singleheader/amalgamate.py\n\n"
f"See HACKING.md for the full amalgamation rules.\n"
)
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
# if include.amalgamator_file:
# assert include.amalgamator_file == self, f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
else:
assert include.is_amalgamator or not include.is_conditional_include, f"Error: Free dependency file '{self}' is trying to include '{include}', which is an amalgamated file. Free dependency files (top-level headers) can only include amalgamator files or other free files, not amalgamated files directly. This prevents improper layering. Move the include to an amalgamator or restructure dependencies. {rules}"
if not (include.is_amalgamator or not include.is_conditional_include):
raise AssertionError(
f"Error: free (top-level) header '{self}' includes amalgamated file '{include}'.\n\n"
f" Why: top-level headers are emitted once into the single-header output\n"
f" and form the public API surface. Amalgamated files are emitted\n"
f" multiple times (once per CPU implementation), so including one\n"
f" from the top level would either duplicate them or pin them to a\n"
f" single implementation.\n\n"
f"FIX: move the include to an amalgamator file (e.g. simdjson/arm64.h,\n"
f" simdjson/generic/amalgamated.h) or, if you actually need the\n"
f" declarations at the top level, factor them out into a top-level\n"
f" header that '{self}' can include instead.\n\n"
f"After editing, re-run:\n\n"
f" python3 singleheader/amalgamate.py\n\n"
f"See HACKING.md for the full amalgamation rules.\n"
)
self.includes.append(include)
include.included_from.add(self)
def add_editor_only_include(self, include: 'SimdjsonFile'):
assert self.is_conditional_include, f"Error: File '{self}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE', but '{self}' is not an amalgamated file. Conditional include blocks are only allowed in amalgamated files (those with dependencies). Remove the conditional block or ensure the file is amalgamated. {rules}"
if not include.is_conditional_include:
assert self.dependency_file, f"Error: In '{self}', editor-only include of '{include}' requires a dependency file, but '{self}' has none. Ensure '{self}' has an associated dependencies.h file. {rules}"
if not self.is_conditional_include:
raise AssertionError(
f"Error: '{self}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE',\n"
f" but '{self}' is not an amalgamated file (it has no\n"
f" associated dependencies.h).\n\n"
f" Why: conditional-include blocks only make sense in amalgamated files,\n"
f" because the SIMDJSON_CONDITIONAL_INCLUDE macro is only defined\n"
f" while the amalgamator is emitting them.\n\n"
f"FIX: either remove the '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block from\n"
f" '{self}' (and include the headers unconditionally), or — if '{self}'\n"
f" really should be amalgamated — give it an associated dependencies.h\n"
f" file under the same directory.\n\n"
f"See HACKING.md for the full amalgamation rules.\n"
)
if not include.is_conditional_include and not self.dependency_file:
raise AssertionError(
f"Error: '{self}' editor-only-includes '{include}', but '{self}' has no\n"
f" associated dependencies.h, so the amalgamator has nowhere to record\n"
f" that '{include}' must be emitted earlier in the single-header output.\n\n"
f"FIX: add a 'dependencies.h' file in the same directory as '{self}',\n"
f" and list '{include}' inside it:\n\n"
f" #include \"{include}\"\n\n"
f"See HACKING.md for the full amalgamation rules.\n"
)
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
# elif include.amalgamator_file:
# assert self.is_amalgamated_before(self.amalgamator_file), f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
@@ -310,11 +379,40 @@ class SimdjsonFile:
if file.dependency_file == self:
for editor_only_include in file.editor_only_includes:
if not editor_only_include.is_conditional_include:
assert editor_only_include in self.includes, f"Error: Dependency file '{self}' is missing an include for '{editor_only_include}', which is editor-only included in '{file}'. Add '{editor_only_include}' to '{self}' to ensure completeness. {rules}"
if editor_only_include not in self.includes:
raise AssertionError(
f"Error: dependency manifest '{self}' is missing\n"
f" '{editor_only_include}'.\n\n"
f" It is editor-only-included by '{file}'\n"
f" (inside a '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block),\n"
f" which means the amalgamator does NOT see that include —\n"
f" so unless '{self}' lists it, '{editor_only_include}' will\n"
f" never be emitted in the single-header build.\n\n"
f"FIX: add this line to '{self}':\n\n"
f" #include \"{editor_only_include}\"\n\n"
f"Then re-run:\n\n"
f" python3 singleheader/amalgamate.py\n\n"
f"See HACKING.md for the full amalgamation rules.\n"
)
if editor_only_include in extra_include_set:
extra_include_set.remove(editor_only_include)
assert len(extra_include_set) == 0, f"Error: Dependency file '{self}' includes {extra_include_set}, which are not used in any amalgamated files. Remove these unnecessary includes to clean up dependencies. {rules}"
if len(extra_include_set) > 0:
extras = sorted(str(f) for f in extra_include_set)
bullet_list = "\n".join(f" #include \"{e}\"" for e in extras)
raise AssertionError(
f"Error: dependency manifest '{self}' lists includes that no\n"
f" amalgamated file actually needs:\n\n"
f"{bullet_list}\n\n"
f" Why: every include in a dependencies.h file should correspond to a\n"
f" '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block in some amalgamated\n"
f" sibling file. Stale entries here pull unused headers into the\n"
f" single-header build.\n\n"
f"FIX: either delete the unused entries from '{self}', or, if they were\n"
f" supposed to support a real consumer, add the matching editor-only\n"
f" '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block to that consumer.\n\n"
f"See HACKING.md for the full amalgamation rules.\n"
)
class SimdjsonRepository:
def __init__(self, project_path: str, relative_roots: List[RelativeRoot]):
@@ -366,12 +464,28 @@ class SimdjsonRepository:
used_files = set([file.include_path for file in self if file.root == root])
all_files.difference_update(used_files)
all_files.difference_update(DEPRECATED_FILES)
assert len(all_files) == 0, f"Error: The following files in '{root}' are not used in the amalgamation: {sorted(all_files)}. All .h and .cpp files must be included or added to DEPRECATED_FILES. Check for missing includes or deprecate unused files."
if len(all_files) > 0:
bullet_list = "\n".join(f" {root}/{f}" for f in sorted(all_files))
raise AssertionError(
f"Error: the following files under '{root}/' exist on disk but are not\n"
f" reachable from any #include chain seen by the amalgamator:\n\n"
f"{bullet_list}\n\n"
f" Why: every .h/.cpp file in the tree must either be transitively included\n"
f" from the amalgamation roots, or explicitly listed in DEPRECATED_FILES\n"
f" in singleheader/amalgamate.py. This catches files that were forgotten\n"
f" after a rename, or were added without a corresponding #include.\n\n"
f"FIX: pick one for each file above:\n"
f" - Add an #include for it in the appropriate amalgamator/dependencies file.\n"
f" - If the file is genuinely obsolete, delete it.\n"
f" - If the file is a deprecated public header kept only for source\n"
f" compatibility, add it to the DEPRECATED_FILES set in\n"
f" singleheader/amalgamate.py.\n"
)
class Amalgamator:
@classmethod
def amalgamate(cls, output_path: str, filename: str, roots: List[RelativeRoot], timestamp: str, version: str):
print(f"Creating {output_path}")
log(f"Creating {output_path}")
fid = open(output_path, 'w')
print(f"/* auto-generated on {timestamp}. version {version} Do not edit! */", file=fid)
amalgamator = cls(fid, SimdjsonRepository(PROJECTPATH, roots))
@@ -425,7 +539,17 @@ class Amalgamator:
def write_file(self, file: SimdjsonFile):
# Detect cyclic dependencies
assert file not in self.include_stack, f"Error: Cyclic include detected: {self.include_stack} -> {file}. Remove the circular dependency by restructuring includes."
if file in self.include_stack:
cycle_start = self.include_stack.index(file)
chain = self.include_stack[cycle_start:] + [file]
arrow_chain = "\n -> ".join(str(f) for f in chain)
raise AssertionError(
f"Error: cyclic include detected:\n\n"
f" {arrow_chain}\n\n"
f"FIX: break the cycle by moving the shared declarations into a separate\n"
f" header that both ends of the cycle can include, or by forward-declaring\n"
f" instead of including.\n"
)
self.include_stack.append(file)
file.processed = False
@@ -577,12 +701,12 @@ class Amalgamator:
try:
proc = subprocess.run(['git', 'show', '-s', '--format=%ci', 'HEAD'],
stdout=subprocess.PIPE)
print("the commandline is {}".format(proc.args))
log("the commandline is {}".format(proc.args))
timestamp = proc.stdout.decode('utf-8').strip()
except:
print("git not found, timestamp based on current time")
timestamp = str(datetime.datetime.now())
print(f"timestamp is {timestamp}")
log(f"timestamp is {timestamp}")
os.makedirs(AMALGAMATE_OUTPUT_PATH, exist_ok=True)
AMAL_H = os.path.join(AMALGAMATE_OUTPUT_PATH, "simdjson.h")
@@ -625,7 +749,7 @@ if not validate_implementations():
print("Validation failed. Please update IMPLEMENTATIONS list in amalgamate.py.")
sys.exit(1)
else:
print("implementation validated")
log("implementation validated")
Amalgamator.amalgamate(AMAL_H, "simdjson.h", ['include'], timestamp, version).validate_all_files_used('include')
Amalgamator.amalgamate(AMAL_C, "simdjson.cpp", ['src', 'include'], timestamp, version).validate_all_files_used('src')
@@ -642,20 +766,20 @@ def create_zip():
outdir = AMALGAMATE_OUTPUT_PATH
path = os.path.join(outdir, "singleheader.zip")
print(f"Creating {path}")
log(f"Creating {path}")
with zipfile.ZipFile(path, 'w') as zf:
for name in ["simdjson.cpp", "simdjson.h"]:
source = os.path.join(outdir, name)
print(f"Adding {source}")
log(f"Adding {source}")
zf.write(source, name)
print(f"Created {path}")
log(f"Created {path}")
create_zip()
print("Done with all files generation.")
log("Done with all files generation.")
print(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
print(subprocess.run(['ls', '-la', AMAL_C, AMAL_H, DEMOCPP, README],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip())
print("Done with all files generation.")
log(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
log(subprocess.run(['ls', '-la', AMAL_C, AMAL_H, DEMOCPP, README],
stdout=subprocess.PIPE).stdout.decode('utf-8').strip())
log("Done with all files generation.")
@@ -664,7 +788,7 @@ print("Done with all files generation.")
# Instructions to create demo
#
print("\nGiving final instructions:")
log("\nGiving final instructions:")
with open(README) as r:
for line in r:
print(line)
log(line)
+33 -45
View File
@@ -38,55 +38,43 @@ namespace {
using namespace simd;
simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64<uint8_t>& in) {
// Functional programming causes trouble with Visual Studio.
// Keeping this version in comments since it is much nicer:
// auto v = in.map<uint8_t>([&](simd8<uint8_t> chunk) {
// auto nib_lo = chunk & 0xf;
// auto nib_hi = chunk.shr<4>();
// auto shuf_lo = nib_lo.lookup_16<uint8_t>(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0);
// auto shuf_hi = nib_hi.lookup_16<uint8_t>(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0);
// return shuf_lo & shuf_hi;
// });
const simd8<uint8_t> table1(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0);
const simd8<uint8_t> table2(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0);
simd8x64<uint8_t> v(
(in.chunks[0] & 0xf).lookup_16(table1) & (in.chunks[0].shr<4>()).lookup_16(table2),
(in.chunks[1] & 0xf).lookup_16(table1) & (in.chunks[1].shr<4>()).lookup_16(table2),
(in.chunks[2] & 0xf).lookup_16(table1) & (in.chunks[2].shr<4>()).lookup_16(table2),
(in.chunks[3] & 0xf).lookup_16(table1) & (in.chunks[3].shr<4>()).lookup_16(table2)
const uint8x16_t op_table = simd8<uint8_t>(
0xff, 0, ',', ':', 0, '[', ']', '{', '}', 0, 0, 0, 0, 0, 0, 0
);
const uint8x16_t ws_table = simd8<uint8_t>(
0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0xff, 0, 0
);
const uint8x16_t d0_0 = in.chunks[0];
const uint8x16_t d0_1 = in.chunks[1];
const uint8x16_t d0_2 = in.chunks[2];
const uint8x16_t d0_3 = in.chunks[3];
// We compute whitespace and op separately. If the code later only use one or the
// other, given the fact that all functions are aggressively inlined, we can
// hope that useless computations will be omitted. This is namely case when
// minifying (we only need whitespace). *However* if we only need spaces,
// it is likely that we will still compute 'v' above with two lookup_16: one
// could do it a bit cheaper. This is in contrast with the x64 implementations
// where we can, efficiently, do the white space and structural matching
// separately. One reason for this difference is that on ARM NEON, the table
// lookups either zero or leave unchanged the characters exceeding 0xF whereas
// on x64, the equivalent instruction (pshufb) automatically applies a mask,
// ignoring the 4 most significant bits. Thus the x64 implementation is
// optimized differently. This being said, if you use this code strictly
// just for minification (or just to identify the structural characters),
// there is a small untaken optimization opportunity here. We deliberately
// do not pick it up.
const uint8x16_t match_op_0 = vceqq_u8(vqtbl1q_u8(op_table, vshrq_n_u8(vaddq_u8(d0_0, vdupq_n_u8(3)), 4)), d0_0);
const uint8x16_t match_op_1 = vceqq_u8(vqtbl1q_u8(op_table, vshrq_n_u8(vaddq_u8(d0_1, vdupq_n_u8(3)), 4)), d0_1);
const uint8x16_t match_op_2 = vceqq_u8(vqtbl1q_u8(op_table, vshrq_n_u8(vaddq_u8(d0_2, vdupq_n_u8(3)), 4)), d0_2);
const uint8x16_t match_op_3 = vceqq_u8(vqtbl1q_u8(op_table, vshrq_n_u8(vaddq_u8(d0_3, vdupq_n_u8(3)), 4)), d0_3);
uint64_t op = simd8x64<bool>(
v.chunks[0].any_bits_set(0x7),
v.chunks[1].any_bits_set(0x7),
v.chunks[2].any_bits_set(0x7),
v.chunks[3].any_bits_set(0x7)
).to_bitmask();
const uint8x16_t match_ws_0 = vqtbx1q_u8(vceqq_u8(d0_0, vdupq_n_u8(' ')), ws_table, d0_0);
const uint8x16_t match_ws_1 = vqtbx1q_u8(vceqq_u8(d0_1, vdupq_n_u8(' ')), ws_table, d0_1);
const uint8x16_t match_ws_2 = vqtbx1q_u8(vceqq_u8(d0_2, vdupq_n_u8(' ')), ws_table, d0_2);
const uint8x16_t match_ws_3 = vqtbx1q_u8(vceqq_u8(d0_3, vdupq_n_u8(' ')), ws_table, d0_3);
uint64_t whitespace = simd8x64<bool>(
v.chunks[0].any_bits_set(0x18),
v.chunks[1].any_bits_set(0x18),
v.chunks[2].any_bits_set(0x18),
v.chunks[3].any_bits_set(0x18)
).to_bitmask();
const uint8x16_t bit_mask = simd8<uint8_t>(
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
);
uint8x16_t op_sum0 = vpaddq_u8(vandq_u8(match_op_0, bit_mask), vandq_u8(match_op_1, bit_mask));
uint8x16_t ws_sum0 = vpaddq_u8(vandq_u8(match_ws_0, bit_mask), vandq_u8(match_ws_1, bit_mask));
uint8x16_t op_sum1 = vpaddq_u8(vandq_u8(match_op_2, bit_mask), vandq_u8(match_op_3, bit_mask));
uint8x16_t ws_sum1 = vpaddq_u8(vandq_u8(match_ws_2, bit_mask), vandq_u8(match_ws_3, bit_mask));
op_sum0 = vpaddq_u8(op_sum0, op_sum1);
ws_sum0 = vpaddq_u8(ws_sum0, ws_sum1);
op_sum0 = vpaddq_u8(op_sum0, op_sum0);
ws_sum0 = vpaddq_u8(ws_sum0, ws_sum0);
const uint64_t op = vgetq_lane_u64(vreinterpretq_u64_u8(op_sum0), 0);
const uint64_t whitespace = vgetq_lane_u64(vreinterpretq_u64_u8(ws_sum0), 0);
return { whitespace, op };
}
@@ -132,7 +120,7 @@ simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_
return arm64::stage1::json_minifier::minify<64>(buf, len, dst, dst_len);
}
simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept {
simdjson_flatten simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept {
this->buf = _buf;
this->len = _len;
return arm64::stage1::json_structural_indexer::index<64>(buf, len, *this, streaming);
+27 -1
View File
@@ -291,7 +291,20 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primit
case '"': return visitor.visit_root_string(*this, value);
case 't': return visitor.visit_root_true_atom(*this, value);
case 'f': return visitor.visit_root_false_atom(*this, value);
#if SIMDJSON_ENABLE_NAN_INF
case 'n': {
auto err = visitor.visit_root_null_atom(*this, value);
if (err == SUCCESS) { return err; }
// propagate the error value returned by a bad 'null' atom if parsing 'nan' fails
return visitor.visit_root_nan_atom(*this, value, err);
}
// 'N' isn't a canonically recognized atom, so we return a TAPE_ERROR if failure occurs
case 'N': return visitor.visit_root_nan_atom(*this, value, TAPE_ERROR);
case 'i':
case 'I': return visitor.visit_root_inf_atom(*this, value);
#else
case 'n': return visitor.visit_root_null_atom(*this, value);
#endif
case '-':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
@@ -313,7 +326,20 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V
switch (*value) {
case 't': return visitor.visit_true_atom(*this, value);
case 'f': return visitor.visit_false_atom(*this, value);
#if SIMDJSON_ENABLE_NAN_INF
case 'n': {
auto err = visitor.visit_null_atom(*this, value);
if (err == SUCCESS) { return err; }
// propagate the error value returned by a bad 'null' atom if parsing 'nan' fails
return visitor.visit_nan_atom(*this, value, err);
}
// 'N' isn't a canonically recognized atom, so we return a TAPE_ERROR if failure occurs
case 'N': return visitor.visit_nan_atom(*this, value, TAPE_ERROR);
case 'i':
case 'I': return visitor.visit_inf_atom(*this, value);
#else
case 'n': return visitor.visit_null_atom(*this, value);
#endif
default:
log_error("Non-value found when value was expected!");
return TAPE_ERROR;
@@ -325,4 +351,4 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H
#endif // SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H
+42 -1
View File
@@ -76,6 +76,15 @@ struct tape_builder {
simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept;
simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept;
#if SIMDJSON_ENABLE_NAN_INF
simdjson_warn_unused simdjson_inline error_code visit_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept;
simdjson_warn_unused simdjson_inline error_code visit_root_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept;
// Attempts to parse 'inf' or 'infinity' (case insensitive). Because neither are canonical atoms,
// this returns a tape error on failure.
simdjson_warn_unused simdjson_inline error_code visit_inf_atom(json_iterator &iter, const uint8_t *value) noexcept;
simdjson_warn_unused simdjson_inline error_code visit_root_inf_atom(json_iterator &iter, const uint8_t *value) noexcept;
#endif
/** Called each time a new field or element in an array or object is found. */
simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept;
@@ -255,6 +264,38 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_at
return SUCCESS;
}
#if SIMDJSON_ENABLE_NAN_INF
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept {
iter.log_value("nan");
if (!atomparsing::is_valid_nan_atom(value)) { return errc; }
tape.append_double(std::numeric_limits<double>::quiet_NaN());
return SUCCESS;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept {
iter.log_value("nan");
if (!atomparsing::is_valid_nan_atom(value, iter.remaining_len())) { return errc; }
tape.append_double(std::numeric_limits<double>::quiet_NaN());
return SUCCESS;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_inf_atom(json_iterator &iter, const uint8_t *value) noexcept {
iter.log_value("inf");
// Because 'inf' is an extension, non a canonical atom, a tape error should be returned on failure
if (!atomparsing::is_valid_inf_atom(value)) { return TAPE_ERROR; }
tape.append_double(std::numeric_limits<double>::infinity());
return SUCCESS;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_inf_atom(json_iterator &iter, const uint8_t *value) noexcept {
iter.log_value("inf");
// Because 'inf' is an extension, non a canonical atom, a tape error should be returned on failure
if (!atomparsing::is_valid_inf_atom(value, iter.remaining_len())) { return TAPE_ERROR; }
tape.append_double(std::numeric_limits<double>::infinity());
return SUCCESS;
}
#endif // SIMDJSON_ENABLE_NAN_INF
// private:
simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept {
@@ -310,4 +351,4 @@ simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept {
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H
#endif // SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H
+8 -2
View File
@@ -52,13 +52,17 @@ POSSIBILITY OF SUCH DAMAGE.
#include <cstdlib>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)
#elif (defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)) || defined(__FILC__)
#include <cpuid.h>
#endif
#if defined(__loongarch__) && defined(__linux__)
#include <sys/auxv.h>
#endif
#ifdef __FILC__
#include <stdfil.h>
#endif
namespace simdjson {
namespace internal {
@@ -109,7 +113,7 @@ static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
*ebx = cpu_info[1];
*ecx = cpu_info[2];
*edx = cpu_info[3];
#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)
#elif (defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)) || defined(__FILC__)
uint32_t level = *eax;
__get_cpuid(level, eax, ebx, ecx, edx);
#else
@@ -126,6 +130,8 @@ static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
static inline uint64_t xgetbv() {
#if defined(_MSC_VER)
return _xgetbv(0);
#elif defined(__FILC__)
return zxgetbv();
#else
uint32_t xcr0_lo, xcr0_hi;
asm volatile("xgetbv\n\t" : "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0));
+1
View File
@@ -8,6 +8,7 @@ if(SIMDJSON_STATIC_REFLECTION)
add_cpp_test(static_reflection_edge_cases_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(static_reflection_enum_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(static_reflection_fractured_json_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(static_reflection_annotations_tests LABELS ondemand acceptance per_implementation)
endif(SIMDJSON_STATIC_REFLECTION)
# Copy the simdjson dll into the tests directory
if(MSVC AND BUILD_SHARED_LIBS)
+162 -20
View File
@@ -1,5 +1,8 @@
#include "simdjson.h"
#include "test_builder.h"
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <string_view>
@@ -17,28 +20,27 @@ struct Car {
std::vector<double> tire_pressure;
}; // Car
#if SIMDJSON_SUPPORTS_CONCEPTS
struct Car2549 {
std::string make;
std::string model;
int64_t year;
std::vector<float> tire_pressure;
std::string make;
std::string model;
int64_t year;
std::vector<float> tire_pressure;
};
namespace simdjson {
// we intentionally pass by non-const reference to car.
template <typename builder_type>
void tag_invoke(serialize_tag, builder_type& builder, Car2549& car) {
builder.start_object();
builder.append_key_value("make", car.make);
builder.append_comma();
builder.append_key_value("model", car.model);
builder.append_comma();
builder.append_key_value("year", car.year);
builder.append_comma();
builder.append_key_value("tire_pressure", car.tire_pressure);
builder.end_object();
}
// we intentionally pass by non-const reference to car.
template <typename builder_type>
void tag_invoke(serialize_tag, builder_type &builder, Car2549 &car) {
builder.start_object();
builder.append_key_value("make", car.make);
builder.append_comma();
builder.append_key_value("model", car.model);
builder.append_comma();
builder.append_key_value("year", car.year);
builder.append_comma();
builder.append_key_value("tire_pressure", car.tire_pressure);
builder.end_object();
}
} // namespace simdjson
static_assert(simdjson::require_custom_serialization<Car2549>);
@@ -160,6 +162,140 @@ bool append_float() {
TEST_SUCCEED();
}
#if SIMDJSON_ENABLE_NAN_INF
bool append_nan() {
TEST_START();
simdjson::builder::string_builder sb;
sb.append(std::numeric_limits<double>::quiet_NaN());
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "NaN");
TEST_SUCCEED();
}
bool append_positive_infinity() {
TEST_START();
simdjson::builder::string_builder sb;
sb.append(std::numeric_limits<double>::infinity());
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "Infinity");
TEST_SUCCEED();
}
bool append_negative_infinity() {
TEST_START();
simdjson::builder::string_builder sb;
sb.append(-std::numeric_limits<double>::infinity());
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "-Infinity");
TEST_SUCCEED();
}
bool append_float_nan_inf() {
TEST_START();
{
simdjson::builder::string_builder sb;
sb.append(std::numeric_limits<float>::quiet_NaN());
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "NaN");
}
{
simdjson::builder::string_builder sb;
sb.append(std::numeric_limits<float>::infinity());
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "Infinity");
}
{
simdjson::builder::string_builder sb;
sb.append(-std::numeric_limits<float>::infinity());
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "-Infinity");
}
TEST_SUCCEED();
}
bool nan_inf_in_array() {
TEST_START();
simdjson::builder::string_builder sb;
sb.start_array();
sb.append(1.5);
sb.append_comma();
sb.append(std::numeric_limits<double>::quiet_NaN());
sb.append_comma();
sb.append(std::numeric_limits<double>::infinity());
sb.append_comma();
sb.append(-std::numeric_limits<double>::infinity());
sb.append_comma();
sb.append(2.5);
sb.end_array();
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "[1.5,NaN,Infinity,-Infinity,2.5]");
TEST_SUCCEED();
}
bool nan_inf_in_object() {
TEST_START();
simdjson::builder::string_builder sb;
sb.start_object();
sb.append_key_value("a", std::numeric_limits<double>::quiet_NaN());
sb.append_comma();
sb.append_key_value("b", std::numeric_limits<double>::infinity());
sb.append_comma();
sb.append_key_value("c", -std::numeric_limits<double>::infinity());
sb.end_object();
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
ASSERT_EQUAL(p, "{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}");
TEST_SUCCEED();
}
bool nan_inf_roundtrip() {
TEST_START();
simdjson::builder::string_builder sb;
sb.start_array();
sb.append(std::numeric_limits<double>::quiet_NaN());
sb.append_comma();
sb.append(std::numeric_limits<double>::infinity());
sb.append_comma();
sb.append(-std::numeric_limits<double>::infinity());
sb.end_array();
std::string_view p;
ASSERT_SUCCESS(sb.view().get(p));
simdjson::padded_string output{p};
simdjson::dom::parser parser;
simdjson::dom::element doc;
ASSERT_SUCCESS(parser.parse(output).get(doc));
simdjson::dom::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
std::array<double, 3> expected{
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
};
size_t index = 0;
for (auto val : arr) {
double parsed;
ASSERT_SUCCESS(val.get_double().get(parsed));
if (std::isnan(expected[index])) {
ASSERT_TRUE(std::isnan(parsed));
} else {
ASSERT_EQUAL(parsed, expected[index]);
}
index++;
}
ASSERT_EQUAL(index, expected.size());
TEST_SUCCEED();
}
#endif // SIMDJSON_ENABLE_NAN_INF
bool append_null() {
TEST_START();
simdjson::builder::string_builder sb;
@@ -459,14 +595,15 @@ bool car_test() {
bool issue2549() {
TEST_START();
simdjson::builder::string_builder sb;
Car2549 c = { "Toyota", "Corolla", 2017, {1.0f,2.0f,3.0f} };
Car2549 c = {"Toyota", "Corolla", 2017, {1.0f, 2.0f, 3.0f}};
sb.start_object();
sb.append_key_value("car", c);
sb.end_object();
std::string_view p;
auto result = sb.view().get(p);
ASSERT_SUCCESS(result);
ASSERT_EQUAL(p, "{\"car\":{\"make\":\"Toyota\",\"model\":\"Corolla\",\"year\":2017,\"tire_pressure\":[1.0,2.0,3.0]}}");
ASSERT_EQUAL(p, "{\"car\":{\"make\":\"Toyota\",\"model\":\"Corolla\","
"\"year\":2017,\"tire_pressure\":[1.0,2.0,3.0]}}");
TEST_SUCCEED();
}
@@ -661,6 +798,11 @@ bool run() {
issue2549() && car_test_template() && serialize_optional() &&
#endif
append_char() && append_integer() && append_float() && append_null() &&
#if SIMDJSON_ENABLE_NAN_INF
append_nan() && append_positive_infinity() &&
append_negative_infinity() && append_float_nan_inf() &&
nan_inf_in_array() && nan_inf_in_object() && nan_inf_roundtrip() &&
#endif
clear() && escape_and_append() && escape_and_append_with_quotes() &&
append_raw() && raw_with_length() && string_convertion() &&
buffer_growth() && unicode_validation() && true;
@@ -0,0 +1,146 @@
#include "simdjson.h"
#include "test_builder.h"
#include <string>
#include <optional>
using namespace simdjson;
#if SIMDJSON_STATIC_REFLECTION
struct RenamedFields {
[[= simdjson::rename<"first_name">]] std::string firstName = "";
[[= simdjson::rename<"last_name">]] std::string lastName = "";
int age = 0;
};
struct SkippedField {
std::string name = "";
[[= simdjson::skip]] int internalCache = 0;
};
struct MixedAnnotations {
[[= simdjson::rename<"user_name">]] std::string userName = "";
[[= simdjson::skip]] int sessionToken = 0;
int age = 0;
};
#endif // SIMDJSON_STATIC_REFLECTION
namespace annotation_tests {
bool rename_serialize_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
RenamedFields r{"Alice", "Smith", 30};
std::string out;
ASSERT_SUCCESS(simdjson::to_json(r).get(out));
ASSERT_EQUAL(out, "{\"first_name\":\"Alice\",\"last_name\":\"Smith\",\"age\":30}");
#endif
TEST_SUCCEED();
}
bool rename_deserialize_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
std::string json = R"({"first_name":"Bob","last_name":"Jones","age":25})";
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
RenamedFields r;
ASSERT_SUCCESS(doc.get<RenamedFields>().get(r));
ASSERT_EQUAL(r.firstName, "Bob");
ASSERT_EQUAL(r.lastName, "Jones");
ASSERT_EQUAL(r.age, 25);
#endif
TEST_SUCCEED();
}
bool rename_roundtrip_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
RenamedFields original{"Carol", "White", 40};
std::string json;
ASSERT_SUCCESS(simdjson::to_json(original).get(json));
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
RenamedFields result;
ASSERT_SUCCESS(doc.get<RenamedFields>().get(result));
ASSERT_EQUAL(result.firstName, original.firstName);
ASSERT_EQUAL(result.lastName, original.lastName);
ASSERT_EQUAL(result.age, original.age);
#endif
TEST_SUCCEED();
}
bool skip_serialize_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
SkippedField s{"Alice", 999};
std::string out;
ASSERT_SUCCESS(simdjson::to_json(s).get(out));
ASSERT_EQUAL(out, "{\"name\":\"Alice\"}");
#endif
TEST_SUCCEED();
}
bool skip_deserialize_ignores_field_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
// The key "internalCache" is present in JSON but the field is annotated skip -
// it should be ignored and the field should keep its default value.
std::string json = R"({"name":"Carol","internalCache":999})";
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
SkippedField s;
ASSERT_SUCCESS(doc.get<SkippedField>().get(s));
ASSERT_EQUAL(s.name, "Carol");
ASSERT_EQUAL(s.internalCache, 0);
#endif
TEST_SUCCEED();
}
bool mixed_annotations_serialize_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
MixedAnnotations m{"dave", 12345, 28};
std::string out;
ASSERT_SUCCESS(simdjson::to_json(m).get(out));
// sessionToken must not appear; userName must appear as "user_name"
ASSERT_EQUAL(out, "{\"user_name\":\"dave\",\"age\":28}");
#endif
TEST_SUCCEED();
}
bool mixed_annotations_deserialize_test() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
std::string json = R"({"user_name":"eve","age":35})";
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
MixedAnnotations m;
ASSERT_SUCCESS(doc.get<MixedAnnotations>().get(m));
ASSERT_EQUAL(m.userName, "eve");
ASSERT_EQUAL(m.age, 35);
ASSERT_EQUAL(m.sessionToken, 0);
#endif
TEST_SUCCEED();
}
bool run_all() {
return rename_serialize_test()
&& rename_deserialize_test()
&& rename_roundtrip_test()
&& skip_serialize_test()
&& skip_deserialize_ignores_field_test()
&& mixed_annotations_serialize_test()
&& mixed_annotations_deserialize_test();
}
} // namespace annotation_tests
int main() {
return annotation_tests::run_all() ? EXIT_SUCCESS : EXIT_FAILURE;
}
+6 -3
View File
@@ -13,6 +13,7 @@ add_cpp_test(errortests LABELS dom acceptance per_implementation
add_cpp_test(extracting_values_example LABELS dom acceptance per_implementation)
add_cpp_test(integer_tests LABELS dom acceptance per_implementation)
add_cpp_test(big_integer_tests LABELS dom acceptance per_implementation)
add_cpp_test(nan_inf_tests LABELS dom acceptance per_implementation)
add_cpp_test(jsoncheck LABELS dom acceptance per_implementation)
add_cpp_test(json_path_tests LABELS dom acceptance per_implementation)
add_cpp_test(minefieldcheck LABELS dom acceptance per_implementation)
@@ -127,14 +128,16 @@ endif()
# 1. Visual Studio 2022 v17.6 or later
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
# 4. or if we are targeting C++20 or better
if(
(MSVC AND MSVC_VERSION LESS 1930) OR
(MSVC AND MSVC_VERSION GREATER_EQUAL 1930) OR
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0")
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0") OR
(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
)
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
add_cpp_test(ranges_test LABELS dom acceptance per_implementation)
if(NOT SIMDJSON_STATIC_REFLECTION)
if(NOT SIMDJSON_STATIC_REFLECTION AND NOT CMAKE_CXX_STANDARD GREATER_EQUAL 20)
set_target_properties(ranges_test PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
endif()
endif()
+28 -13
View File
@@ -270,6 +270,25 @@ namespace document_stream_tests {
}
TEST_SUCCEED();
}
bool issue_non_ascii_separator_source() {
TEST_START();
std::string bytes = "1 ";
bytes.push_back(char(0xFF));
bytes += " 2";
simdjson::padded_string json(bytes);
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
ASSERT_SUCCESS(parser.parse_many(json).get(stream));
auto i = stream.begin();
ASSERT_TRUE(i != stream.end());
std::string_view source = i.source();
ASSERT_TRUE(!source.empty());
ASSERT_EQUAL(source.front(), '1');
TEST_SUCCEED();
}
bool issue1310() {
std::cout << "Running " << __func__ << std::endl;
// hex : 20 20 5B 20 33 2C 31 5D 20 22 22 22 22 22 22 22 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
@@ -1752,41 +1771,36 @@ namespace document_stream_tests {
// Note on source(): the comma_delimited filter strips root-level
// commas from structural_indexes but the bytes remain in the buffer.
// source() for a scalar slices [current_index, next_doc_index) and
// strips trailing whitespace, so it currently includes the trailing
// separator comma (e.g. doc 0 source = "1,"). This is the analogous
// bug to the json_sequence RS-trailing source bug. The assertions
// below lock down current_index() and parsed values strictly, and
// accept source() either with or without a single trailing comma.
// strips trailing whitespace; trailing delimiter is stripped for
// consistency with json_sequence. The assertions below lock down
// current_index() and parsed values strictly, and source() returns the
// JSON value without trailing delimiter.
auto input = R"(1,2,"x",true,null)"_padded;
ASSERT_SUCCESS(parser.parse_many(input, simdjson::dom::DEFAULT_BATCH_SIZE, simdjson::stream_format::comma_delimited).get(stream));
auto it = stream.begin();
auto src_matches = [](std::string_view src, std::string_view want) {
return src == want || src == std::string(want) + ",";
};
// doc 0: 1
ASSERT_EQUAL(it.current_index(), size_t(0));
int64_t i1; ASSERT_SUCCESS((*it).get(i1)); ASSERT_EQUAL(i1, int64_t(1));
ASSERT_TRUE(src_matches(it.source(), "1"));
ASSERT_EQUAL(it.source(), std::string_view("1"));
++it;
// doc 1: 2
ASSERT_EQUAL(it.current_index(), size_t(2));
int64_t i2; ASSERT_SUCCESS((*it).get(i2)); ASSERT_EQUAL(i2, int64_t(2));
ASSERT_TRUE(src_matches(it.source(), "2"));
ASSERT_EQUAL(it.source(), std::string_view("2"));
++it;
// doc 2: "x"
ASSERT_EQUAL(it.current_index(), size_t(4));
std::string_view sv; ASSERT_SUCCESS((*it).get(sv)); ASSERT_EQUAL(sv, std::string_view("x"));
ASSERT_TRUE(src_matches(it.source(), "\"x\""));
ASSERT_EQUAL(it.source(), std::string_view("\"x\""));
++it;
// doc 3: true
ASSERT_EQUAL(it.current_index(), size_t(8));
bool b{}; ASSERT_SUCCESS((*it).get(b)); ASSERT_TRUE(b);
ASSERT_TRUE(src_matches(it.source(), "true"));
ASSERT_EQUAL(it.source(), std::string_view("true"));
++it;
// doc 4: null (last doc, no trailing comma in source either way)
@@ -2097,6 +2111,7 @@ namespace document_stream_tests {
comma_delimited_tests() &&
issue2181() &&
issue2170() &&
issue_non_ascii_separator_source() &&
skipbom() &&
fuzzaccess() &&
baby_fuzzer() &&
+23 -3
View File
@@ -60,6 +60,26 @@ bool validate_minefield(const char *dirname) {
char *fullpath = static_cast<char *>(malloc(fullpathlen));
snprintf(fullpath, fullpathlen, "%s%s%s", dirname, needsep ? "/" : "", name);
// Skip any files that have invalid names
if (namelen < 2 || name[1] != '_') {
printf("warning: file %s should begin with 'y_', 'n_', or 'i_' (skipping)\n", name);
continue;
}
// Determines if the file should pass.
// 'y' for 'expected to pass', 'n' for 'expected to fail', 'i' for 'ignore'
char should_pass = name[0];
#if SIMDJSON_ENABLE_NAN_INF
// If nan/infinity are enabled, these files should pass (rather than failing).
//
// Therefore, we mark them as 'should_pass'
bool is_nan_inf_test = contains("NaN", name) || contains("_Inf", name) || contains("_infinity", name);
if (is_nan_inf_test && should_pass == 'n') {
should_pass = 'y';
}
#endif
simdjson::padded_string p;
auto error = simdjson::padded_string::load(fullpath).get(p);
if (error) {
@@ -72,16 +92,16 @@ bool validate_minefield(const char *dirname) {
auto errorcode = parser.parse(p).error();
++how_many;
printf("%s\n", errorcode == simdjson::error_code::SUCCESS ? "ok" : "invalid");
if (starts_with("i_", name) ) {
if (should_pass == 'i') {
// skipping
how_many--;
} else if (starts_with("y_", name) && errorcode != simdjson::error_code::SUCCESS) {
} else if (should_pass == 'y' && errorcode != simdjson::error_code::SUCCESS) {
is_file_as_expected[i] = false;
printf("warning: file %s should pass but it fails. Error is: %s\n",
name, simdjson::error_message(errorcode));
printf("size of file in bytes: %zu \n", p.size());
everything_fine = false;
} else if (starts_with("n_", name) && errorcode == simdjson::error_code::SUCCESS) {
} else if (should_pass == 'n' && errorcode == simdjson::error_code::SUCCESS) {
is_file_as_expected[i] = false;
printf("warning: file %s should fail but it passes.\n", name);
printf("size of file in bytes: %zu \n", p.size());
+472
View File
@@ -0,0 +1,472 @@
#include "simdjson.h"
#include "test_macros.h"
#include "test_main.h"
#include <array>
#include <cmath>
#include <limits>
#include <string>
using namespace simdjson;
namespace nan_inf_tests {
#if SIMDJSON_ENABLE_NAN_INF
bool parse_nan() {
TEST_START();
for (auto json_str : {"NaN", "nan", "NAN"}) {
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(padded_string(json_str, strlen(json_str))).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double().get(value));
ASSERT_TRUE(std::isnan(value));
}
TEST_SUCCEED();
}
bool parse_infinity() {
TEST_START();
for (auto json_str : {"infinity", "Infinity", "INFINITY", "inf", "Inf", "INF",
// Check that 'Inf' parses correctly even when padded to
// the same length as 'Infinity'
"inf ", "Inf ", "INF "}) {
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(padded_string(json_str, strlen(json_str))).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double().get(value));
ASSERT_TRUE(std::isinf(value));
ASSERT_TRUE(value > 0);
}
TEST_SUCCEED();
}
bool parse_negative_infinity() {
TEST_START();
for (auto json_str :
{"-infinity", "-Infinity", "-INFINITY", "-inf", "-Inf", "-INF"}) {
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(padded_string(json_str, strlen(json_str))).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double().get(value));
ASSERT_TRUE(std::isinf(value));
ASSERT_TRUE(value < 0);
}
TEST_SUCCEED();
}
bool nan_in_array() {
TEST_START();
dom::parser parser;
dom::element doc;
auto json = R"([5, NaN, NAN, nan, -nan, -NAN, -NaN, 1.25])"_padded;
ASSERT_SUCCESS(parser.parse(json).get(doc));
dom::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
double nan = std::numeric_limits<double>::quiet_NaN();
std::array<double, 8> expected_values{5, nan, nan, nan, nan, nan, nan, 1.25};
size_t index = 0;
for (auto val : arr) {
if (index == expected_values.size()) {
TEST_FAIL("Array contained more values than expected");
}
double parsed;
ASSERT_SUCCESS(val.get_double().get(parsed));
double expected = expected_values[index];
if (std::isnan(expected)) {
ASSERT_TRUE(std::isnan(parsed))
} else {
ASSERT_EQUAL(parsed, expected_values[index]);
}
index++;
}
ASSERT_EQUAL(index, expected_values.size());
TEST_SUCCEED();
}
bool infinity_in_array() {
TEST_START();
dom::parser parser;
auto json = R"([1,
infinity,
INFINITY,
Infinity,
inf,
Inf,
INF,
-infinity,
-INFINITY,
-Infinity,
-inf,
-Inf,
-INF,
6.5])"_padded;
dom::element doc;
ASSERT_SUCCESS(parser.parse(json).get(doc));
dom::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
double inf = std::numeric_limits<double>::infinity();
std::array<double, 14> expected_values{
1, inf, inf, inf, inf, inf, inf, -inf, -inf, -inf, -inf, -inf, -inf, 6.5};
size_t index = 0;
for (auto val : arr) {
if (index == expected_values.size()) {
TEST_FAIL("Array contained more values than expected");
}
double parsed;
ASSERT_SUCCESS(val.get_double().get(parsed));
ASSERT_EQUAL(parsed, expected_values[index]);
index++;
}
ASSERT_EQUAL(index, expected_values.size());
TEST_SUCCEED();
}
bool nan_in_object() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(parser.parse(R"({"a": NaN, "b": nan})"_padded).get(doc));
double a;
ASSERT_SUCCESS(doc["a"].get_double().get(a));
ASSERT_TRUE(std::isnan(a));
double b;
ASSERT_SUCCESS(doc["b"].get_double().get(b));
ASSERT_TRUE(std::isnan(b));
TEST_SUCCEED();
}
bool infinity_in_object() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(parser.parse(R"({"a": Infinity, "b": -inf})"_padded).get(doc));
double a;
ASSERT_SUCCESS(doc["a"].get_double().get(a));
ASSERT_TRUE(std::isinf(a));
ASSERT_TRUE(a > 0);
double b;
ASSERT_SUCCESS(doc["b"].get_double().get(b));
ASSERT_TRUE(std::isinf(b));
ASSERT_TRUE(b < 0);
TEST_SUCCEED();
}
// Bad 'Infinity' atoms should yield TAPE_ERROR (extension, not canonical).
// Bad 'NaN' atoms (capital 'N') should yield TAPE_ERROR.
// Bad 'nan' atoms (lowercase 'n') should yield N_ATOM_ERROR (shares the
// 'null' dispatch, so a bad 'nan' reports the same error as bad 'null').
// Bad negative atoms (any case) should yield NUMBER_ERROR: a leading '-'
// routes through parse_number, which falls back to compute_nan_inf and
// returns NUMBER_ERROR if that fails.
//
// Each reject_* test runs the cases in three contexts: at the document root
// (visit_root_primitive), inside an array, and inside an object
// (visit_primitive). Both dispatch paths must agree on error codes.
padded_string wrap_in_array(const char *atom) {
return padded_string(std::string("[") + atom + "]");
}
padded_string wrap_in_object(const char *atom) {
return padded_string(std::string("{\"key\": ") + atom + "}");
}
bool reject_trailing_junk() {
TEST_START();
struct {
const char *json;
error_code expected;
} cases[] = {
{"NaNa", TAPE_ERROR}, {"NaN1", TAPE_ERROR},
{"nana", N_ATOM_ERROR}, {"InfX", TAPE_ERROR},
{"Inf_", TAPE_ERROR}, {"Infinityy", TAPE_ERROR},
{"InfinityX", TAPE_ERROR}, {"-NaNa", NUMBER_ERROR},
{"-nana", NUMBER_ERROR}, {"-InfX", NUMBER_ERROR},
{"-Infinityy", NUMBER_ERROR},
};
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(padded_string(c.json, strlen(c.json))).get(doc);
ASSERT_ERROR(err, c.expected);
}
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(wrap_in_array(c.json)).get(doc);
ASSERT_ERROR(err, c.expected);
}
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(wrap_in_object(c.json)).get(doc);
ASSERT_ERROR(err, c.expected);
}
TEST_SUCCEED();
}
bool reject_similar_prefix() {
TEST_START();
struct {
const char *json;
error_code expected;
} cases[] = {
{"Nope", TAPE_ERROR}, {"Napalm", TAPE_ERROR},
{"nope", N_ATOM_ERROR}, {"Infant", TAPE_ERROR},
{"Inform", TAPE_ERROR}, {"Information", TAPE_ERROR},
{"-Nope", NUMBER_ERROR}, {"-nope", NUMBER_ERROR},
{"-Infant", NUMBER_ERROR}, {"-Information", NUMBER_ERROR},
};
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(padded_string(c.json, strlen(c.json))).get(doc);
ASSERT_ERROR(err, c.expected);
}
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(wrap_in_array(c.json)).get(doc);
ASSERT_ERROR(err, c.expected);
}
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(wrap_in_object(c.json)).get(doc);
ASSERT_ERROR(err, c.expected);
}
TEST_SUCCEED();
}
bool reject_truncated_atoms() {
TEST_START();
struct {
const char *json;
error_code expected;
} cases[] = {
{"N", TAPE_ERROR}, {"Na", TAPE_ERROR}, {"na", N_ATOM_ERROR},
{"I", TAPE_ERROR}, {"In", TAPE_ERROR}, {"Infinit", TAPE_ERROR},
{"-N", NUMBER_ERROR}, {"-Na", NUMBER_ERROR}, {"-na", NUMBER_ERROR},
{"-I", NUMBER_ERROR}, {"-In", NUMBER_ERROR}, {"-Infinit", NUMBER_ERROR},
};
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(padded_string(c.json, strlen(c.json))).get(doc);
ASSERT_ERROR(err, c.expected);
}
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(wrap_in_array(c.json)).get(doc);
ASSERT_ERROR(err, c.expected);
}
for (auto c : cases) {
dom::parser parser;
dom::element doc;
auto err = parser.parse(wrap_in_object(c.json)).get(doc);
ASSERT_ERROR(err, c.expected);
}
TEST_SUCCEED();
}
// DOM printer (to_string / minify / prettify) tests. When NaN/Infinity
// parsing is enabled, the writer must emit the same literals on output so
// that round-tripping through the parser preserves the value.
bool print_nan() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(parser.parse("NaN"_padded).get(doc));
ASSERT_EQUAL(simdjson::to_string(doc), "NaN");
ASSERT_EQUAL(simdjson::minify(doc), "NaN");
TEST_SUCCEED();
}
bool print_infinity() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(parser.parse("Infinity"_padded).get(doc));
ASSERT_EQUAL(simdjson::to_string(doc), "Infinity");
ASSERT_EQUAL(simdjson::minify(doc), "Infinity");
TEST_SUCCEED();
}
bool print_negative_infinity() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(parser.parse("-Infinity"_padded).get(doc));
ASSERT_EQUAL(simdjson::to_string(doc), "-Infinity");
ASSERT_EQUAL(simdjson::minify(doc), "-Infinity");
TEST_SUCCEED();
}
bool print_nan_in_array() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse("[1.5, NaN, Infinity, -Infinity, 2.5]"_padded).get(doc));
ASSERT_EQUAL(simdjson::to_string(doc), "[1.5,NaN,Infinity,-Infinity,2.5]");
TEST_SUCCEED();
}
bool print_nan_in_object() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(R"({"a": NaN, "b": Infinity, "c": -Infinity})"_padded)
.get(doc));
ASSERT_EQUAL(simdjson::to_string(doc),
"{\"a\":NaN,\"b\":Infinity,\"c\":-Infinity}");
TEST_SUCCEED();
}
bool print_roundtrip() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(parser.parse("[NaN, Infinity, -Infinity]"_padded).get(doc));
std::string serialized = simdjson::to_string(doc);
dom::parser parser2;
dom::element doc2;
ASSERT_SUCCESS(parser2.parse(padded_string(serialized)).get(doc2));
dom::array arr;
ASSERT_SUCCESS(doc2.get_array().get(arr));
std::array<double, 3> expected{
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
};
size_t index = 0;
for (auto val : arr) {
double parsed;
ASSERT_SUCCESS(val.get_double().get(parsed));
if (std::isnan(expected[index])) {
ASSERT_TRUE(std::isnan(parsed));
} else {
ASSERT_EQUAL(parsed, expected[index]);
}
index++;
}
ASSERT_EQUAL(index, expected.size());
TEST_SUCCEED();
}
// FracturedJson aligns values into columns in table mode. The column width
// is driven by the estimator for unseen elements and by measure_value_length
// for the chosen cells; if either undercounts NaN/Infinity, column 1's
// padding won't match column 2's emitted width and rows visibly misalign.
// Force table mode with min_table_rows = 2 and max_inline_length = 0.
bool table_aligns_nan() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(R"([{"a": 1, "b": 1},{"a": NaN, "b": 1}])"_padded).get(doc));
fractured_json_options opts;
opts.min_table_rows = 2;
opts.max_inline_length = 0;
ASSERT_EQUAL(simdjson::fractured_json(doc, opts),
"[\n"
" { \"a\": 1 , \"b\": 1 },\n"
" { \"a\": NaN, \"b\": 1 }\n"
"]");
TEST_SUCCEED();
}
bool table_aligns_inf() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(R"([{"a": 1, "b": 1},{"a": Infinity, "b": 1}])"_padded)
.get(doc));
fractured_json_options opts;
opts.min_table_rows = 2;
opts.max_inline_length = 0;
ASSERT_EQUAL(simdjson::fractured_json(doc, opts),
"[\n"
" { \"a\": 1 , \"b\": 1 },\n"
" { \"a\": Infinity, \"b\": 1 }\n"
"]");
TEST_SUCCEED();
}
bool table_aligns_neg_inf() {
TEST_START();
dom::parser parser;
dom::element doc;
ASSERT_SUCCESS(
parser.parse(R"([{"a": 1, "b": 1},{"a": -Infinity, "b": 1}])"_padded)
.get(doc));
fractured_json_options opts;
opts.min_table_rows = 2;
opts.max_inline_length = 0;
ASSERT_EQUAL(simdjson::fractured_json(doc, opts),
"[\n"
" { \"a\": 1 , \"b\": 1 },\n"
" { \"a\": -Infinity, \"b\": 1 }\n"
"]");
TEST_SUCCEED();
}
bool run() {
return parse_nan() //
&& parse_infinity() //
&& parse_negative_infinity() //
&& nan_in_array() //
&& infinity_in_array() //
&& nan_in_object() //
&& infinity_in_object() //
&& reject_trailing_junk() //
&& reject_similar_prefix() //
&& reject_truncated_atoms() //
&& print_nan() //
&& print_infinity() //
&& print_negative_infinity() //
&& print_nan_in_array() //
&& print_nan_in_object() //
&& print_roundtrip() //
&& table_aligns_nan() //
&& table_aligns_inf() //
&& table_aligns_neg_inf() //
;
}
#else // !SIMDJSON_ENABLE_NAN_INF
bool run() {
std::cout << "NaN/Infinity parsing is disabled (SIMDJSON_ENABLE_NAN_INF=0), "
"skipping tests."
<< std::endl;
return true;
}
#endif // SIMDJSON_ENABLE_NAN_INF
} // namespace nan_inf_tests
int main(int argc, char *argv[]) {
return test_main(argc, argv, nan_inf_tests::run);
}
+1
View File
@@ -257,6 +257,7 @@ int main() {
&& json_pointer_failure_test(TEST_JSON, "/~01abc", NO_SUCH_FIELD) // Test that we don't try to compare the literal key
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/01", INVALID_JSON_POINTER) // Leading 0 in integer index
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/", INVALID_JSON_POINTER) // Empty index to array
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/18446744073709551616", INDEX_OUT_OF_BOUNDS) // Overflowed index
&& json_pointer_failure_test(TEST_JSON, "/~1~001abc/-", INDEX_OUT_OF_BOUNDS) // End index is always out of bounds
) {
std::cout << "Success!" << std::endl;
+26 -2
View File
@@ -22,6 +22,7 @@ add_cpp_test(compile_time_json_pointer_tests LABELS ondemand acceptance
add_cpp_test(compile_time_no_validation_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_key_string_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_misc_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_nan_inf_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_number_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_number_in_string_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_object_tests LABELS ondemand acceptance per_implementation)
@@ -37,15 +38,38 @@ add_cpp_test(ondemand_wrong_type_error_tests LABELS ondemand acceptance
add_cpp_test(ondemand_iterate_many_csv LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_custom_types_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_custom_types_document_tests LABELS ondemand acceptance per_implementation)
# Reflective deserialization through the experimental key-selector path. The
# SIMDJSON_USE_KEY_SELECTOR_REFLECTION macro is defined at the top of the test
# source itself, so this is an ordinary test target.
add_cpp_test(ondemand_key_selector_reflection_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_stl_types_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_convert_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_unknown_tests LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_wildcard_tests LABELS ondemand acceptance per_implementation)
if(NOT SIMDJSON_SANITIZE)
add_cpp_test(ondemand_padded_input LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_padded_input LABELS ondemand acceptance per_implementation)
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
endif()
# Add the tests if we're on:
# 1. Visual Studio 2022 v17.6 or later
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
# 4. or if we are targeting C++20 or better
if(
(MSVC AND MSVC_VERSION GREATER_EQUAL 1930) OR
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0") OR
(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
)
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
add_cpp_test(ondemand_ranges_tests LABELS ondemand acceptance per_implementation)
if(NOT SIMDJSON_STATIC_REFLECTION AND NOT CMAKE_CXX_STANDARD GREATER_EQUAL 20)
set_target_properties(ondemand_ranges_tests PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
endif()
endif()
if(HAVE_POSIX_FORK AND HAVE_POSIX_WAIT) # assert tests use fork and wait, which aren't on MSVC
add_cpp_test(ondemand_assert_out_of_order_values LABELS assert per_implementation explicitonly ondemand)
endif()
+12 -1
View File
@@ -1,3 +1,12 @@
#ifdef __FILC__
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("This test is not relevant for FILC.\n");
return EXIT_SUCCESS;
}
#else // This test is not relevant for FILC
#ifdef _WIN32
#include <windows.h>
#include <sysinfoapi.h>
@@ -86,4 +95,6 @@ int main() {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
}
#endif // This test is not relevant for FILC
+3 -3
View File
@@ -230,7 +230,7 @@ simdjson::padded_string json_cars =
.location = "Denver",
.is_recurring = true
});
Meeting m2 = simdjson::from(json);
Meeting m2 = simdjson::from(simdjson::pad(json));
std::cout << m2.title << std::endl;
TEST_SUCCEED();
}
@@ -245,7 +245,7 @@ simdjson::padded_string json_cars =
.is_recurring = true
});
std::cout << json << std::endl;
MeetingTime m2 = simdjson::from(json);
MeetingTime m2 = simdjson::from(simdjson::pad(json));
//ASSERT_EQUAL(m2.start_time, start_time);
std::cout << m2.title << std::endl;
TEST_SUCCEED();
@@ -523,4 +523,4 @@ int main(int argc, char *argv[]) {
}
#else
int main() { return 0; }
#endif
#endif
@@ -238,6 +238,25 @@ namespace document_stream_tests {
TEST_SUCCEED();
}
bool issue_non_ascii_separator_source() {
TEST_START();
std::string bytes = "1 ";
bytes.push_back(char(0xFF));
bytes += " 2";
simdjson::padded_string json(bytes);
ondemand::parser parser;
ondemand::document_stream stream;
ASSERT_SUCCESS(parser.iterate_many(json).get(stream));
auto i = stream.begin();
ASSERT_TRUE(i != stream.end());
std::string_view source = i.source();
ASSERT_TRUE(!source.empty());
ASSERT_EQUAL(source.front(), '1');
TEST_SUCCEED();
}
bool issue1977() {
TEST_START();
std::string json = R"( 1111 })";
@@ -1810,22 +1829,18 @@ namespace document_stream_tests {
// Note on source(): the comma_delimited filter strips root-level
// commas from structural_indexes but the bytes remain in the
// buffer. source() for a scalar slices [current_index,
// next_doc_index) and strips trailing whitespace, so it
// currently includes the trailing separator comma (e.g. doc 0
// source = "1,"). The assertions below lock down current_index()
// and parsed values strictly, and accept source() either with
// or without a single trailing comma.
// next_doc_index) and strips trailing whitespace; trailing
// delimiter is stripped for consistency with json_sequence. The
// assertions below lock down current_index() and parsed values
// strictly, and source() returns the JSON value without trailing
// delimiter.
auto input = R"(1,2,"x",true,null)"_padded;
ASSERT_SUCCESS(parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE, simdjson::stream_format::comma_delimited).get(stream));
auto it = stream.begin();
auto src_matches = [](std::string_view src, std::string_view want) {
return src == want || src == std::string(want) + ",";
};
// doc 0: 1
ASSERT_EQUAL(it.current_index(), size_t(0));
ASSERT_TRUE(src_matches(it.source(), "1"));
ASSERT_EQUAL(it.source(), std::string_view("1"));
{
ondemand::document_reference doc;
ASSERT_SUCCESS((*it).get(doc));
@@ -1836,7 +1851,7 @@ namespace document_stream_tests {
// doc 1: 2
ASSERT_EQUAL(it.current_index(), size_t(2));
ASSERT_TRUE(src_matches(it.source(), "2"));
ASSERT_EQUAL(it.source(), std::string_view("2"));
{
ondemand::document_reference doc;
ASSERT_SUCCESS((*it).get(doc));
@@ -1847,7 +1862,7 @@ namespace document_stream_tests {
// doc 2: "x"
ASSERT_EQUAL(it.current_index(), size_t(4));
ASSERT_TRUE(src_matches(it.source(), "\"x\""));
ASSERT_EQUAL(it.source(), std::string_view("\"x\""));
{
ondemand::document_reference doc;
ASSERT_SUCCESS((*it).get(doc));
@@ -1858,7 +1873,7 @@ namespace document_stream_tests {
// doc 3: true
ASSERT_EQUAL(it.current_index(), size_t(8));
ASSERT_TRUE(src_matches(it.source(), "true"));
ASSERT_EQUAL(it.source(), std::string_view("true"));
{
ondemand::document_reference doc;
ASSERT_SUCCESS((*it).get(doc));
@@ -2345,6 +2360,7 @@ namespace document_stream_tests {
comma_delimited_tests() &&
issue2181() &&
issue2170() &&
issue_non_ascii_separator_source() &&
issue2137() &&
skipbom() &&
issue1977() &&
@@ -243,6 +243,28 @@ namespace json_path_tests {
TEST_SUCCEED();
}
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
bool incomplete_string_value_with_allow_incomplete_json() {
TEST_START();
ondemand::parser parser;
ondemand::document doc;
std::string_view val;
auto incomplete_string_value = R"({"key":")"_padded;
auto complete_string_before_incomplete_value = R"({"key":"ok","incomplete":")"_padded;
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(incomplete_string_value).get(doc));
ASSERT_ERROR(doc.at_path(".key").get_string().get(val), simdjson::STRING_ERROR);
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(complete_string_before_incomplete_value).get(doc));
ASSERT_SUCCESS(doc.at_path(".key").get_string().get(val));
ASSERT_EQUAL(val, "ok");
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(complete_string_before_incomplete_value).get(doc));
ASSERT_ERROR(doc.at_path(".incomplete").get_string().get(val), simdjson::STRING_ERROR);
TEST_SUCCEED();
}
#endif
bool many_json_paths_object_array() {
TEST_START();
@@ -442,6 +464,9 @@ namespace json_path_tests {
many_json_paths_object_array() &&
many_json_paths_with_prefix() &&
run_broken_tests() &&
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
incomplete_string_value_with_allow_incomplete_json() &&
#endif
json_path_invalidation() &&
demo_test() &&
demo_relative_path() &&
@@ -509,6 +509,7 @@ namespace json_pointer_tests {
run_failure_test(TEST_JSON, "/~01abc", NO_SUCH_FIELD) &&
run_failure_test(TEST_JSON, "/~1~001abc/01", INVALID_JSON_POINTER) &&
run_failure_test(TEST_JSON, "/~1~001abc/", INVALID_JSON_POINTER) &&
run_failure_test(TEST_JSON, "/~1~001abc/18446744073709551616", INDEX_OUT_OF_BOUNDS) &&
run_failure_test(TEST_JSON, "/~1~001abc/-", INDEX_OUT_OF_BOUNDS) &&
many_json_pointers() &&
document_as_scalar() &&
@@ -518,4 +519,4 @@ namespace json_pointer_tests {
int main(int argc, char *argv[]) {
return test_main(argc, argv, json_pointer_tests::run);
}
}
@@ -0,0 +1,124 @@
// Exercises the experimental key-selector-based reflective deserializer.
//
// We enable the option here (before including simdjson) so the reflective
// tag_invoke uses a compile-time key_selector + object::for_each single pass
// instead of one obj[key] lookup per member. The point of the option is to
// deserialize structs correctly even when the JSON keys are NOT in declaration
// order, so the tests below deliberately scramble the key order.
#define SIMDJSON_USE_KEY_SELECTOR_REFLECTION 1
#include "simdjson.h"
#include <optional>
#include <string>
#include "test_ondemand.h"
#if SIMDJSON_STATIC_REFLECTION
namespace {
using namespace simdjson;
// Default member initializers keep -Weffc++ happy; reflective deserialization
// overwrites these fields from the JSON.
struct User {
uint64_t id = 0;
std::string screen_name{};
std::optional<std::string> location{};
bool verified = false;
};
struct Tweet {
uint64_t id = 0;
std::string text{};
uint64_t retweet_count = 0;
User user{};
};
struct Flat {
uint64_t id = 0;
std::string text{};
uint64_t retweet_count = 0;
};
bool flat_out_of_order() {
TEST_START();
// Keys deliberately out of declaration order.
auto json = R"({ "retweet_count": 7, "id": 12345, "text": "hello world" })"_padded;
ondemand::parser parser;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
Flat f;
ASSERT_SUCCESS(doc.get(f));
ASSERT_EQUAL(f.id, 12345);
ASSERT_EQUAL(f.text, "hello world");
ASSERT_EQUAL(f.retweet_count, 7);
TEST_SUCCEED();
}
bool nested_out_of_order() {
TEST_START();
// Both the outer and the nested object have scrambled key order, and the
// optional "location" is present here.
auto json = R"({
"user": {
"verified": true,
"location": "Montreal",
"screen_name": "ayuu0123",
"id": 1186275104
},
"retweet_count": 3,
"text": "nested",
"id": 99
})"_padded;
ondemand::parser parser;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
Tweet t;
ASSERT_SUCCESS(doc.get(t));
ASSERT_EQUAL(t.id, 99);
ASSERT_EQUAL(t.text, "nested");
ASSERT_EQUAL(t.retweet_count, 3);
ASSERT_EQUAL(t.user.id, 1186275104);
ASSERT_EQUAL(t.user.screen_name, "ayuu0123");
ASSERT_TRUE(t.user.location.has_value());
ASSERT_EQUAL(t.user.location.value(), "Montreal");
ASSERT_TRUE(t.user.verified);
TEST_SUCCEED();
}
bool optional_missing() {
TEST_START();
// "location" is absent: the optional member must be left empty, not error.
auto json = R"({
"user": { "id": 7, "screen_name": "nobody", "verified": false },
"id": 1, "text": "t", "retweet_count": 0
})"_padded;
ondemand::parser parser;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
Tweet t;
ASSERT_SUCCESS(doc.get(t));
ASSERT_EQUAL(t.user.id, 7);
ASSERT_EQUAL(t.user.screen_name, "nobody");
ASSERT_FALSE(t.user.location.has_value());
ASSERT_FALSE(t.user.verified);
TEST_SUCCEED();
}
bool run() {
return flat_out_of_order() &&
nested_out_of_order() &&
optional_missing() &&
true;
}
} // namespace
int main(int argc, char *argv[]) {
return test_main(argc, argv, run);
}
#else
int main() { return 0; }
#endif
+352
View File
@@ -0,0 +1,352 @@
#include "simdjson.h"
#include "test_ondemand.h"
#include <array>
#include <cmath>
#include <limits>
using namespace simdjson;
namespace nan_inf_tests {
#if SIMDJSON_ENABLE_NAN_INF
bool parse_nan() {
TEST_START();
for (auto json_str : {"NaN", "nan", "-nan", "-NAN"}) {
ondemand::parser parser;
padded_string json(json_str, strlen(json_str));
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double().get(value));
ASSERT_TRUE(std::isnan(value));
}
TEST_SUCCEED();
}
bool parse_infinity() {
TEST_START();
for (auto json_str : {"Infinity", "inf", "INF", "Inf"}) {
ondemand::parser parser;
padded_string json(json_str, strlen(json_str));
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double().get(value));
ASSERT_TRUE(std::isinf(value));
ASSERT_TRUE(value > 0);
}
TEST_SUCCEED();
}
bool parse_negative_infinity() {
TEST_START();
for (auto json_str : {"-Infinity", "-inf", "-INF", "-Inf"}) {
ondemand::parser parser;
padded_string json(json_str, strlen(json_str));
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double().get(value));
ASSERT_TRUE(std::isinf(value));
ASSERT_TRUE(value < 0);
}
TEST_SUCCEED();
}
bool nan_in_array() {
TEST_START();
ondemand::parser parser;
auto json = R"([5, NaN, NAN, nan, -nan, -NAN, -NaN, 1.25])"_padded;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
ondemand::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
double nan = std::numeric_limits<double>::quiet_NaN();
std::array<double, 8> expected_values{5, nan, nan, nan, nan, nan, nan, 1.25};
size_t index = 0;
for (auto val : arr) {
if (index == expected_values.size()) {
TEST_FAIL("Array contained more values than expected");
}
double parsed;
ASSERT_SUCCESS(val.get_double().get(parsed));
double expected = expected_values[index];
if (std::isnan(expected)) {
ASSERT_TRUE(std::isnan(parsed))
} else {
ASSERT_EQUAL(parsed, expected_values[index]);
}
index++;
}
ASSERT_EQUAL(index, expected_values.size());
TEST_SUCCEED();
}
bool infinity_in_array() {
TEST_START();
ondemand::parser parser;
auto json = R"([1,
infinity,
INFINITY,
Infinity,
inf,
Inf,
INF,
-infinity,
-INFINITY,
-Infinity,
-inf,
-Inf,
-INF,
6.5])"_padded;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
ondemand::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
double inf = std::numeric_limits<double>::infinity();
std::array<double, 14> expected_values{
1, inf, inf, inf, inf, inf, inf, -inf, -inf, -inf, -inf, -inf, -inf, 6.5};
size_t index = 0;
for (auto val : arr) {
if (index == expected_values.size()) {
TEST_FAIL("Array contained more values than expected");
}
double parsed;
ASSERT_SUCCESS(val.get_double().get(parsed));
ASSERT_EQUAL(parsed, expected_values[index]);
index++;
}
ASSERT_EQUAL(index, expected_values.size());
TEST_SUCCEED();
}
bool nan_in_object() {
TEST_START();
ondemand::parser parser;
auto json = R"({"a": NaN, "b": nan})"_padded;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double a;
ASSERT_SUCCESS(doc["a"].get_double().get(a));
ASSERT_TRUE(std::isnan(a));
// rewind to access second field
doc.rewind();
double b;
ASSERT_SUCCESS(doc["b"].get_double().get(b));
ASSERT_TRUE(std::isnan(b));
TEST_SUCCEED();
}
bool infinity_in_object() {
TEST_START();
ondemand::parser parser;
auto json = R"({"a": Infinity, "b": -inf})"_padded;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double a;
ASSERT_SUCCESS(doc["a"].get_double().get(a));
ASSERT_TRUE(std::isinf(a));
ASSERT_TRUE(a > 0);
doc.rewind();
double b;
ASSERT_SUCCESS(doc["b"].get_double().get(b));
ASSERT_TRUE(std::isinf(b));
ASSERT_TRUE(b < 0);
TEST_SUCCEED();
}
bool nan_in_string() {
TEST_START();
for (auto json_str : {R"("NaN")", R"("nan")"}) {
ondemand::parser parser;
padded_string json(json_str, strlen(json_str));
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double_in_string().get(value));
ASSERT_TRUE(std::isnan(value));
}
TEST_SUCCEED();
}
bool infinity_in_string() {
TEST_START();
for (auto json_str : {R"("Infinity")", R"("inf")", R"("INF")", R"("Inf")"}) {
ondemand::parser parser;
padded_string json(json_str, strlen(json_str));
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double_in_string().get(value));
ASSERT_TRUE(std::isinf(value));
ASSERT_TRUE(value > 0);
}
TEST_SUCCEED();
}
bool negative_infinity_in_string() {
TEST_START();
for (auto json_str :
{R"("-Infinity")", R"("-inf")", R"("-INF")", R"("-Inf")"}) {
ondemand::parser parser;
padded_string json(json_str, strlen(json_str));
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double value;
ASSERT_SUCCESS(doc.get_double_in_string().get(value));
ASSERT_TRUE(std::isinf(value));
ASSERT_TRUE(value < 0);
}
TEST_SUCCEED();
}
bool nan_inf_in_string_in_array() {
TEST_START();
ondemand::parser parser;
auto json = R"(["NaN", "inf", "-Infinity"])"_padded;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
ondemand::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
size_t index = 0;
for (auto val : arr) {
double parsed;
ASSERT_SUCCESS(val.get_double_in_string().get(parsed));
if (index == 0) {
ASSERT_TRUE(std::isnan(parsed));
} else if (index == 1) {
ASSERT_TRUE(std::isinf(parsed));
ASSERT_TRUE(parsed > 0);
} else {
ASSERT_TRUE(std::isinf(parsed));
ASSERT_TRUE(parsed < 0);
}
index++;
}
ASSERT_EQUAL(index, 3);
TEST_SUCCEED();
}
bool nan_inf_in_string_in_object() {
TEST_START();
ondemand::parser parser;
auto json = R"({"a": "NaN", "b": "inf", "c": "-Infinity"})"_padded;
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
double a;
ASSERT_SUCCESS(doc["a"].get_double_in_string().get(a));
ASSERT_TRUE(std::isnan(a));
doc.rewind();
double b;
ASSERT_SUCCESS(doc["b"].get_double_in_string().get(b));
ASSERT_TRUE(std::isinf(b));
ASSERT_TRUE(b > 0);
doc.rewind();
double c;
ASSERT_SUCCESS(doc["c"].get_double_in_string().get(c));
ASSERT_TRUE(std::isinf(c));
ASSERT_TRUE(c < 0);
TEST_SUCCEED();
}
// Bad atom tokens should not be parseable as doubles, and the raw JSON
// token should still be extractable via raw_json_token().
bool reject_trailing_junk() {
TEST_START();
for (auto atom :
{"NaNa", "NaN1", "nana", "InfX", "Inf_", "Infinityy", "InfinityX"}) {
std::string wrapped = std::string("{\"key\": ") + atom + "}";
ondemand::parser parser;
padded_string json(wrapped);
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
auto val = doc["key"];
double num = 0.0;
ASSERT_ERROR(val.get(num), INCORRECT_TYPE);
std::string_view str;
ASSERT_SUCCESS(val.raw_json_token().get(str));
ASSERT_EQUAL(str, atom);
}
TEST_SUCCEED();
}
bool reject_similar_prefix() {
TEST_START();
for (auto atom :
{"Nope", "Napalm", "nope", "Infant", "Inform", "Information"}) {
std::string wrapped = std::string("{\"key\": ") + atom + "}";
ondemand::parser parser;
padded_string json(wrapped);
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
auto val = doc["key"];
double num = 0.0;
ASSERT_ERROR(val.get(num), INCORRECT_TYPE);
std::string_view str;
ASSERT_SUCCESS(val.raw_json_token().get(str));
ASSERT_EQUAL(str, atom);
}
TEST_SUCCEED();
}
bool reject_truncated_atoms() {
TEST_START();
for (auto atom : {"N", "Na", "na", "I", "In", "Infinit"}) {
std::string wrapped = std::string("{\"key\": ") + atom + "}";
ondemand::parser parser;
padded_string json(wrapped);
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(json).get(doc));
auto val = doc["key"];
double num = 0.0;
ASSERT_ERROR(val.get(num), INCORRECT_TYPE);
std::string_view str;
ASSERT_SUCCESS(val.raw_json_token().get(str));
ASSERT_EQUAL(str, atom);
}
TEST_SUCCEED();
}
bool run() {
return parse_nan() //
&& parse_infinity() //
&& parse_negative_infinity() //
&& nan_in_array() //
&& infinity_in_array() //
&& nan_in_object() //
&& infinity_in_object() //
&& nan_in_string() //
&& infinity_in_string() //
&& negative_infinity_in_string() //
&& nan_inf_in_string_in_array() //
&& nan_inf_in_string_in_object() //
&& reject_trailing_junk() //
&& reject_similar_prefix() //
&& reject_truncated_atoms() //
;
}
#else // !SIMDJSON_ENABLE_NAN_INF
bool run() {
std::cout << "NaN/Infinity parsing is disabled (SIMDJSON_ENABLE_NAN_INF=0), "
"skipping tests."
<< std::endl;
return true;
}
#endif // SIMDJSON_ENABLE_NAN_INF
} // namespace nan_inf_tests
int main(int argc, char *argv[]) {
return test_main(argc, argv, nan_inf_tests::run);
}
@@ -1,5 +1,6 @@
#include "simdjson.h"
#include "test_ondemand.h"
#include <limits>
#include <string>
using namespace simdjson;
@@ -335,7 +336,12 @@ namespace number_in_string_tests {
double d;
std::string_view view;
ASSERT_SUCCESS(doc.find_field("ticker").find_field("change").get(value));
#if SIMDJSON_ENABLE_NAN_INF
ASSERT_SUCCESS(value.get_double_in_string().get(d));
ASSERT_EQUAL(d, std::numeric_limits<double>::infinity());
#else
ASSERT_ERROR(value.get_double_in_string().get(d), INCORRECT_TYPE);
#endif
ASSERT_SUCCESS(value.get_string().get(view));
ASSERT_EQUAL(view,"Infinity");
TEST_SUCCEED();
@@ -365,4 +371,4 @@ namespace number_in_string_tests {
int main(int argc, char *argv[]) {
return test_main(argc, argv, number_in_string_tests::run);
}
}
@@ -177,30 +177,212 @@ namespace object_tests {
bool object_find_field_key_selector() {
TEST_START();
auto json = R"({ "name": "John", "age": 30, "city": "New York" })"_padded;
constexpr std::array<std::string_view, 3> keys = {"name", "age", "city"};
constexpr auto selector = ondemand::key_selector<3>(keys);
using sel_t = ondemand::key_selector<"name", "age", "city">;
SUBTEST("ondemand::object with key_selector", test_ondemand_doc(json, [&](auto doc_result) {
// The selector maps keys to compile-time indices.
ASSERT_EQUAL(sel_t::size(), 3);
ASSERT_EQUAL(sel_t::key_at(0), "name");
ASSERT_EQUAL(sel_t::key_at(1), "age");
ASSERT_EQUAL(sel_t::key_at(2), "city");
SUBTEST("ondemand::object with key_selector for_each", test_ondemand_doc(json, [&](auto doc_result) {
ondemand::object object;
ASSERT_SUCCESS( doc_result.get(object) );
auto [index, value_result] = object.find_field(selector);
ASSERT_TRUE(index < 3);
ASSERT_SUCCESS(value_result);
std::string_view str_val;
ASSERT_SUCCESS(value_result.get(str_val));
ASSERT_EQUAL(str_val, "John");
std::array<bool, 3> seen{};
std::string_view name_val{};
uint64_t age_val{};
std::string_view city_val{};
// for_each only yields indices in [0, size()), so index is always < 3 here.
ASSERT_SUCCESS( object.for_each<sel_t>([&](std::size_t index, ondemand::value v) {
seen[index] = true;
switch (index) {
case 0: { std::string_view s; if (!v.get(s)) { name_val = s; } break; }
case 1: { uint64_t n; if (!v.get(n)) { age_val = n; } break; }
case 2: { std::string_view s; if (!v.get(s)) { city_val = s; } break; }
}
}) );
// Test that we can find different keys
ASSERT_EQUAL(selector.index_of("name"), 0);
ASSERT_EQUAL(selector.index_of("age"), 1);
ASSERT_EQUAL(selector.index_of("city"), 2);
ASSERT_EQUAL(selector.index_of("invalid"), 3); // Not found
ASSERT_TRUE(seen[0] && seen[1] && seen[2]);
ASSERT_EQUAL(name_val, "John");
ASSERT_EQUAL(age_val, 30);
ASSERT_EQUAL(city_val, "New York");
return true;
}));
TEST_SUCCEED();
}
// Regression test for the for_each callback error contract: when the callback
// returns an error_code, for_each stops at the first non-SUCCESS result and
// returns it (so a value-parse error is no longer silently dropped).
bool object_for_each_callback_error() {
TEST_START();
using sel_t = ondemand::key_selector<"id", "name">;
auto json = R"({ "id": 7, "name": "Daniel" })"_padded;
// Happy path: an error_code-returning callback extracts the values and
// for_each returns SUCCESS.
SUBTEST("error_code callback success", test_ondemand_doc(json, [&](auto doc_result) {
ondemand::object object;
ASSERT_SUCCESS( doc_result.get(object) );
uint64_t id{};
std::string_view name{};
ASSERT_SUCCESS( object.for_each<sel_t>([&](std::size_t index, ondemand::value v) -> simdjson::error_code {
switch (index) {
case 0: { return v.get(id); }
case 1: { return v.get(name); }
default: { return SUCCESS; }
}
}) );
ASSERT_EQUAL(id, 7);
ASSERT_EQUAL(name, "Daniel");
return true;
}));
// Error path: a value-parse error inside the callback (here, reading the
// integer "id" as a string) is propagated by for_each.
SUBTEST("error_code callback propagates error", test_ondemand_doc(json, [&](auto doc_result) {
ondemand::object object;
ASSERT_SUCCESS( doc_result.get(object) );
ASSERT_ERROR( object.for_each<sel_t>([&](std::size_t index, ondemand::value v) -> simdjson::error_code {
std::string_view s;
if (index == 0) { return v.get(s); } // wrong type on purpose
return SUCCESS;
}), INCORRECT_TYPE );
return true;
}));
TEST_SUCCEED();
}
#endif
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
// Mirrors doc/basics.md "Key selectors", Example 1 (top-level fields).
bool key_selector_example_toplevel() {
TEST_START();
auto json = R"({ "name": "Daniel", "age": 42, "city": "Montreal" })"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
ondemand::object obj = doc.get_object();
using fields = ondemand::key_selector<"name", "city">;
std::string_view name, city;
obj.for_each<fields>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: name = std::string_view(v); break; // "name"
case 1: city = std::string_view(v); break; // "city"
}
});
ASSERT_EQUAL(name, "Daniel");
ASSERT_EQUAL(city, "Montreal");
TEST_SUCCEED();
}
// Mirrors doc/basics.md "Key selectors", Example 2 (nested object).
bool key_selector_example_nested() {
TEST_START();
auto json = R"({ "user": { "id": 1186275104, "screen_name": "ayuu0123", "verified": false } })"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
ondemand::object user = doc.find_field("user").get_object();
using user_fields = ondemand::key_selector<"id", "screen_name">;
uint64_t id = 0;
std::string_view handle;
user.for_each<user_fields>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: id = uint64_t(v); break; // "id"
case 1: handle = std::string_view(v); break; // "screen_name"
}
});
ASSERT_EQUAL(id, 1186275104);
ASSERT_EQUAL(handle, "ayuu0123");
TEST_SUCCEED();
}
// Mirrors doc/basics.md "Key selectors", Example 3 (a selected value that is
// itself an object, processed with a nested for_each).
bool key_selector_example_inner_object() {
TEST_START();
auto json = R"({
"id": 42,
"author": { "name": "Daniel", "handle": "lemire" },
"title": "On Demand"
})"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json);
ondemand::object obj = doc.get_object();
using post_fields = ondemand::key_selector<"id", "author", "title">;
using author_fields = ondemand::key_selector<"name", "handle">;
uint64_t id = 0;
std::string_view title, author_name, author_handle;
obj.for_each<post_fields>([&](std::size_t i, ondemand::value v) {
switch (i) {
case 0: id = uint64_t(v); break; // "id"
case 1: { // "author" is itself an object
ondemand::object author = v.get_object();
author.for_each<author_fields>([&](std::size_t j, ondemand::value av) {
switch (j) {
case 0: author_name = std::string_view(av); break; // "name"
case 1: author_handle = std::string_view(av); break; // "handle"
}
});
break;
}
case 2: title = std::string_view(v); break; // "title"
}
});
ASSERT_EQUAL(id, 42);
ASSERT_EQUAL(title, "On Demand");
ASSERT_EQUAL(author_name, "Daniel");
ASSERT_EQUAL(author_handle, "lemire");
TEST_SUCCEED();
}
#endif
#if SIMDJSON_SUPPORTS_CONCEPTS
// The key_selector's matcher (match_raw, the perfect hash) must return the
// right selector index for every key, including tricky cases: keys that are
// prefixes of one another, keys that extend a real key, a long key, and misses.
// This guards the prefix/length disambiguation against the hash.
bool key_selector_matchers_agree() {
TEST_START();
// Probe builds "<key>\"" in a padded buffer and checks match_raw returns the
// expected selector index (or N for a miss).
auto probe = [](auto sel_tag, std::string_view key, std::size_t expected) -> bool {
using sel = decltype(sel_tag);
char buf[64] = {};
for (size_t i = 0; i < key.size(); ++i) { buf[i] = key[i]; }
buf[key.size()] = '"';
ondemand::raw_json_string r(reinterpret_cast<const uint8_t*>(buf));
ASSERT_EQUAL(sel::match_raw(r), expected);
return true;
};
// Selector with prefix keys and a 30-character key.
using small_sel = ondemand::key_selector<"a", "ab", "abc", "id", "name",
"abcdefghijklmnopqrstuvwxyz1234">;
std::size_t i = 0;
for (auto k : {"a", "ab", "abc", "id", "name", "abcdefghijklmnopqrstuvwxyz1234"}) {
if (!probe(small_sel{}, k, i++)) { return false; }
}
for (auto k : {"x", "abcd", "nam", "names", "i", "ids", "zzzzz", ""}) {
if (!probe(small_sel{}, k, small_sel::size())) { return false; }
}
// Larger selector exercising the same matcher.
using big_sel = ondemand::key_selector<"k00","k01","k02","k03","k04","k05",
"k06","k07","k08","k09","k10","k11">;
if (!probe(big_sel{}, "k00", 0)) { return false; }
if (!probe(big_sel{}, "k05", 5)) { return false; }
if (!probe(big_sel{}, "k11", 11)) { return false; }
for (auto k : {"k12","nope",""}) {
if (!probe(big_sel{}, k, big_sel::size())) { return false; }
}
TEST_SUCCEED();
}
#endif
bool run() {
@@ -213,6 +395,13 @@ namespace object_tests {
value_object_find_field() &&
#if SIMDJSON_SUPPORTS_CONCEPTS
object_find_field_key_selector() &&
object_for_each_callback_error() &&
key_selector_matchers_agree() &&
#endif
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
key_selector_example_toplevel() &&
key_selector_example_nested() &&
key_selector_example_inner_object() &&
#endif
true;
}
+11
View File
@@ -1,3 +1,12 @@
#ifdef __FILC__
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("This test is not relevant for FILC.\n");
return EXIT_SUCCESS;
}
#else // This test is not relevant for FILC
#include "simdjson.h"
#include "simdjson/padded_string_view.h"
#include <cstdio>
@@ -195,3 +204,5 @@ int main() {
}
#endif // SIMDJSON_CPLUSPLUS17
#endif // This test is not relevant for FILC
+319
View File
@@ -0,0 +1,319 @@
#include <iostream>
#include "simdjson.h"
#include "test_macros.h"
#include "test_main.h"
#if SIMDJSON_SUPPORTS_RANGES
#include <algorithm>
#include <ranges>
#include <string>
#include <vector>
using namespace simdjson;
namespace ondemand_ranges_tests {
#if SIMDJSON_EXCEPTIONS
bool array_get_range_basic() {
TEST_START();
auto json = R"([10, 20, 30])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
auto range = ondemand::get_range(arr);
std::vector<int64_t> values;
for (auto elem : range) {
values.push_back(int64_t(elem));
}
ASSERT_EQUAL(values.size(), size_t(3));
ASSERT_EQUAL(values[0], int64_t(10));
ASSERT_EQUAL(values[1], int64_t(20));
ASSERT_EQUAL(values[2], int64_t(30));
TEST_SUCCEED();
}
bool array_range_with_transform() {
TEST_START();
auto json = R"([1, 2, 3, 4, 5])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
auto doubled = ondemand::get_range(arr)
| std::views::transform([](auto v) -> int64_t { return int64_t(v) * 2; });
std::vector<int64_t> values;
for (auto val : doubled) {
values.push_back(val);
}
ASSERT_EQUAL(values.size(), size_t(5));
ASSERT_EQUAL(values[0], int64_t(2));
ASSERT_EQUAL(values[1], int64_t(4));
ASSERT_EQUAL(values[2], int64_t(6));
ASSERT_EQUAL(values[3], int64_t(8));
ASSERT_EQUAL(values[4], int64_t(10));
TEST_SUCCEED();
}
bool array_range_strings() {
TEST_START();
auto json = R"(["alpha", "beta", "gamma"])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
auto to_string = [](auto v) -> std::string {
return std::string(std::string_view(v));
};
auto strings = ondemand::get_range(arr) | std::views::transform(to_string);
std::vector<std::string> values;
for (auto s : strings) {
values.push_back(s);
}
ASSERT_EQUAL(values.size(), size_t(3));
ASSERT_TRUE(values[0] == "alpha");
ASSERT_TRUE(values[1] == "beta");
ASSERT_TRUE(values[2] == "gamma");
TEST_SUCCEED();
}
bool array_range_empty() {
TEST_START();
auto json = R"([])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
auto range = ondemand::get_range(arr);
int count = 0;
for (simdjson_unused auto elem : range) {
count++;
}
ASSERT_EQUAL(count, 0);
TEST_SUCCEED();
}
bool array_range_nested() {
TEST_START();
auto json = R"([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
auto get_name = [](auto v) -> std::string {
return std::string(std::string_view(v["name"]));
};
auto names = ondemand::get_range(arr) | std::views::transform(get_name);
std::vector<std::string> values;
for (auto name : names) {
values.push_back(name);
}
ASSERT_EQUAL(values.size(), size_t(2));
ASSERT_TRUE(values[0] == "Alice");
ASSERT_TRUE(values[1] == "Bob");
TEST_SUCCEED();
}
bool object_get_range_basic() {
TEST_START();
auto json = R"({"a": 1, "b": 2, "c": 3})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto obj = doc.get_object();
auto range = ondemand::get_key_value_range(obj);
std::vector<std::string> keys;
std::vector<int64_t> vals;
for (auto field_result : range) {
keys.push_back(std::string(std::string_view(field_result.escaped_key())));
vals.push_back(int64_t(field_result.value()));
}
ASSERT_EQUAL(keys.size(), size_t(3));
ASSERT_TRUE(keys[0] == "a");
ASSERT_TRUE(keys[1] == "b");
ASSERT_TRUE(keys[2] == "c");
ASSERT_EQUAL(vals[0], int64_t(1));
ASSERT_EQUAL(vals[1], int64_t(2));
ASSERT_EQUAL(vals[2], int64_t(3));
TEST_SUCCEED();
}
bool object_range_with_transform() {
TEST_START();
auto json = R"({"x": 10, "y": 20, "z": 30})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto obj = doc.get_object();
auto get_key = [](auto field_result) -> std::string {
return std::string(std::string_view(field_result.escaped_key()));
};
auto keys = ondemand::get_key_value_range(obj) | std::views::transform(get_key);
std::vector<std::string> values;
for (auto k : keys) {
values.push_back(k);
}
ASSERT_EQUAL(values.size(), size_t(3));
ASSERT_TRUE(values[0] == "x");
ASSERT_TRUE(values[1] == "y");
ASSERT_TRUE(values[2] == "z");
TEST_SUCCEED();
}
bool object_range_empty() {
TEST_START();
auto json = R"({})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto obj = doc.get_object();
auto range = ondemand::get_key_value_range(obj);
int count = 0;
for (simdjson_unused auto elem : range) {
count++;
}
ASSERT_EQUAL(count, 0);
TEST_SUCCEED();
}
bool get_range_from_result() {
TEST_START();
auto json = R"([100, 200, 300])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
// get_range with simdjson_result<array> - unwraps automatically
auto range = ondemand::get_range(doc.get_array());
std::vector<int64_t> values;
for (auto elem : range) {
values.push_back(int64_t(elem));
}
ASSERT_EQUAL(values.size(), size_t(3));
ASSERT_EQUAL(values[0], int64_t(100));
ASSERT_EQUAL(values[1], int64_t(200));
ASSERT_EQUAL(values[2], int64_t(300));
TEST_SUCCEED();
}
bool object_range_key_iteration() {
TEST_START();
auto json = R"({"name": "Alice", "age": 30, "city": "New York"})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto obj = doc.get_object();
// Test the specific pattern: iterating over field_result.key()
std::vector<std::string> keys;
for (auto field_result : ondemand::get_key_value_range(obj)) {
keys.push_back(std::string(field_result.escaped_key().value()));
}
ASSERT_EQUAL(keys.size(), size_t(3));
bool has_name = false, has_age = false, has_city = false;
for (const auto& key : keys) {
if (key == "name") has_name = true;
else if (key == "age") has_age = true;
else if (key == "city") has_city = true;
}
ASSERT_TRUE(has_name);
ASSERT_TRUE(has_age);
ASSERT_TRUE(has_city);
TEST_SUCCEED();
}
// Verify that the types satisfy the expected C++20 concepts.
bool concept_checks() {
TEST_START();
static_assert(std::input_iterator<ondemand::array_range_iterator>);
static_assert(std::input_iterator<ondemand::object_range_iterator>);
static_assert(std::ranges::input_range<ondemand::array_range>);
static_assert(std::ranges::input_range<ondemand::object_range>);
static_assert(std::ranges::view<ondemand::array_range>);
static_assert(std::ranges::view<ondemand::object_range>);
TEST_SUCCEED();
}
#endif // SIMDJSON_EXCEPTIONS
// These tests work without exceptions.
bool array_range_noexcept_basic() {
TEST_START();
auto json = R"([1, 2, 3])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
ondemand::array arr;
ASSERT_SUCCESS(doc.get_array().get(arr));
auto range = ondemand::get_range(arr);
ASSERT_SUCCESS(range.error());
int count = 0;
for (auto elem : range) {
int64_t val;
ASSERT_SUCCESS(elem.get_int64().get(val));
count++;
}
ASSERT_EQUAL(count, 3);
TEST_SUCCEED();
}
bool object_range_noexcept_basic() {
TEST_START();
auto json = R"({"a": 1, "b": 2})"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
ondemand::object obj;
ASSERT_SUCCESS(doc.get_object().get(obj));
auto range = ondemand::get_key_value_range(obj);
ASSERT_SUCCESS(range.error());
int count = 0;
for (auto field_result : range) {
simdjson_unused ondemand::field f;
ASSERT_SUCCESS(std::move(field_result).get(f));
count++;
}
ASSERT_EQUAL(count, 2);
TEST_SUCCEED();
}
bool run() {
return
array_range_noexcept_basic() &&
object_range_noexcept_basic() &&
#if SIMDJSON_EXCEPTIONS
concept_checks() &&
array_get_range_basic() &&
array_range_with_transform() &&
array_range_strings() &&
array_range_empty() &&
array_range_nested() &&
object_get_range_basic() &&
object_range_with_transform() &&
object_range_empty() &&
get_range_from_result() &&
object_range_key_iteration() &&
#endif // SIMDJSON_EXCEPTIONS
true;
}
} // namespace ondemand_ranges_tests
int main(int argc, char *argv[]) {
return test_main(argc, argv, ondemand_ranges_tests::run);
}
#else // !SIMDJSON_SUPPORTS_RANGES
int main() {
std::cout << "Ranges tests require C++20 ranges support, skipping." << std::endl;
return 0;
}
#endif // SIMDJSON_SUPPORTS_RANGES
@@ -205,6 +205,68 @@ simdjson_inline simdjson_result<Car> simdjson::ondemand::document::get() & noexc
#if SIMDJSON_EXCEPTIONS
// Test matching the doc example in doc/basics.md
// "Using for_each_at_path_with_wildcard for JSONPath Queries (On-Demand)"
bool wildcard_basics_example() {
TEST_START();
simdjson::padded_string json_string = R"(
{
"firstName": "John",
"lastName": "doe",
"age": 26,
"address": {
"streetAddress": "naist street",
"city": "Nara",
"postalCode": "630-0192"
},
"phoneNumbers": [
{
"type": "iPhone",
"numbers": ["0123-4567-8888", "0123-4567-8788"]
},
{
"type": "home",
"numbers": ["0123-4567-8910"]
}
]
})"_padded;
ondemand::parser parser;
ondemand::document doc = parser.iterate(json_string);
// Fetch all fields in the address object
std::vector<std::string_view> fields;
auto error = doc.for_each_at_path_with_wildcard("$.address.*",
[&](ondemand::value value) {
std::string_view field;
if (value.get(field) == SUCCESS) {
fields.push_back(field);
}
});
ASSERT_SUCCESS(error);
ASSERT_EQUAL(fields.size(), 3);
ASSERT_EQUAL(fields[0], "naist street");
ASSERT_EQUAL(fields[1], "Nara");
ASSERT_EQUAL(fields[2], "630-0192");
// Fetch all phone numbers
std::vector<std::string_view> numbers;
error = doc.for_each_at_path_with_wildcard("$.phoneNumbers[*].numbers[*]",
[&](ondemand::value value) {
std::string_view number;
if (value.get(number) == SUCCESS) {
numbers.push_back(number);
}
});
ASSERT_SUCCESS(error);
ASSERT_EQUAL(numbers.size(), 3);
ASSERT_EQUAL(numbers[0], "0123-4567-8888");
ASSERT_EQUAL(numbers[1], "0123-4567-8788");
ASSERT_EQUAL(numbers[2], "0123-4567-8910");
TEST_SUCCEED();
}
void main_capture() {
padded_string json_padded = "{\"a\":[1,2,3], \"b\": 2, \"c\": \"hello\"}"_padded;
std::vector<std::string_view> fields;
@@ -2041,6 +2103,7 @@ bool value_raw_json_object() {
}
#endif
bool run() {
return true
&& fatal_error()
@@ -2100,6 +2163,7 @@ bool run() {
&& current_location_no_error()
&& to_string_example_no_except()
#if SIMDJSON_EXCEPTIONS
&& wildcard_basics_example()
&& issue2215()
&& to_string_example()
&& raw_string()
+16 -5
View File
@@ -1,6 +1,8 @@
#include "simdjson.h"
#include "test_ondemand.h"
#include <cmath>
using namespace simdjson;
namespace unknown_tests {
@@ -23,7 +25,7 @@ namespace unknown_tests {
bool object_value_type() {
TEST_START();
padded_string json = "{\"key\": NaN}"_padded;
ASSERT_TRUE(test_ondemand_doc(json, [&](auto doc_result) {
auto do_test = [&](auto doc_result) {
simdjson::ondemand::object object;
ASSERT_SUCCESS( doc_result.get_object().get(object) );
@@ -31,33 +33,42 @@ namespace unknown_tests {
auto val = object["key"];
ASSERT_SUCCESS( val.type().get(type) );
ASSERT_EQUAL( type, simdjson::ondemand::json_type::unknown );
double num;
double num = 0.0;
#if SIMDJSON_ENABLE_NAN_INF
ASSERT_SUCCESS( val.get(num) );
ASSERT_TRUE( std::isnan(num) );
#else
ASSERT_EQUAL( val.get(num), simdjson::error_code::INCORRECT_TYPE);
#endif
std::string_view str;
ASSERT_SUCCESS( val.raw_json_token().get(str) );
ASSERT_EQUAL( str, "NaN");
return true;
}));
};
ASSERT_TRUE(test_ondemand_doc(json, do_test));
TEST_SUCCEED();
}
#if SIMDJSON_EXCEPTIONS
bool object_value_type_exception() {
TEST_START();
padded_string json = "{\"key\": NaN}"_padded;
padded_string json = "{\"key\": ThisIsNotANumber}"_padded;
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(json);
simdjson::ondemand::object object = doc.get_object();
simdjson::ondemand::value val = object["key"];
simdjson::ondemand::json_type type = val.type();
ASSERT_EQUAL( type, simdjson::ondemand::json_type::unknown);
bool exception_caught = false;
try {
double num = val.get_double();
(void)num;
} catch (const simdjson::simdjson_error& e) {
exception_caught = true;
ASSERT_EQUAL( e.error(), simdjson::error_code::INCORRECT_TYPE);
}
ASSERT_TRUE( exception_caught );
std::string_view str = val.raw_json_token();
ASSERT_EQUAL( str, "NaN");
ASSERT_EQUAL( str, "ThisIsNotANumber");
TEST_SUCCEED();
}
#endif
+104 -86
View File
@@ -21,18 +21,16 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> titles;
ASSERT_SUCCESS(doc.at_path_with_wildcard("$.books[*].title").get(titles));
std::vector<std::string_view> titles;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$.books[*].title", [&](ondemand::value v) {
std::string_view sv;
if (v.get_string().get(sv) == SUCCESS) { titles.push_back(sv); }
}));
ASSERT_EQUAL(titles.size(), 3);
std::string_view title;
ASSERT_SUCCESS(titles[0].get_string().get(title));
ASSERT_EQUAL(title, "Book A");
ASSERT_SUCCESS(titles[1].get_string().get(title));
ASSERT_EQUAL(title, "Book B");
ASSERT_SUCCESS(titles[2].get_string().get(title));
ASSERT_EQUAL(title, "Book C");
ASSERT_EQUAL(titles[0], "Book A");
ASSERT_EQUAL(titles[1], "Book B");
ASSERT_EQUAL(titles[2], "Book C");
TEST_SUCCEED();
}
@@ -46,18 +44,16 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> prices;
ASSERT_SUCCESS(doc.at_path_with_wildcard("$.prices[*]").get(prices));
std::vector<uint64_t> prices;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$.prices[*]", [&](ondemand::value v) {
uint64_t p;
if (v.get_uint64().get(p) == SUCCESS) { prices.push_back(p); }
}));
ASSERT_EQUAL(prices.size(), 5);
uint64_t price;
ASSERT_SUCCESS(prices[0].get_uint64().get(price));
ASSERT_EQUAL(price, 10);
ASSERT_SUCCESS(prices[2].get_uint64().get(price));
ASSERT_EQUAL(price, 30);
ASSERT_SUCCESS(prices[4].get_uint64().get(price));
ASSERT_EQUAL(price, 50);
ASSERT_EQUAL(prices[0], 10);
ASSERT_EQUAL(prices[2], 30);
ASSERT_EQUAL(prices[4], 50);
TEST_SUCCEED();
}
@@ -75,19 +71,14 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> names;
auto error = doc.at_path_with_wildcard("$.users.*.name").get(names);
std::set<std::string_view> name_set;
auto error = doc.for_each_at_path_with_wildcard("$.users.*.name", [&](ondemand::value v) {
std::string_view sv;
if (v.get_string().get(sv) == SUCCESS) { name_set.insert(sv); }
});
ASSERT_SUCCESS(error);
ASSERT_EQUAL(names.size(), 3);
// Verify we got all three names (order may vary)
std::set<std::string_view> name_set;
for (auto& name : names) {
std::string_view view;
ASSERT_SUCCESS(name.get_string().get(view));
name_set.insert(view);
}
ASSERT_EQUAL(name_set.size(), 3);
ASSERT_TRUE(name_set.count("Alice") > 0);
ASSERT_TRUE(name_set.count("Bob") > 0);
ASSERT_TRUE(name_set.count("Charlie") > 0);
@@ -117,19 +108,15 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> salaries;
auto error = doc.at_path_with_wildcard("$.departments.*.employees[*].salary").get(salaries);
uint64_t total = 0;
size_t count = 0;
auto error = doc.for_each_at_path_with_wildcard("$.departments.*.employees[*].salary", [&](ondemand::value v) {
uint64_t s;
if (v.get_uint64().get(s) == SUCCESS) { total += s; count++; }
});
ASSERT_SUCCESS(error);
ASSERT_EQUAL(salaries.size(), 4);
// Check sum of all salaries
uint64_t total = 0;
for (auto& salary : salaries) {
uint64_t value;
ASSERT_SUCCESS(salary.get_uint64().get(value));
total += value;
}
ASSERT_EQUAL(count, 4);
ASSERT_EQUAL(total, 360000);
TEST_SUCCEED();
@@ -142,11 +129,12 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> items;
auto error = doc.at_path_with_wildcard("$.items[*].name").get(items);
size_t count = 0;
auto error = doc.for_each_at_path_with_wildcard("$.items[*].name", [&](ondemand::value) {
count++;
});
ASSERT_SUCCESS(error);
ASSERT_EQUAL(items.size(), 0);
ASSERT_EQUAL(count, 0);
TEST_SUCCEED();
}
@@ -158,11 +146,12 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> names;
auto error = doc.at_path_with_wildcard("$.users.*.name").get(names);
size_t count = 0;
auto error = doc.for_each_at_path_with_wildcard("$.users.*.name", [&](ondemand::value) {
count++;
});
ASSERT_SUCCESS(error);
ASSERT_EQUAL(names.size(), 0);
ASSERT_EQUAL(count, 0);
TEST_SUCCEED();
}
@@ -175,8 +164,7 @@ namespace wildcard_tests {
auto doc = parser.iterate(json);
// Can't use array wildcard on scalar
std::vector<ondemand::value> values;
auto error = doc.at_path_with_wildcard("$.data[*]").get(values);
auto error = doc.for_each_at_path_with_wildcard("$.data[*]", [](ondemand::value) {});
ASSERT_ERROR(error, INVALID_JSON_POINTER);
TEST_SUCCEED();
@@ -195,12 +183,12 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
// Get all nested arrays
std::vector<ondemand::value> arrays;
ASSERT_SUCCESS(doc.at_path_with_wildcard("$.matrix[*]").get(arrays));
size_t count = 0;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$.matrix[*]", [&](ondemand::value) {
count++;
}));
// Verify we got 3 arrays
ASSERT_EQUAL(arrays.size(), 3);
ASSERT_EQUAL(count, 3);
TEST_SUCCEED();
}
@@ -218,18 +206,16 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> statuses;
ASSERT_SUCCESS(doc.at_path_with_wildcard("$.data[*].info.status").get(statuses));
std::vector<std::string_view> statuses;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$.data[*].info.status", [&](ondemand::value v) {
std::string_view sv;
if (v.get_string().get(sv) == SUCCESS) { statuses.push_back(sv); }
}));
ASSERT_EQUAL(statuses.size(), 3);
std::string_view status;
ASSERT_SUCCESS(statuses[0].get_string().get(status));
ASSERT_EQUAL(status, "active");
ASSERT_SUCCESS(statuses[1].get_string().get(status));
ASSERT_EQUAL(status, "inactive");
ASSERT_SUCCESS(statuses[2].get_string().get(status));
ASSERT_EQUAL(status, "active");
ASSERT_EQUAL(statuses[0], "active");
ASSERT_EQUAL(statuses[1], "inactive");
ASSERT_EQUAL(statuses[2], "active");
TEST_SUCCEED();
}
@@ -250,13 +236,12 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> values;
ASSERT_SUCCESS(doc.at_path_with_wildcard("$.mixed[*]").get(values));
size_t count = 0;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$.mixed[*]", [&](ondemand::value) {
count++;
}));
// Just verify we got all 6 elements
ASSERT_EQUAL(values.size(), 6);
// Don't try to access the values - they've been consumed by the OnDemand API
ASSERT_EQUAL(count, 6);
TEST_SUCCEED();
}
@@ -275,8 +260,11 @@ namespace wildcard_tests {
auto doc = parser.iterate(json);
// Third element doesn't have "name" field
std::vector<ondemand::value> names;
auto error = doc.at_path_with_wildcard("$.data[*].name").get(names);
std::vector<std::string_view> names;
auto error = doc.for_each_at_path_with_wildcard("$.data[*].name", [&](ondemand::value v) {
std::string_view sv;
if (v.get_string().get(sv) == SUCCESS) { names.push_back(sv); }
});
// This might error or return partial results
if (error) {
@@ -301,18 +289,47 @@ namespace wildcard_tests {
ondemand::parser parser;
auto doc = parser.iterate(json);
std::vector<ondemand::value> names;
ASSERT_SUCCESS(doc.at_path_with_wildcard("$[*].name").get(names));
std::vector<std::string_view> names;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$[*].name", [&](ondemand::value v) {
std::string_view sv;
if (v.get_string().get(sv) == SUCCESS) { names.push_back(sv); }
}));
ASSERT_EQUAL(names.size(), 3);
ASSERT_EQUAL(names[0], "Item 1");
ASSERT_EQUAL(names[1], "Item 2");
ASSERT_EQUAL(names[2], "Item 3");
std::string_view name;
ASSERT_SUCCESS(names[0].get_string().get(name));
ASSERT_EQUAL(name, "Item 1");
ASSERT_SUCCESS(names[1].get_string().get(name));
ASSERT_EQUAL(name, "Item 2");
ASSERT_SUCCESS(names[2].get_string().get(name));
ASSERT_EQUAL(name, "Item 3");
TEST_SUCCEED();
}
// https://github.com/simdjson/simdjson/issues/2684
bool wildcard_raw_json_issue_2684() {
TEST_START();
auto json = R"([{"tag_meta":{"meta_code":2000211,"meta_value":""},"tag_value":""}])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
size_t count = 0;
bool raw_json_ok = true;
ASSERT_SUCCESS(doc.for_each_at_path_with_wildcard("$[*].tag_meta", [&](ondemand::value v) {
count++;
std::string_view raw;
if (v.raw_json().get(raw) != SUCCESS) {
raw_json_ok = false;
return;
}
// raw_json() must not leak sibling fields or outer array delimiters
std::string_view expected = R"({"meta_code":2000211,"meta_value":""})";
if (raw != expected || raw.find("tag_value") != std::string_view::npos) {
std::cerr << " raw_json() returned: " << raw << std::endl;
raw_json_ok = false;
}
}));
ASSERT_EQUAL(count, 1);
ASSERT_TRUE(raw_json_ok);
TEST_SUCCEED();
}
@@ -329,10 +346,11 @@ namespace wildcard_tests {
wildcard_with_nested_objects() &&
mixed_types_in_array() &&
wildcard_nonexistent_field() &&
root_array_wildcard();
root_array_wildcard() &&
wildcard_raw_json_issue_2684();
}
}
int main(int argc, char *argv[]) {
return test_main(argc, argv, wildcard_tests::run);
}
}