Compare commits

..

29 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* If SIMDJSON_ENABLE_NAN_INF is set, write out NaN and Infinity

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix tests

---------

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

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

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

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

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

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

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

* simplifying.

* update

* more fixes

* moving example

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

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

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

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

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

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

* minor tweak

* minor update

* update doc

---------

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

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

* removing non-ascii

* saving.

* bumping up

* take 2
2026-04-12 18:12:31 -04:00
Daniel Lemire ede3129081 modernizing the benchmarks (#2679) 2026-04-12 18:09:46 -04:00
Daniel Lemire 8ea4c1c2e6 being more precise 2026-04-12 17:06:27 -04:00
Daniel Lemire 486b2a3828 minor update 2026-04-11 14:45:38 -04:00
96 changed files with 4376 additions and 2377 deletions
+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 .
+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
+12 -8
View File
@@ -13,18 +13,22 @@ jobs:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo}
- {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: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF, nan_inf: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: ON}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: ON}
# Exercise the opt-in Windows memory-file mapping path at least once in CI.
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: OFF}
steps:
- 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}} -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
@@ -37,4 +41,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}}
+41
View File
@@ -75,6 +75,41 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
)
endif()
# padded_memory_map is always available on POSIX. On Windows it is disabled
# by default because it depends on the `CreateFileMapping2` / `MapViewOfFile3`
# APIs, which require Windows 10 version 1803 or later and are exported via
# onecore.lib rather than the default kernel32.lib. Turn this option ON to
# opt into the feature on Windows; simdjson will then set the appropriate
# Windows version macros and link onecore, so everything that links
# simdjson picks up both the compile-time declarations and the import
# library automatically. The option is a no-op on POSIX (where the feature
# is unconditionally enabled).
option(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
"Enable simdjson::padded_memory_map on Windows (requires Windows 10 \
version 1803 or later). Always enabled on POSIX." OFF)
if(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS)
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1
)
if(WIN32)
# Raise the Windows version floor so that <windows.h> declares the
# modern memory-mapping APIs, and link the import library that
# actually exports them. _WIN32_WINNT / WINVER / NTDDI_VERSION together
# tell <sdkddkver.h> which APIs to light up.
simdjson_add_props(
target_compile_definitions PUBLIC
_WIN32_WINNT=0x0A00
WINVER=0x0A00
NTDDI_VERSION=0x0A000006 # NTDDI_WIN10_RS5, Windows 10 version 1809
)
simdjson_add_props(
target_link_libraries PUBLIC
onecore
)
endif()
endif()
if(is_top_project)
option(SIMDJSON_INSTALL "Enable target install" ON)
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
@@ -219,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
+2 -1
View File
@@ -1,9 +1,10 @@
add_subdirectory(dom)
include_directories( . linux )
include_directories( . )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
link_libraries(counters)
if(SIMDJSON_STATIC_REFLECTION)
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
endif(SIMDJSON_STATIC_REFLECTION)
File diff suppressed because it is too large Load Diff
+1
View File
@@ -124,6 +124,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
#include "kostya/boostjson.h"
#include "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
+2 -39
View File
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <cassert>
#include <cctype>
@@ -25,7 +26,6 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
@@ -204,12 +204,8 @@ struct feature_benchmarker {
}
// Rate of 1-7-structural misses per 8-structural flip
double struct1_7_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped);
#endif
}
// Extra cost of an 8-15 structural block over a 1-7 structural block
double struct8_15_cost(BenchmarkStage stage) const {
@@ -221,12 +217,8 @@ struct feature_benchmarker {
}
// Rate of 8-15-structural misses per 8-structural flip
double struct8_15_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped);
#endif
}
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
@@ -239,12 +231,8 @@ struct feature_benchmarker {
}
// Rate of 16-structural misses per 16-structural flip
double struct16_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped);
#endif
}
@@ -258,12 +246,8 @@ struct feature_benchmarker {
}
// Rate of UTF-8 misses per UTF-8 flip
double utf8_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped);
#endif
}
// Extra cost of having escapes in a block
double escape_cost(BenchmarkStage stage) const {
@@ -275,12 +259,8 @@ struct feature_benchmarker {
}
// Rate of escape misses per escape flip
double escape_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped);
#endif
}
@@ -378,22 +358,6 @@ struct feature_benchmarker {
}
};
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
double calc_misses = features.calc_expected_misses(stage, results);
double calc_miss_cost = features.calc_expected_miss_cost(stage, results);
printf(" | %-8s ", benchmark_stage_name(stage));
printf("| %-15s ", filename);
printf("| %8.3g ", features.calc_expected_feature_cost(stage, results));
printf("| %8.3g ", calc_miss_cost);
printf("| %8.3g ", calc);
printf("| %8.3g ", actual);
printf("| %+8.3g ", actual - calc);
printf("| %13llu ", (long long unsigned)(calc_misses));
}
#else
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
@@ -417,7 +381,6 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
}
printf("|\n");
}
#endif
int main(int argc, char *argv[]) {
// Read options
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef _BENCHMARK_H_
#define _BENCHMARK_H_
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
/*
* Prints the best number of operations per cycle where
+4 -11
View File
@@ -1,7 +1,8 @@
#ifndef __BENCHMARKER_H
#define __BENCHMARKER_H
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include "simdjson.h"
#include <cassert>
@@ -28,11 +29,9 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
#include "simdjson.h"
#include <functional>
@@ -423,18 +422,12 @@ struct benchmarker {
stage.instructions() / static_cast<double>(stats->structurals),
stage.instructions() / static_cast<double>(stage.cycles())
);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
// NOTE: removed cycles/miss because it is a somewhat misleading stat
printf("%s%-13s: %7.0f branch misses (%6.2f%%) - %.0f cache misses (%6.2f%%) - %.2f cache references\n",
printf("%s%-13s: %7.0f branch misses (%6.2f%%)\n",
prefix,
"Misses",
stage.branch_misses(),
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
stage.cache_misses(),
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
stage.cache_references()
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses())
);
#endif
}
}
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <random>
#include <vector>
+3 -2
View File
@@ -1,11 +1,12 @@
include_directories( .. ../linux )
include_directories( .. )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
link_libraries(counters)
add_executable(perfdiff perfdiff.cpp)
add_executable(parse parse.cpp)
add_executable(parse_stream parse_stream.cpp)
add_executable(statisticalmodel statisticalmodel.cpp)
add_executable(parse_noutf8validation parse.cpp)
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
+2 -2
View File
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <cassert>
#include <cctype>
@@ -24,7 +25,6 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
-207
View File
@@ -1,207 +0,0 @@
#include <iostream>
#include <unistd.h>
#include "simdjson.h"
#ifdef __linux__
#include "linux-perf-events.h"
#endif
size_t count_nonasciibytes(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += input[i] >> 7;
}
return count;
}
size_t count_backslash(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += (input[i] == '\\') ? 1 : 0;
}
return count;
}
struct stat_s {
size_t integer_count;
size_t float_count;
size_t string_count;
size_t backslash_count;
size_t non_ascii_byte_count;
size_t object_count;
size_t array_count;
size_t null_count;
size_t true_count;
size_t false_count;
size_t byte_count;
size_t structural_indexes_count;
bool valid;
};
using stat_t = struct stat_s;
simdjson_inline void simdjson_process_atom(stat_t &s,
simdjson::dom::element element) {
if (element.is<int64_t>()) {
s.integer_count++;
} else if(element.is<std::string_view>()) {
s.string_count++;
} else if(element.is<double>()) {
s.float_count++;
} else if (element.is<bool>()) {
bool v;
simdjson::error_code error;
if ((error = element.get(v))) { std::cerr << error << std::endl; abort(); }
if (v) {
s.true_count++;
} else {
s.false_count++;
}
} else if (element.is_null()) {
s.null_count++;
}
}
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
simdjson::error_code error;
if (element.is<simdjson::dom::array>()) {
s.array_count++;
simdjson::dom::array array;
if ((error = element.get(array))) { std::cerr << error << std::endl; abort(); }
for (auto child : array) {
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
simdjson_recurse(s, child);
} else {
simdjson_process_atom(s, child);
}
}
} else if (element.is<simdjson::dom::object>()) {
s.object_count++;
simdjson::dom::object object;
if ((error = element.get(object))) { std::cerr << error << std::endl; abort(); }
for (auto field : object) {
s.string_count++; // for key
if (field.value.is<simdjson::dom::array>() || field.value.is<simdjson::dom::object>()) {
simdjson_recurse(s, field.value);
} else {
simdjson_process_atom(s, field.value);
}
}
} else {
simdjson_process_atom(s, element);
}
}
stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
stat_t answer{};
simdjson::dom::parser parser;
simdjson::dom::element doc;
auto error = parser.parse(p).get(doc);
if (error) {
answer.valid = false;
return answer;
}
answer.valid = true;
answer.backslash_count =
count_backslash(reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.non_ascii_byte_count = count_nonasciibytes(
reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.byte_count = p.size();
answer.structural_indexes_count = parser.implementation->n_structural_indexes;
simdjson_recurse(answer, doc);
return answer;
}
int main(int argc, char *argv[]) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
abort();
}
}
#else
int optind = 1;
#endif
if (optind >= argc) {
std::cerr << "Reads json, prints stats. " << std::endl;
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
exit(1);
}
const char *filename = argv[optind];
if (optind + 1 < argc) {
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
<< std::endl;
}
simdjson::padded_string p;
auto error = simdjson::padded_string::load(filename).get(p);
if (error) {
std::cerr << "Could not load the file " << filename << std::endl;
return EXIT_FAILURE;
}
stat_t s = simdjson_compute_stats(p);
if (!s.valid) {
std::cerr << "not a valid JSON" << std::endl;
return EXIT_FAILURE;
}
printf("# integer_count float_count string_count backslash_count "
"non_ascii_byte_count object_count array_count null_count true_count "
"false_count byte_count structural_indexes_count ");
#ifdef __linux__
printf(" stage1_cycle_count stage1_instruction_count stage2_cycle_count "
" stage2_instruction_count stage3_cycle_count "
"stage3_instruction_count ");
#else
printf("(you are not under linux, so perf counters are disaabled)");
#endif
printf("\n");
printf("%zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu ", s.integer_count,
s.float_count, s.string_count, s.backslash_count,
s.non_ascii_byte_count, s.object_count, s.array_count, s.null_count,
s.true_count, s.false_count, s.byte_count, s.structural_indexes_count);
#ifdef __linux__
simdjson::dom::parser parser;
simdjson::error_code alloc_error = parser.allocate(p.size());
if (alloc_error) {
std::cerr << alloc_error << std::endl;
return EXIT_FAILURE;
}
const uint32_t iterations = p.size() < 1 * 1000 * 1000 ? 1000 : 50;
std::vector<int> evts;
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
LinuxEvents<PERF_TYPE_HARDWARE> unified(evts);
unsigned long cy1 = 0, cy2 = 0;
unsigned long cl1 = 0, cl2 = 0;
std::vector<unsigned long long> results;
results.resize(evts.size());
for (uint32_t i = 0; i < iterations; i++) {
unified.start();
// The default template is simdjson::architecture::NATIVE.
bool isok = (parser.implementation->stage1((const uint8_t *)p.data(), p.size(), simdjson::stage1_mode::regular) == simdjson::SUCCESS);
unified.end(results);
cy1 += results[0];
cl1 += results[1];
unified.start();
isok = isok && (parser.implementation->stage2(parser.doc) == simdjson::SUCCESS);
unified.end(results);
cy2 += results[0];
cl2 += results[1];
if (!isok) {
std::cerr << "failure?" << std::endl;
}
}
printf("%f %f %f %f ", static_cast<double>(cy1) / static_cast<double>(iterations), static_cast<double>(cl1) / static_cast<double>(iterations),
static_cast<double>(cy2) / static_cast<double>(iterations), static_cast<double>(cl2) / static_cast<double>(iterations));
#endif // __linux__
printf("\n");
return EXIT_SUCCESS;
}
-201
View File
@@ -1,201 +0,0 @@
#ifndef __EVENT_COUNTER_H
#define __EVENT_COUNTER_H
#ifndef SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
#ifdef __aarch64__
// on ARM, we use just cycles and instructions
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 1
#else
// elsewhere, we try to use four counters.
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 0
#endif
#endif
#include <cassert>
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#ifdef __linux__
#include "linux-perf-events.h"
#include <libgen.h>
#endif
#if __APPLE__ && __aarch64__
#include "apple/apple_arm_events.h"
#endif
#include "simdjson.h"
using std::string;
using std::vector;
using std::chrono::steady_clock;
using std::chrono::time_point;
using std::chrono::duration;
struct event_count {
duration<double> elapsed;
vector<unsigned long long> event_counts;
event_count() : elapsed(0), event_counts{0,0,0,0,0} {}
event_count(const duration<double> _elapsed, const vector<unsigned long long> _event_counts) : elapsed(_elapsed), event_counts(_event_counts) {}
event_count(const event_count& other): elapsed(other.elapsed), event_counts(other.event_counts) { }
// The types of counters (so we can read the getter more easily)
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS
};
#else
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS,
BRANCH_MISSES,
CACHE_REFERENCES,
CACHE_MISSES
};
#endif
double elapsed_sec() const { return duration<double>(elapsed).count(); }
double elapsed_ns() const { return duration<double, std::nano>(elapsed).count(); }
double cycles() const { return static_cast<double>(event_counts[CPU_CYCLES]); }
double instructions() const { return static_cast<double>(event_counts[INSTRUCTIONS]); }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return static_cast<double>(event_counts[BRANCH_MISSES]); }
double cache_references() const { return static_cast<double>(event_counts[CACHE_REFERENCES]); }
double cache_misses() const { return static_cast<double>(event_counts[CACHE_MISSES]); }
#endif
event_count& operator=(const event_count& other) {
this->elapsed = other.elapsed;
this->event_counts = other.event_counts;
return *this;
}
event_count operator+(const event_count& other) const {
return event_count(elapsed+other.elapsed, {
event_counts[0]+other.event_counts[0],
event_counts[1]+other.event_counts[1],
event_counts[2]+other.event_counts[2],
event_counts[3]+other.event_counts[3],
event_counts[4]+other.event_counts[4],
});
}
void operator+=(const event_count& other) {
*this = *this + other;
}
};
struct event_aggregate {
int iterations = 0;
event_count total{};
event_count best{};
event_count worst{};
event_aggregate() {}
void operator<<(const event_count& other) {
if (iterations == 0 || other.elapsed < best.elapsed) {
best = other;
}
if (iterations == 0 || other.elapsed > worst.elapsed) {
worst = other;
}
iterations++;
total += other;
}
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
double total_elapsed_ns() const { return total.elapsed_ns(); }
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
double cycles() const { return total.cycles() / iterations; }
double instructions() const { return total.instructions() / iterations; }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return total.branch_misses() / iterations; }
double cache_references() const { return total.cache_references() / iterations; }
double cache_misses() const { return total.cache_misses() / iterations; }
#endif
};
struct event_collector {
event_count count{};
time_point<steady_clock> start_clock{};
#if defined(__linux__)
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
event_collector() : linux_events(vector<int>{
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
#else
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
PERF_COUNT_HW_BRANCH_MISSES,
PERF_COUNT_HW_CACHE_REFERENCES,
PERF_COUNT_HW_CACHE_MISSES
#endif
}) {}
bool has_events() {
return linux_events.is_working();
}
#elif __APPLE__ && __aarch64__
AppleEvents apple_events;
performance_counters diff;
event_collector() : diff(0) {
apple_events.setup_performance_counters();
}
bool has_events() {
return apple_events.setup_performance_counters();
}
#else
event_collector() {}
bool has_events() {
return false;
}
#endif
simdjson_inline void start() {
#if defined(__linux)
linux_events.start();
#elif __APPLE__ && __aarch64__
if(has_events()) { diff = apple_events.get_counters(); }
#endif
start_clock = steady_clock::now();
}
simdjson_inline event_count& end() {
time_point<steady_clock> end_clock = steady_clock::now();
#if defined(__linux)
linux_events.end(count.event_counts);
#elif __APPLE__ && __aarch64__
if(has_events()) {
performance_counters end = apple_events.get_counters();
diff = end - diff;
}
count.event_counts[0] = diff.cycles;
count.event_counts[1] = diff.instructions;
count.event_counts[2] = diff.missed_branches;
count.event_counts[3] = 0;
count.event_counts[4] = 0;
#endif
count.elapsed = end_clock - start_clock;
return count;
}
};
#endif
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef BENCHMARK_HELPERS_H
#define BENCHMARK_HELPERS_H
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <atomic>
event_collector collector;
+2 -13
View File
@@ -1,7 +1,8 @@
#pragma once
#include "simdjson.h"
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <iostream>
namespace json_benchmark {
@@ -58,11 +59,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) {
state.counters["instructions"] = events.instructions();
state.counters["cycles"] = events.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["branch_miss"] = events.branch_misses();
state.counters["cache_miss"] = events.cache_misses();
state.counters["cache_ref"] = events.cache_references();
#endif
state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration());
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration());
@@ -70,11 +67,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
state.counters["best_instructions"] = events.best.instructions();
state.counters["best_cycles"] = events.best.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["best_branch_miss"] = events.best.branch_misses();
state.counters["best_cache_miss"] = events.best.cache_misses();
state.counters["best_cache_ref"] = events.best.cache_references();
#endif
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration());
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
@@ -95,11 +88,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) {
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
#endif
}
label << " items=" << setw(10) << bench.items_per_iteration() << setw(0);
@@ -1,6 +1,7 @@
#pragma once
#include "json_benchmark/string_runner.h"
#include <fstream>
#include <map>
#include <string>
@@ -0,0 +1,32 @@
#pragma once
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
#include "large_random.h"
namespace large_random {
using namespace simdjson;
// Identical to simdjson_ondemand but uses get_range() for iteration.
// Demonstrates that the ranges wrapper has zero per-element overhead.
struct simdjson_ondemand_ranges {
static constexpr diff_flags DiffFlags = diff_flags::NONE;
ondemand::parser parser{};
bool run(simdjson::padded_string &json, std::vector<point> &result) {
auto doc = parser.iterate(json);
for (auto coord_result : ondemand::get_range(doc.get_array())) {
ondemand::object coord = coord_result;
result.emplace_back(json_benchmark::point{coord.find_field("x"), coord.find_field("y"), coord.find_field("z")});
}
return true;
}
};
BENCHMARK_TEMPLATE(large_random, simdjson_ondemand_ranges)->UseManualTime();
} // namespace large_random
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
+6 -1
View File
@@ -103,7 +103,12 @@ error_code Sax::RunNoExcept(const padded_string &json) noexcept {
error_code Sax::Allocate(size_t new_capacity) {
// 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
-105
View File
@@ -1,105 +0,0 @@
#pragma once
#ifdef __linux__
#include <asm/unistd.h> // for __NR_perf_event_open
#include <linux/perf_event.h> // for perf event constants
#include <sys/ioctl.h> // for ioctl
#include <unistd.h> // for syscall
#include <cerrno> // for errno
#include <cstring> // for memset
#include <stdexcept>
#include <iostream>
#include <vector>
template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
int fd;
bool working;
perf_event_attr attribs{};
size_t num_events{};
std::vector<uint64_t> temp_result_vec{};
std::vector<uint64_t> ids{};
public:
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
memset(&attribs, 0, sizeof(attribs));
attribs.type = TYPE;
attribs.size = sizeof(attribs);
attribs.disabled = 1;
attribs.exclude_kernel = 1;
attribs.exclude_hv = 1;
attribs.sample_period = 0;
attribs.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
const int pid = 0; // the current process
const int cpu = -1; // all CPUs
const unsigned long flags = 0;
int group = -1; // no group
num_events = config_vec.size();
ids.resize(config_vec.size());
uint32_t i = 0;
for (auto config : config_vec) {
attribs.config = config;
int _fd = static_cast<int>(syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags));
if (_fd == -1) {
report_error("perf_event_open");
}
ioctl(_fd, PERF_EVENT_IOC_ID, &ids[i++]);
if (group == -1) {
group = _fd;
fd = _fd;
}
}
temp_result_vec.resize(num_events * 2 + 1);
}
~LinuxEvents() { if (fd != -1) { close(fd); } }
inline void start() {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_RESET)");
}
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
}
}
}
inline void end(std::vector<unsigned long long> &results) {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
}
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
report_error("read");
}
}
// our actual results are in slots 1,3,5, ... of this structure
for (uint32_t i = 1; i < temp_result_vec.size(); i += 2) {
results[i / 2] = temp_result_vec[i];
}
for (uint32_t i = 2; i < temp_result_vec.size(); i += 2) {
if(ids[i/2-1] != temp_result_vec[i]) {
report_error("event mismatch");
}
}
}
bool is_working() {
return working;
}
private:
void report_error(const std::string &) {
working = false;
}
};
#endif
@@ -1,6 +1,7 @@
#ifndef BENCHMARK_HELPER_HPP
#define BENCHMARK_HELPER_HPP
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <atomic>
inline event_collector &get_collector() {
@@ -0,0 +1,88 @@
#ifndef GLAZE_CITM_CATALOG_DATA_H
#define GLAZE_CITM_CATALOG_DATA_H
#include <cstdint>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include <glaze/glaze.hpp>
// Glaze-specific shadow types. We mirror the Rust serde struct here (rather
// than the C++ CitmCatalog) so that fields the source JSON encodes as `null`
// (e.g. CITMEvent.name) parse cleanly into std::optional. This matches what
// the Rust benchmark does, and the resulting JSON output volume is therefore
// directly comparable to the Rust numbers.
struct GlazeCITMPrice {
uint64_t amount;
uint64_t audienceSubCategoryId;
uint64_t seatCategoryId;
};
struct GlazeCITMArea {
uint64_t areaId;
std::vector<uint64_t> blockIds;
};
struct GlazeCITMSeatCategory {
std::vector<GlazeCITMArea> areas;
uint64_t seatCategoryId;
};
struct GlazeCITMPerformance {
uint64_t id;
uint64_t eventId;
std::optional<std::string> logo;
std::optional<std::string> name;
std::vector<GlazeCITMPrice> prices;
std::vector<GlazeCITMSeatCategory> seatCategories;
std::optional<std::string> seatMapImage;
uint64_t start;
std::string venueCode;
};
struct GlazeCITMEvent {
uint64_t id;
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> logo;
std::vector<uint64_t> subTopicIds;
std::optional<std::string> subjectCode;
std::optional<std::string> subtitle;
std::vector<uint64_t> topicIds;
};
struct GlazeCitmCatalog {
std::map<std::string, GlazeCITMEvent> events;
std::vector<GlazeCITMPerformance> performances;
};
inline GlazeCitmCatalog glaze_deserialize_citm(const std::string &json_str) {
GlazeCitmCatalog data;
constexpr glz::opts opts{.error_on_unknown_keys = false};
auto err = glz::read<opts>(data, json_str);
if (err) {
throw std::runtime_error("glaze citm parse error: " +
glz::format_error(err, json_str));
}
return data;
}
inline std::string glaze_serialize_citm(const GlazeCitmCatalog &data) {
std::string out;
// skip_null_members = false: emit `"field":null` for unset optionals so the
// output has the same field count as simdjson's (which writes `"field":""`).
// Glaze still produces 4-char `null` vs simdjson's 2-char `""`, so it's not
// byte-identical, but the work-per-field is comparable.
constexpr glz::opts opts{.skip_null_members = false};
auto err = glz::write<opts>(data, out);
if (err) {
throw std::runtime_error("glaze citm write error");
}
return out;
}
#endif // GLAZE_CITM_CATALOG_DATA_H
@@ -0,0 +1,95 @@
// Standalone Glaze benchmark for CITM Catalog (see glaze_twitter_bench.cpp
// header comment for context).
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include "../citm_catalog_benchmark/glaze_citm_catalog_data.h"
#include "../benchmark_utils/benchmark_helper.h"
namespace {
std::string read_file(const std::string &filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 65536;
std::ifstream stream(filename, std::ios::binary);
if (!stream) {
std::cerr << "Could not open file: " << filename << std::endl;
std::exit(EXIT_FAILURE);
}
stream.exceptions(std::ios_base::badbit);
std::string out;
std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount()));
}
out.append(buf, 0, size_t(stream.gcount()));
return out;
}
void bench_glaze_serialization(GlazeCitmCatalog &data) {
std::string output = glaze_serialize_citm(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_glaze",
bench([&data, &measured_volume, &output_volume]() {
std::string output = glaze_serialize_citm(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
void bench_glaze_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_glaze_parsing",
bench([&json_str, &result]() {
try {
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
} // namespace
int main(int argc, char *argv[]) {
const char *json_file = std::getenv("CITM_JSON");
if (!json_file) {
json_file = "buildreflect/jsonexamples/citm_catalog.json";
}
if (argc > 1) {
json_file = argv[1];
}
std::string json_str = read_file(json_file);
const char *mode = std::getenv("BENCH_MODE");
if (!mode) mode = "all";
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
if (std::string(mode) == "all" || std::string(mode) == "parse") {
printf("\n=== Glaze CITM Parsing ===\n");
bench_glaze_parsing(json_str);
}
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
printf("\n=== Glaze CITM Serialization ===\n");
bench_glaze_serialization(data);
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,99 @@
// Standalone Glaze benchmark for Twitter, built with g++ instead of the
// p2996 clang fork (which crashes on Glaze's heavy template metaprogramming).
// Reuses the same data structs and bench() helper as the in-tree benchmarks
// so the throughput numbers are directly comparable.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "../twitter_benchmark/twitter_data.h"
#include "../twitter_benchmark/glaze_twitter_data.h"
#include "../benchmark_utils/benchmark_helper.h"
namespace {
std::string read_file(const std::string &filename) {
printf("# Reading file %s\n", filename.c_str());
constexpr size_t read_size = 4096;
std::ifstream stream(filename);
if (!stream) {
std::cerr << "Could not open file: " << filename << std::endl;
std::exit(EXIT_FAILURE);
}
stream.exceptions(std::ios_base::badbit);
std::string out;
std::string buf(read_size, '\0');
while (stream.read(&buf[0], read_size)) {
out.append(buf, 0, size_t(stream.gcount()));
}
out.append(buf, 0, size_t(stream.gcount()));
return out;
}
void bench_glaze_serialization(TwitterData &data) {
std::string output = glaze_serialize(data);
size_t output_volume = output.size();
printf("# output volume: %zu bytes\n", output_volume);
volatile size_t measured_volume = 0;
pretty_print(1, output_volume, "bench_glaze",
bench([&data, &measured_volume, &output_volume]() {
std::string output = glaze_serialize(data);
measured_volume = output.size();
if (measured_volume != output_volume) {
printf("mismatch\n");
}
}));
}
void bench_glaze_parsing(const std::string &json_str) {
size_t input_volume = json_str.size();
printf("# input volume: %zu bytes\n", input_volume);
volatile bool result = true;
pretty_print(1, input_volume, "bench_glaze_parsing",
bench([&json_str, &result]() {
try {
TwitterData data = glaze_deserialize(json_str);
result = true;
} catch (...) {
result = false;
printf("parse error\n");
}
}));
}
} // namespace
int main(int argc, char *argv[]) {
const char *json_file = std::getenv("TWITTER_JSON");
if (!json_file) {
json_file = "buildreflect/jsonexamples/twitter.json";
}
if (argc > 1) {
json_file = argv[1];
}
std::string json_str = read_file(json_file);
const char *mode = std::getenv("BENCH_MODE");
if (!mode) mode = "all";
TwitterData data = glaze_deserialize(json_str);
if (std::string(mode) == "all" || std::string(mode) == "parse") {
printf("\n=== Glaze Twitter Parsing ===\n");
bench_glaze_parsing(json_str);
}
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
printf("\n=== Glaze Twitter Serialization ===\n");
bench_glaze_serialization(data);
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,31 @@
#ifndef GLAZE_TWITTER_DATA_H
#define GLAZE_TWITTER_DATA_H
#include "twitter_data.h"
#include <glaze/glaze.hpp>
#include <stdexcept>
#include <string>
// Glaze auto-reflects aggregate types whose field names already match JSON
// keys (snake_case here matches the JSON), so no glz::meta is required.
inline TwitterData glaze_deserialize(const std::string &json_str) {
TwitterData data;
constexpr glz::opts opts{.error_on_unknown_keys = false};
auto err = glz::read<opts>(data, json_str);
if (err) {
throw std::runtime_error("glaze parse error: " + glz::format_error(err, json_str));
}
return data;
}
inline std::string glaze_serialize(const TwitterData &data) {
std::string out;
auto err = glz::write_json(data, out);
if (err) {
throw std::runtime_error("glaze write error");
}
return out;
}
#endif // GLAZE_TWITTER_DATA_H
+8
View File
@@ -20,6 +20,14 @@ if(SIMDJSON_GOOGLE_BENCHMARKS)
)
endif()
CPMAddPackage(
NAME counters
URL https://github.com/lemire/counters/archive/refs/tags/v3.1.0.zip
OPTIONS
"COUNTERS_BUILD_TESTS OFF"
"COUNTERS_INSTALL OFF"
)
CPMAddPackage(
NAME simdjson-data
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
+120 -27
View File
@@ -28,6 +28,9 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
- [UTF-8 validation (alone)](#utf-8-validation-alone)
- [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)
- [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)
@@ -56,7 +59,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.
@@ -269,10 +272,35 @@ ondemand::document doc = parser.iterate(simdjson::pad(json));
We recommend against creating many `std::string` or many `std::padded_string` instances in your application to store your JSON data.
Consider reusing the same buffers and limiting memory allocations.
**Memory-file mapping (non-Windows).** You can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk:
**Memory-file mapping.** You can use `simdjson::padded_memory_map` to create a
`simdjson::padded_string_view` from a file on disk. On POSIX systems (Linux,
macOS, BSD, ...) it uses `mmap` for true zero-copy access and is always
available. On Windows it is an **opt-in** feature because it relies on the
`CreateFileMapping2` / `MapViewOfFile3` APIs (Windows 10, version 1803 or
later) which are exported from `onecore.lib` rather than the default
`kernel32.lib`. To enable it, you must satisfy **all** of the following:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before including simdjson, in every translation
unit that uses `padded_memory_map`.
The Windows implementation then uses `CreateFileMapping2` / `MapViewOfFile3`
for true zero-copy access whenever possible, with a transparent
buffered-read fallback for files that end too close to a page boundary.
The availability of the class can be tested with the preprocessor macro
`SIMDJSON_HAS_PADDED_MEMORY_MAP`.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view();
@@ -305,7 +333,7 @@ Some users may want to browse code along with the compiled assembly:
| `simdjson::pad(std::string&)` | Adds padding if needed | Returns `padded_string_view` pointing to the (possibly resized) string | References original string | Recommended to silence sanitizers when using `std::string`. |
| `padded_string(data, length)` or `padded_string(std::string)` | Automatic (copies into padded buffer) | Explicit copy into owned padded buffer | Owned by `padded_string` | Safe when you want full ownership and padding guaranteed. |
| `padded_string_view` (manual) | User guarantees `SIMDJSON_PADDING` extra bytes after the viewed length | User provides pointer + length + capacity | Non-owning view | Low-level; requires careful buffer management. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping (non-Windows only) | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Advanced; efficient for large files on Linux/macOS/etc. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping / padded read | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Always available on POSIX (zero-copy `mmap`). On Windows, opt-in via `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON` (requires Windows 10 1803+ and links `onecore.lib`) and `#include <windows.h>` before simdjson; uses `CreateFileMapping2` + `MapViewOfFile3`. |
Documents are iterators
@@ -1415,6 +1443,8 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
that are not made by Toyota.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
### 3. Using static reflection (C++26)
@@ -1491,6 +1521,10 @@ void f() {
}
```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
#### Special cases
However, there are instances where the construction cannot
@@ -1568,6 +1602,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;
@@ -1808,15 +1846,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"(
@@ -1845,31 +1883,86 @@ 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.
## 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
+26 -3
View File
@@ -127,11 +127,31 @@ codepage, and they may call SetFileApisToOEM accordingly.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view`
from a file on disk without copying the file contents into your own buffer.
On POSIX systems (Linux, macOS, BSD, ...) it uses `mmap` for true zero-copy
access. On Windows it is available as an **opt-in** feature and requires:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`.
When enabled on Windows, the implementation uses `CreateFileMapping2` and
`MapViewOfFile3` for true zero-copy mapping whenever the file does not end
within `SIMDJSON_PADDING` bytes of a page boundary; otherwise it falls back
to reading the file into a padded heap buffer. If those requirements are
not met, the class is not declared and the code below will fail to compile.
```cpp
// if the macro _WIN32 is defined, this will not work since we do not support Windows
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(TWITTER_JSON);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
@@ -770,6 +790,9 @@ void basics_treewalk_1() {
}
```
Notice that we do not include `dom::element_type::BIGINT` in this example
as `dom::element_type::BIGINT` type is only generated if the parser was
set to support big integers (`parser.number_as_string(true)`).
Reusing the parser for maximum efficiency
+79 -5
View File
@@ -22,6 +22,7 @@ Contents
- [Threads](#threads)
- [Support](#support)
- [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams)
@@ -156,13 +157,79 @@ for (auto doc : docs) {
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most efficient
way to feed `iterate_many` is to use `simdjson::padded_memory_map`. It returns
a `padded_string_view` with the right amount of trailing padding, so you can
hand it straight to `iterate_many` without ever copying the file contents into
your own buffer.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::ondemand::parser parser;
simdjson::ondemand::document_stream stream;
auto error = parser.iterate_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.iterate(...)`:
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
@@ -512,6 +579,9 @@ Otherwise you may use this longer version for explicit handling of errors:
}
```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
C++26 features (static reflection)
-----------------------------------
@@ -662,3 +732,7 @@ for (auto doc : stream) {
In every case, the user-defined type (`Car` here) does not need a hand-written
`tag_invoke` overload: the library generates the deserialization code from the
type's public data members at compile time.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
+73 -6
View File
@@ -18,6 +18,7 @@ Contents
- [How it works](#how-it-works)
- [Support](#support)
- [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams)
@@ -218,17 +219,83 @@ got full document at 29
**Advanced feature:**
On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most
efficient way to feed `parse_many` is to use `simdjson::padded_memory_map`.
It returns a `padded_string_view` with the right amount of trailing padding,
so you can pass it directly to `parse_many` without copying the file content
into your own buffer first.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.parse(...)`:
```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
simdjson::dom::element doc = parser.parse(view); // parse the JSON
```
Incomplete streams
+7 -1
View File
@@ -208,7 +208,7 @@ You can still make sure of this capability in your code if you are an expert
programmer and you are willing to silence sanitizer warnings.
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view` or a C-style string with length. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
Example usage:
@@ -216,6 +216,12 @@ Example usage:
std::string_view json = get_json_data();
simdjson::padded_input input(json); // Automatically pads if needed
auto result = parser.parse(input);
// Also works with std::string, considering capacity
std::string json_str = get_json_string();
json_str.reserve(json_str.size() + 100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
auto result2 = parser.parse(input2);
```
This simplifies padding management compared to manually checking and allocating.
+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
-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);
+2
View File
@@ -22,6 +22,8 @@ enum class element_type {
STRING = '"', ///< std::string_view
BOOL = 't', ///< bool
NULL_VALUE = 'n', ///< null
/// The BIGINT type is for integers that do not fit in 64 bits. It is only present
// if you set parser.number_as_string(true).
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
};
+19 -1
View File
@@ -227,8 +227,18 @@ inline size_t structure_analyzer::estimate_string_length(std::string_view s) con
}
inline size_t structure_analyzer::estimate_number_length(double d) const {
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];
@@ -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;
+6
View File
@@ -181,6 +181,9 @@ inline simdjson_result<document_stream> parser::parse_many(const std::string &s,
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size) noexcept {
return parse_many(s.data(), s.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size) noexcept {
return parse_many(v.data(), v.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
@@ -217,6 +220,9 @@ inline simdjson_result<document_stream> parser::parse_many(const std::string &s,
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept {
return parse_many(v.data(), v.length(), batch_size, format);
}
simdjson_inline size_t parser::capacity() const noexcept {
return implementation ? implementation->capacity() : 0;
+12
View File
@@ -490,6 +490,16 @@ public:
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
*
* Because padded_string_view guarantees SIMDJSON_PADDING trailing bytes, this
* overload is safe to use with buffers that the caller owns elsewhere (for
* example, a padded_memory_map), with no extra copy. Without this overload,
* passing a padded_string_view would silently bind to the padded_string
* overload via an implicit conversion, allocating and copying the input, and
* because that temporary is destroyed at the end of the full-expression
* leaving the returned document_stream pointing at freed memory. */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
/** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
@@ -510,6 +520,8 @@ public:
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept;
/**
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
+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
+280 -98
View File
@@ -21,23 +21,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 +135,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 +241,83 @@ 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) {
simdjson_really_inline constexpr void atom(writer &w, const T &t) {
// Per-field block: ensure key+value worst case, then write key + value
// through the writer's local pos. For arithmetic fields, the integer
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
// never round-trips through memory.
int i = 0;
b.append('{');
if (!w.ensure(1)) return;
w.ptr[w.pos++] = '{';
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
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:]);
constexpr auto first_key = std::define_static_string(
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
constexpr auto rest_key = std::define_static_string(
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
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 (i == 0) {
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
w.pos += first_key_len;
} else {
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
w.pos += rest_key_len;
}
atom(w, t.[:dm:]);
i++;
};
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 +327,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 +365,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 +392,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 +417,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 +468,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 +480,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
@@ -561,62 +563,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 +582,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 +681,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 +745,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 +758,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 +794,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 +815,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; }
//
@@ -22,6 +22,7 @@
#include "simdjson/generic/ondemand/field.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
@@ -39,6 +40,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/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
View File
@@ -40,6 +40,13 @@ class token_iterator;
class value;
class value_iterator;
#if SIMDJSON_SUPPORTS_RANGES
class array_range;
class array_range_iterator;
class object_range;
class object_range_iterator;
#endif // SIMDJSON_SUPPORTS_RANGES
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@@ -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);
+21 -23
View File
@@ -177,9 +177,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;
@@ -189,34 +193,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);
}
}
}
@@ -347,9 +339,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 {
+18 -4
View File
@@ -172,13 +172,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
@@ -328,7 +336,13 @@ public:
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]);
@@ -272,7 +275,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();
@@ -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 */
@@ -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
@@ -267,6 +267,22 @@ constexpr bool user_defined_type = (std::is_class_v<T>
!concepts::appendable_containers<T>);
// Compile-time predicate: does T have any std::optional member?
// Used to decide whether single-pass dispatch is worth it.
template <typename T>
consteval bool struct_has_optional_member() {
bool result = false;
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)) {
using FieldT = [: std::meta::type_of(mem) :];
if constexpr (concepts::optional_type<FieldT>) {
result = true;
}
}
};
return result;
}
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 {
@@ -276,25 +292,42 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
} else {
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 (concepts::optional_type<decltype(out.[:mem:])>) {
// for optional members, it's ok if the key is missing
auto error = obj[key].get(out.[:mem:]);
if (error && error != NO_SUCH_FIELD) {
if(error == NO_SUCH_FIELD) {
out.[:mem:].reset();
continue;
if constexpr (struct_has_optional_member<T>()) {
// Single-pass dispatch: walk each JSON object field once, dispatching
// to the matching struct member via a compile-time-generated key
// comparison chain (with length pre-filter). This avoids the O(K)
// full-object scan that obj[key] does for *absent* optional fields.
// Worth it when the struct has optionals because some are usually missing.
for (auto field : obj) {
std::string_view key;
SIMDJSON_TRY(field.unescaped_key().get(key));
bool matched = false;
const size_t key_size = key.size();
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 name = std::define_static_string(std::meta::identifier_of(mem));
constexpr size_t name_size = name.size();
if (!matched && key_size == name_size && key == name) {
SIMDJSON_TRY(field.value().get(out.[:mem:]));
matched = true;
}
return error;
}
} else {
// for non-optional members, the key must be present
};
// Unmatched value is skipped automatically by object_iterator::operator++().
(void)matched;
}
} else {
// Per-field obj[key] dispatch: for structs with all-required fields,
// this is O(1) per field when the JSON keys are in declaration order
// (find_field_unordered's fast path). Beats single-pass on dense
// structs (e.g. Twitter Status with 22 always-present fields).
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));
SIMDJSON_TRY(obj[key].get(out.[:mem:]));
}
}
};
};
}
return simdjson::SUCCESS;
}
+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
+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.
+136 -3
View File
@@ -10,13 +10,18 @@
#include <climits>
#include <cwchar>
#ifndef _WIN32
#if SIMDJSON_HAS_UNISTD_H
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
// On Windows, `padded_memory_map` (when it is enabled) depends on types and
// functions declared in <windows.h>. We deliberately do NOT include that
// header here: users of simdjson who want `padded_memory_map` on Windows
// must include <windows.h> themselves *before* including this header. See
// padded_string.h for the detection logic.
namespace simdjson {
namespace internal {
@@ -385,7 +390,9 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
}
#ifndef _WIN32
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#if SIMDJSON_HAS_UNISTD_H
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
int fd = open(filename, O_RDONLY);
@@ -421,7 +428,132 @@ simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
munmap(const_cast<char *>(data), size + simdjson::SIMDJSON_PADDING);
}
}
#elif defined(_WIN32)
// Windows zero-copy implementation using placeholder virtual memory.
//
// We use the modern Windows memory APIs (VirtualAlloc2, CreateFileMapping2,
// MapViewOfFile3 — available since Windows 10 1803) to map the file into a
// contiguous virtual address range that includes at least SIMDJSON_PADDING
// zero bytes after the file content, with no data copies.
//
// Strategy:
// 1. If rounding the file size up to the allocation granularity already
// exceeds file_size + SIMDJSON_PADDING, the OS page zero-fill provides
// the padding and we use a simple MapViewOfFile3 call.
// 2. Otherwise we reserve a contiguous placeholder region via VirtualAlloc2,
// split it at the granularity-aligned file boundary, map the file into
// the first part, and commit zero pages for the second part (padding).
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
HANDLE file_handle = ::CreateFileA(
filename, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return;
}
LARGE_INTEGER file_size_li;
if (!::GetFileSizeEx(file_handle, &file_size_li) || file_size_li.QuadPart < 0) {
::CloseHandle(file_handle);
return;
}
#if SIMDJSON_IS_32BITS
if (static_cast<unsigned long long>(file_size_li.QuadPart) >
static_cast<unsigned long long>(SIZE_MAX - simdjson::SIMDJSON_PADDING)) {
::CloseHandle(file_handle);
return;
}
#endif
size = static_cast<size_t>(file_size_li.QuadPart);
if (size == 0) {
::CloseHandle(file_handle);
return;
}
HANDLE section = ::CreateFileMapping2(
file_handle, NULL, FILE_MAP_READ, PAGE_READONLY,
0, 0, NULL, NULL, 0);
::CloseHandle(file_handle);
if (section == NULL) {
return;
}
SYSTEM_INFO si;
::GetSystemInfo(&si);
const size_t granularity = static_cast<size_t>(si.dwAllocationGranularity);
const size_t file_region = (size + granularity - 1) & ~(granularity - 1);
const size_t total_needed = size + simdjson::SIMDJSON_PADDING;
if (file_region >= total_needed) {
// The zero-fill in the last page already covers the padding.
PVOID view = ::MapViewOfFile3(
section, ::GetCurrentProcess(), NULL, 0, 0,
0, PAGE_READONLY, NULL, 0);
::CloseHandle(section);
if (view != NULL) {
data = static_cast<const char *>(view);
}
return;
}
// We need extra zero pages beyond the file region. Use the placeholder API
// to get a contiguous virtual address range spanning both the file mapping
// and the zero-filled padding.
const size_t padding_region =
((total_needed - file_region) + granularity - 1) & ~(granularity - 1);
const size_t reserve_size = file_region + padding_region;
// Reserve a contiguous placeholder.
PVOID placeholder = ::VirtualAlloc2(
::GetCurrentProcess(), NULL, reserve_size,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, NULL, 0);
if (placeholder == NULL) {
::CloseHandle(section);
return;
}
// Split into two placeholders at the file_region boundary.
if (!::VirtualFree(placeholder, file_region,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
::VirtualFree(placeholder, 0, MEM_RELEASE);
::CloseHandle(section);
return;
}
// Map the file into the first placeholder.
PVOID file_view = ::MapViewOfFile3(
section, ::GetCurrentProcess(), placeholder, 0, file_region,
MEM_REPLACE_PLACEHOLDER, PAGE_READONLY, NULL, 0);
::CloseHandle(section);
if (file_view == NULL) {
::VirtualFree(placeholder, 0, MEM_RELEASE);
::VirtualFree(static_cast<char *>(placeholder) + file_region,
0, MEM_RELEASE);
return;
}
// Commit zero pages in the second placeholder (the padding).
void *pad = static_cast<char *>(placeholder) + file_region;
PVOID padding_ptr = ::VirtualAlloc2(
::GetCurrentProcess(), pad, padding_region,
MEM_REPLACE_PLACEHOLDER | MEM_COMMIT, PAGE_READONLY, NULL, 0);
if (padding_ptr == NULL) {
::UnmapViewOfFile(file_view);
::VirtualFree(pad, 0, MEM_RELEASE);
return;
}
data = static_cast<const char *>(file_view);
padding_view_ = padding_ptr;
}
simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
if (data == nullptr) { return; }
::UnmapViewOfFile(data);
if (padding_view_ != nullptr) {
::VirtualFree(padding_view_, 0, MEM_RELEASE);
}
}
#endif // POSIX or _WIN32
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
if(!is_valid()) {
@@ -433,7 +565,8 @@ simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noe
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
return data != nullptr;
}
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
} // namespace simdjson
+30 -7
View File
@@ -277,11 +277,26 @@ inline std::ostream& operator<<(std::ostream& out, const padded_string& s) { ret
inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string> &s) noexcept(false) { return out << s.value(); }
#endif
#ifndef _WIN32
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
/**
* A class representing a memory-mapped file with padding.
* It is only available on non-Windows platforms, as Windows has different APIs for memory mapping.
*
* On POSIX systems (Linux, macOS, BSD, ...), this uses `mmap` to map the file
* contents directly into memory, which is efficient for large files (no copy).
*
* On Windows, this class is disabled by default and must be opted into at
* build time by defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`. When
* enabled, `<windows.h>` must also be included before `<simdjson.h>` and
* the compilation must target Windows 10, version 1803 or later. The
* Windows implementation uses the modern memory APIs (`VirtualAlloc2`,
* `CreateFileMapping2`, `MapViewOfFile3`) with the placeholder virtual
* memory mechanism to always achieve true zero-copy mapping with
* contiguous zero-filled padding.
*
* Either way, the resulting `padded_string_view` carries at least
* `SIMDJSON_PADDING` bytes of accessible zero-filled padding after the file
* content, so it can be consumed directly by the simdjson parsers (including
* `parse_many` / `iterate_many`).
*/
class padded_memory_map {
public:
@@ -289,9 +304,11 @@ public:
* Create a new padded memory map for the given file.
* After creating the memory map, you can call view() to get a padded_string_view of the file content.
* The memory map will be automatically released when the padded_memory_map instance is destroyed.
* Note that the file content is not copied, so this is efficient for large files. However,
* the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found,
* permission denied, etc.), the memory map will be invalid and view() will return an empty view.
* On POSIX systems, the file content is not copied, so this is efficient for large files.
* On Windows, the file is mapped into memory via `MapViewOfFile3` (zero-copy).
* In all cases, the file must remain unchanged while the memory map is in use.
* In case of error (e.g., file not found, permission denied, etc.), the memory map will be
* invalid and view() will return an empty view.
* You can check if the memory map is valid by calling is_valid() before using view().
*
* @param filename the path to the file to memory-map.
@@ -328,8 +345,14 @@ private:
padded_memory_map &operator=(const padded_memory_map &) = delete;
const char *data{nullptr};
size_t size{0};
#ifdef _WIN32
// When the file ends near an allocation-granularity boundary, we use the
// placeholder API to append zero-filled padding pages. This pointer tracks
// that region so the destructor can release it with VirtualFree.
void *padding_view_{nullptr};
#endif
};
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
+2 -2
View File
@@ -7,7 +7,7 @@
#include <cstring> /* memcmp */
// for page size computation.
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#if SIMDJSON_HAS_UNISTD_H
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/sysctl.h>
@@ -113,7 +113,7 @@ inline uint32_t get_page_size() noexcept {
return static_cast<std::uint32_t>(si.dwPageSize);
}();
return cached;
#elif defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#elif SIMDJSON_HAS_UNISTD_H
static const uint32_t cached = []() -> uint32_t {
long page_size = sysconf(_SC_PAGESIZE);
if (page_size > 0) {
+48
View File
@@ -285,5 +285,53 @@ using std::size_t;
#endif
#endif
#ifndef SIMDJSON_HAS_UNISTD_H
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#define SIMDJSON_HAS_UNISTD_H 1
#else
#define SIMDJSON_HAS_UNISTD_H 0
#endif
#endif
// padded_memory_map availability.
//
// On POSIX platforms the class is always available: the implementation uses
// `mmap` (and a trailing anonymous page for padding) from <sys/mman.h>.
//
// On Windows the class is disabled by default and must be explicitly
// opted into by defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`. Enabling
// it requires:
// 1. `<windows.h>` has been included *before* `<simdjson.h>` (so that
// this header can see the Win32 types and the `_WINDOWS_` include
// guard),
// 2. the compilation targets Windows 10, version 1803 or later
// (i.e. `NTDDI_VERSION >= NTDDI_WIN10_RS4`, `0x0A000005`). This is
// required because the implementation relies on the modern memory
// APIs introduced with that version (`CreateFileMapping2` /
// `MapViewOfFile3`),
// 3. the link step pulls in an import library that exports those APIs,
// typically `onecore.lib` (or `mincore.lib`).
//
// The `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS` CMake option arranges (1)-(3)
// automatically when building simdjson with its own CMake. Consumers using
// simdjson as a pre-built library are responsible for setting the macro,
// the Windows version macros, and the link library themselves.
//
// If the opt-in conditions are not met on Windows, `padded_memory_map`
// simply does not exist — any attempt to use it fails at compile time
// with an "unknown identifier" diagnostic rather than silently degrading.
//
// The SIMDJSON_HAS_PADDED_MEMORY_MAP macro reflects whether the class is
// available in the current translation unit. Users may test this macro to
// conditionally compile code that depends on padded_memory_map.
#ifndef SIMDJSON_HAS_PADDED_MEMORY_MAP
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#elif defined(_WINDOWS_) && defined(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS) && SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#else
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 0
#endif
#endif
#endif // SIMDJSON_PORTABILITY_H
+203 -7
View File
@@ -4356,10 +4356,55 @@ inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string
#endif
#ifndef _WIN32
// padded_memory_map availability.
//
// On POSIX platforms the class is always available: the implementation uses
// `mmap` (and a trailing anonymous page for padding) from <sys/mman.h>.
//
// On Windows the class is only available when all of the following hold:
// 1. <windows.h> has been included *before* <simdjson.h> (so that this
// header can see the Win32 types and the `_WINDOWS_` include guard),
// 2. the compilation targets Windows 11 or later (NTDDI_VERSION
// >= NTDDI_WIN10_CO, 0x0A00000B). This is required because the
// implementation relies on the modern memory APIs introduced with
// that version (CreateFileMapping2 / MapViewOfFile3).
//
// If those conditions are not met on Windows, `padded_memory_map` simply
// does not exist — any attempt to use it fails at compile time with an
// "unknown identifier" diagnostic rather than silently degrading.
//
// The SIMDJSON_HAS_PADDED_MEMORY_MAP macro reflects whether the class is
// available in the current translation unit. Users may test this macro to
// conditionally compile code that depends on padded_memory_map.
#ifndef SIMDJSON_HAS_PADDED_MEMORY_MAP
#if !defined(_WIN32)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#elif defined(_WINDOWS_) && defined(NTDDI_VERSION) && (NTDDI_VERSION >= 0x0A00000B /* NTDDI_WIN10_CO — Windows 11 */)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#else
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 0
#endif
#endif
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
/**
* A class representing a memory-mapped file with padding.
* It is only available on non-Windows platforms, as Windows has different APIs for memory mapping.
*
* On POSIX systems (Linux, macOS, BSD, ...), this uses `mmap` to map the file
* contents directly into memory, which is efficient for large files (no copy).
*
* On Windows, this class is only available when `<windows.h>` is included
* before `<simdjson.h>` and the compilation targets Windows 11 or later
* (NTDDI_VERSION >= NTDDI_WIN10_CO). The Windows implementation uses the
* modern memory APIs (`CreateFileMapping2` / `MapViewOfFile3`) to map the
* file with true zero-copy semantics whenever the last page of the file
* provides enough trailing zero-fill for SIMDJSON_PADDING bytes; otherwise
* it falls back to a heap-allocated padded buffer populated with `ReadFile`.
*
* Either way, the resulting `padded_string_view` carries at least
* `SIMDJSON_PADDING` bytes of accessible zero-filled padding after the file
* content, so it can be consumed directly by the simdjson parsers (including
* `parse_many` / `iterate_many`).
*/
class padded_memory_map {
public:
@@ -4367,9 +4412,12 @@ public:
* Create a new padded memory map for the given file.
* After creating the memory map, you can call view() to get a padded_string_view of the file content.
* The memory map will be automatically released when the padded_memory_map instance is destroyed.
* Note that the file content is not copied, so this is efficient for large files. However,
* the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found,
* permission denied, etc.), the memory map will be invalid and view() will return an empty view.
* On POSIX systems, the file content is not copied, so this is efficient for large files.
* On Windows, the file is mapped into memory via `MapViewOfFile3` whenever possible
* (zero-copy) and otherwise read into a heap-allocated padded buffer.
* In all cases, the file must remain unchanged while the memory map is in use.
* In case of error (e.g., file not found, permission denied, etc.), the memory map will be
* invalid and view() will return an empty view.
* You can check if the memory map is valid by calling is_valid() before using view().
*
* @param filename the path to the file to memory-map.
@@ -4406,8 +4454,14 @@ private:
padded_memory_map &operator=(const padded_memory_map &) = delete;
const char *data{nullptr};
size_t size{0};
#ifdef _WIN32
// On Windows the underlying storage may either be a memory-mapped view
// (released with UnmapViewOfFile) or a heap-allocated padded buffer
// (released with delete[]). This flag distinguishes the two cases.
bool owns_heap_buffer_{false};
#endif
};
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
@@ -4700,6 +4754,11 @@ inline padded_string_view pad_with_reserve(std::string& s) noexcept {
#include <sys/stat.h>
#include <unistd.h>
#endif
// On Windows, `padded_memory_map` (when it is enabled) depends on types and
// functions declared in <windows.h>. We deliberately do NOT include that
// header here: users of simdjson who want `padded_memory_map` on Windows
// must include <windows.h> themselves *before* including this header. See
// padded_string.h for the detection logic.
namespace simdjson {
namespace internal {
@@ -5068,6 +5127,8 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
}
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#ifndef _WIN32
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
@@ -5104,7 +5165,141 @@ simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
munmap(const_cast<char *>(data), size + simdjson::SIMDJSON_PADDING);
}
}
#else // _WIN32
// Windows 11+ implementation.
//
// We use the modern Windows memory APIs (CreateFileMapping2 + MapViewOfFile3,
// available since Windows 10 1803 and gated on Windows 11 in our build) to
// map the file directly into the process address space with zero copies.
//
// Windows guarantees that after a file view is mapped, any bytes in the
// trailing partial page beyond the end of the file are zero-filled. As long
// as the file does not end exactly on (or within SIMDJSON_PADDING bytes of)
// a page boundary, we therefore get SIMDJSON_PADDING accessible zero bytes
// for free at the tail of the view. In the rare edge cases where the tail
// is not large enough (about 1.5% of file sizes if sizes were uniformly
// distributed), we fall back to reading the file into a heap-allocated
// padded buffer. That fallback is still correct — it just performs one
// memory copy instead of a zero-copy mapping.
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
HANDLE file_handle = ::CreateFileA(
filename, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return; // file not found or cannot be opened
}
LARGE_INTEGER file_size_li;
if (!::GetFileSizeEx(file_handle, &file_size_li) || file_size_li.QuadPart < 0) {
::CloseHandle(file_handle);
return; // failed to get file size
}
#if SIMDJSON_IS_32BITS
if (static_cast<unsigned long long>(file_size_li.QuadPart) >
static_cast<unsigned long long>(SIZE_MAX - simdjson::SIMDJSON_PADDING)) {
::CloseHandle(file_handle);
return; // file too large to map on a 32-bit system
}
#endif
size = static_cast<size_t>(file_size_li.QuadPart);
// Fast zero-copy path: only usable when the last partial page of the file
// gives us at least SIMDJSON_PADDING bytes of zero-filled slack.
if (size > 0) {
SYSTEM_INFO sys_info;
::GetSystemInfo(&sys_info);
const size_t page_size = static_cast<size_t>(sys_info.dwPageSize);
const size_t tail_in_page = size % page_size;
const size_t tail_zero_fill = (tail_in_page == 0)
? size_t{0}
: (page_size - tail_in_page);
if (tail_zero_fill >= simdjson::SIMDJSON_PADDING) {
// Create the section with the new CreateFileMapping2 API.
HANDLE mapping = ::CreateFileMapping2(
file_handle, /*SecurityAttributes=*/ NULL,
/*DesiredAccess=*/ FILE_MAP_READ,
/*PageProtection=*/ PAGE_READONLY,
/*AllocationAttributes=*/ 0,
/*MaximumSize=*/ 0, // 0 => entire file
/*Name=*/ NULL,
/*ExtendedParameters=*/ NULL, /*ParameterCount=*/ 0);
if (mapping != NULL) {
// Map the view with the new MapViewOfFile3 API.
PVOID view_ptr = ::MapViewOfFile3(
mapping, ::GetCurrentProcess(),
/*BaseAddress=*/ NULL,
/*Offset=*/ 0,
/*ViewSize=*/ size,
/*AllocationType=*/ 0,
/*PageProtection=*/ PAGE_READONLY,
/*ExtendedParameters=*/ NULL, /*ParameterCount=*/ 0);
::CloseHandle(mapping);
if (view_ptr != NULL) {
::CloseHandle(file_handle);
data = static_cast<const char *>(view_ptr);
owns_heap_buffer_ = false;
return;
}
}
// Fall through to the buffered-read fallback if the mapping failed.
}
}
// Fallback path: the file ends too close to a page boundary (or the
// mapping APIs refused) — read the file contents into a heap-allocated
// padded buffer. This preserves the class' padding invariant at the cost
// of one copy.
size_t total_size = size + simdjson::SIMDJSON_PADDING;
if (total_size < size) { // overflow guard
::CloseHandle(file_handle);
size = 0;
return;
}
char *buffer = new (std::nothrow) char[total_size];
if (buffer == nullptr) {
::CloseHandle(file_handle);
size = 0;
return;
}
size_t total_read = 0;
while (total_read < size) {
size_t remaining = size - total_read;
const size_t chunk_limit = static_cast<size_t>(0x40000000UL); // 1 GiB per call
DWORD to_read = remaining > chunk_limit
? static_cast<DWORD>(chunk_limit)
: static_cast<DWORD>(remaining);
DWORD bytes_read = 0;
if (!::ReadFile(file_handle, buffer + total_read, to_read, &bytes_read, NULL)) {
delete[] buffer;
::CloseHandle(file_handle);
size = 0;
return;
}
if (bytes_read == 0) {
// Unexpected EOF: the file shrank while we were reading it.
delete[] buffer;
::CloseHandle(file_handle);
size = 0;
return;
}
total_read += bytes_read;
}
std::memset(buffer + size, 0, simdjson::SIMDJSON_PADDING);
data = buffer;
owns_heap_buffer_ = true;
::CloseHandle(file_handle);
}
simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
if (data == nullptr) { return; }
if (owns_heap_buffer_) {
delete[] const_cast<char *>(data);
} else {
::UnmapViewOfFile(data);
}
}
#endif // _WIN32
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
if(!is_valid()) {
@@ -5116,7 +5311,8 @@ simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noe
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
return data != nullptr;
}
#endif // _WIN32
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
} // namespace simdjson
+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));
+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;
+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;
+102 -8
View File
@@ -1,13 +1,29 @@
// On Windows, padded_memory_map is an opt-in feature gated on the
// SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS macro. When that macro is set, the
// consumer must also include <windows.h> before <simdjson.h>. We include
// the Win32 header here so that -- in configurations that turned the
// feature on -- the test actually exercises the Windows path.
#ifdef _WIN32
#include <cstdlib>
// This test is not supported on Windows because it relies on POSIX APIs like
// mmap. Please run it on a POSIX-compliant system.
int main() { return EXIT_SUCCESS; }
#else
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include "simdjson.h"
#include "test_macros.h"
// When SIMDJSON_HAS_PADDED_MEMORY_MAP is 0 (e.g. Windows builds without
// SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS, or MinGW configurations that lack
// the required SDK gating), compile the test body out and make main()
// report success so the test suite still runs as a no-op. This is not a
// silent downgrade: users who want the Windows path must explicitly
// enable the CMake option `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS`.
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#if SIMDJSON_EXCEPTIONS
bool test_memory_map_exception() {
TEST_START();
@@ -37,11 +53,89 @@ bool test_memory_map_noexception() {
TEST_SUCCEED();
}
// Verifies that padded_memory_map can feed a streaming parser (iterate_many)
// with JSON documents read from a file. This exercises the API that parse_many
// / iterate_many users typically want: no extra copy on POSIX, portable fallback
// on Windows. The AMAZON_CELLPHONES_NDJSON resource is an NDJSON file so it is
// a realistic stress-test for streaming from a memory-mapped file.
bool test_memory_map_iterate_many() {
TEST_START();
simdjson::padded_memory_map map(AMAZON_CELLPHONES_NDJSON);
if (!map.is_valid()) {
std::cerr << "Failed to memory-map the file " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
simdjson::padded_string_view view = map.view();
simdjson::ondemand::parser parser;
simdjson::ondemand::document_stream stream;
ASSERT_SUCCESS( parser.iterate_many(view).get(stream) );
size_t count = 0;
for (auto doc : stream) {
ASSERT_SUCCESS( doc.error() );
count++;
}
if (count == 0) {
std::cerr << "Expected at least one document in " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
TEST_SUCCEED();
}
// Verifies that padded_memory_map also works with the DOM streaming parser
// (parse_many). Same rationale as the ondemand variant above.
bool test_memory_map_parse_many() {
TEST_START();
simdjson::padded_memory_map map(AMAZON_CELLPHONES_NDJSON);
if (!map.is_valid()) {
std::cerr << "Failed to memory-map the file " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
simdjson::padded_string_view view = map.view();
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
ASSERT_SUCCESS( parser.parse_many(view).get(stream) );
size_t count = 0;
for (auto doc : stream) {
ASSERT_SUCCESS( doc.error() );
count++;
}
if (count == 0) {
std::cerr << "Expected at least one document in " << AMAZON_CELLPHONES_NDJSON << std::endl;
return false;
}
TEST_SUCCEED();
}
// Ensures that trying to memory-map a file that does not exist leaves the map
// in the "invalid" state rather than crashing. This is important on Windows
// where the underlying implementation path differs from POSIX.
bool test_memory_map_missing_file() {
TEST_START();
simdjson::padded_memory_map map("this_file_definitely_does_not_exist_123456789.json");
if (map.is_valid()) {
std::cerr << "Expected is_valid() == false for missing file" << std::endl;
return false;
}
TEST_SUCCEED();
}
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
int main() {
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
bool ok = true;
#if SIMDJSON_EXCEPTIONS
return (test_memory_map_exception() && test_memory_map_noexception()) ? EXIT_SUCCESS : EXIT_FAILURE;
ok = ok && test_memory_map_exception();
#endif
ok = ok && test_memory_map_noexception();
ok = ok && test_memory_map_iterate_many();
ok = ok && test_memory_map_parse_many();
ok = ok && test_memory_map_missing_file();
return ok ? EXIT_SUCCESS : EXIT_FAILURE;
#else
return test_memory_map_noexception() ? EXIT_SUCCESS : EXIT_FAILURE;
std::cout << "padded_memory_map is disabled in this configuration; "
"set SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON in CMake to "
"enable it on Windows. Test skipped." << std::endl;
return EXIT_SUCCESS;
#endif
}
#endif
+20
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)
@@ -45,6 +46,25 @@ if(NOT SIMDJSON_SANITIZE)
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() &&
@@ -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);
}
}
+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);
}
}
+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);
}
}