mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2887e7432c | |||
| 764a6a0bb6 | |||
| 135eeecb40 |
@@ -29,9 +29,6 @@ We accept the identification of an issue by a sanitizer or some checker tool (e.
|
||||
|
||||
We recommend that you run your tests using different optimization levels. In particular, we recommend your run tests with the simdjson library and you code compiled in debug mode. The simdjson then sets the SIMDJSON_DEVELOPMENT_CHECKS macro to 1, and this triggers additional checks on your code and on the internals of the library. If possible, we recommend that you run tests with sanitizers (e.g., see [No more leaks with sanitize flags in gcc and clang](https://lemire.me/blog/2016/04/20/no-more-leaks-with-sanitize-flags-in-gcc-and-clang/)). You can compile the library with sanitizers for debugging purposes (e.g., set SIMDJSON_SANITIZE to ON using CMake), but you should also turn on sanitizers on your own code. You may also use tools like valgrind or the commercial equivalent.
|
||||
|
||||
Mixing debug and release simdjson code is unsafe: you either build all your code using simdjson in
|
||||
release mode or all of it in debug mode.
|
||||
|
||||
Before reporting a bug, please ensure that you have read our documentation.
|
||||
|
||||
**To Reproduce**
|
||||
@@ -58,7 +55,6 @@ We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux syste
|
||||
* We do not support unreleased or experimental compilers. If you encounter an issue with a
|
||||
pre-release version of a compiler, do not report it as a bug to simdjson. However, we always
|
||||
invite contributions either in the form an analysis or of a code contribution.
|
||||
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support. As of 2026, Windows 10 is no longer supported.
|
||||
|
||||
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ How to verify / test
|
||||
Please read before contributing:
|
||||
- CONTRIBUTING: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
|
||||
- HACKING: https://github.com/simdjson/simdjson/blob/master/HACKING.md
|
||||
- AI Usage Policy: https://github.com/simdjson/simdjson/blob/master/AI_USAGE_POLICY.md
|
||||
|
||||
|
||||
|
||||
If you can, we recommend running our tests with the sanitizers turned on.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
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)
|
||||
@@ -42,14 +42,3 @@ 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 .
|
||||
|
||||
@@ -26,4 +26,4 @@ jobs:
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
|
||||
cmake --build build -j=2
|
||||
ctest --output-on-failure --test-dir build -E ondemand_cacheline
|
||||
ctest --output-on-failure --test-dir build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Performance check on Ubuntu 24.04 CI (GCC 13)
|
||||
name: Performance check on Ubuntu 20.04 CI (GCC 9)
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Ubuntu 24.04 CI (CLANG 20)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
if: >-
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
- name: Install clang-20
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
sudo apt-get install -y clang-20
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
CXX=clang++-20 cmake -DSIMDJSON_DEVELOPER_MODE=ON .. &&
|
||||
cmake --build . &&
|
||||
ctest --output-on-failure -LE explicitonly -j
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Ubuntu 24.04 CI (GCC 13) without exceptions
|
||||
name: Ubuntu 20.04 CI (GCC 9) without exceptions
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
cd .. &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_EXCEPTIONS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_EXCEPTIONS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
||||
cmake --build . &&
|
||||
ctest --output-on-failure -LE explicitonly -j &&
|
||||
make install &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Ubuntu 24.04 CI (GCC 13) Without Threads
|
||||
name: Ubuntu 20.04 CI (GCC 9) Without Threads
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Ubuntu 24.04 CI (GCC 13) With Memory Sanitizer
|
||||
name: Ubuntu 20.04 CI (GCC 9) With Memory Sanitizer
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build-address-sanitizer:
|
||||
ubuntu-build-address-sanitizier:
|
||||
if: >-
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
@@ -21,13 +21,6 @@ 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]') &&
|
||||
@@ -46,10 +39,3 @@ 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
|
||||
|
||||
@@ -12,15 +12,14 @@ 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}} -DSIMDJSON_ENABLE_NAN_INF=${{matrix.nan_inf}} -B build
|
||||
run: cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_SANITIZE=${{matrix.sanitizer}} -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
|
||||
env:
|
||||
CXX: ${{matrix.cxx}}
|
||||
- name: Build
|
||||
run: cmake --build build -j=2
|
||||
- name: Test
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
@@ -13,20 +13,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: ON}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: ON}
|
||||
- {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}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=${{matrix.memory_map}} -DSIMDJSON_ENABLE_NAN_INF=${{matrix.nan_inf}} -B build
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
|
||||
- name: Build Debug
|
||||
run: cmake --build build --config ${{matrix.build_type}} --verbose
|
||||
- name: Run tests
|
||||
@@ -39,4 +37,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}}
|
||||
@@ -1,56 +0,0 @@
|
||||
# AI Usage Policy
|
||||
|
||||
Contributors can use whatever tools they would like to
|
||||
craft their contributions, but there must be a **human in the loop**.
|
||||
**Contributors must read and review all LLM-generated code or text before they
|
||||
ask other project members to review it.** The contributor is always the author
|
||||
and is fully accountable for their contributions. Contributors should be
|
||||
sufficiently confident that the contribution is high enough quality that asking
|
||||
for a review is a good use of scarce maintainer time, and they should be **able
|
||||
to answer questions about their work** during review.
|
||||
|
||||
We expect that new contributors will be less confident in their contributions,
|
||||
and our guidance to them is to **start with small contributions** that they can
|
||||
fully understand to build confidence. We aspire to be a welcoming community
|
||||
that helps new contributors grow their expertise, but learning involves taking
|
||||
small steps, getting feedback, and iterating. Passing maintainer feedback to an
|
||||
LLM doesn't help anyone grow, and does not sustain our community.
|
||||
|
||||
This policy includes, but is not limited to, the following kinds of
|
||||
contributions:
|
||||
|
||||
- Code, usually in the form of a pull request
|
||||
- Issues or security vulnerabilities
|
||||
- Comments and feedback on pull requests
|
||||
|
||||
## Extractive Contributions
|
||||
|
||||
The reason for our "human-in-the-loop" contribution policy is that processing
|
||||
patches, PRs, RFCs, and comments is not free -- it takes a lot of
|
||||
maintainer time and energy to review those contributions! Sending the
|
||||
unreviewed output of an LLM to open source project maintainers *extracts* work
|
||||
from them in the form of design and code review, so we call this kind of
|
||||
contribution an "extractive contribution".
|
||||
|
||||
## Transparency
|
||||
|
||||
For contributions involving significant AI assistance, we encourage you to disclose
|
||||
its use and explain your process. If a submission appears to rely heavily on AI
|
||||
without disclosure, we may doubt that the **human-in-the-loop** requirement has
|
||||
been met. Please show awareness of your use of AI.
|
||||
|
||||
## Copyright
|
||||
|
||||
Artificial intelligence systems raise many questions around copyright that have
|
||||
yet to be answered. Our policy on AI tools is similar to our copyright policy:
|
||||
Contributors are responsible for ensuring that they have the right to
|
||||
contribute code under the terms of our license, typically meaning that either
|
||||
they, their employer, or their collaborators hold the copyright. Using AI tools
|
||||
to regenerate copyrighted material does not remove the copyright, and
|
||||
contributors are responsible for ensuring that such material does not appear in
|
||||
their contributions. Contributions found to violate this policy will be removed
|
||||
just like any other offending contribution.
|
||||
|
||||
## Reference
|
||||
|
||||
- [LLVM AI Tool Use Policy](https://discourse.llvm.org/t/rfc-llvm-ai-tool-policy-human-in-the-loop/89159)
|
||||
+90
-176
@@ -12,7 +12,7 @@ endif()
|
||||
project(
|
||||
simdjson
|
||||
# The version number is modified by tools/release.py
|
||||
VERSION 4.6.1
|
||||
VERSION 4.2.4
|
||||
DESCRIPTION "Parsing gigabytes of JSON per second"
|
||||
HOMEPAGE_URL "https://simdjson.org/"
|
||||
LANGUAGES CXX C
|
||||
@@ -29,8 +29,8 @@ string(
|
||||
# ---- Options, variables ----
|
||||
|
||||
# These version numbers are modified by tools/release.py
|
||||
set(SIMDJSON_LIB_VERSION "33.0.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "33" CACHE STRING "simdjson library soversion")
|
||||
set(SIMDJSON_LIB_VERSION "29.0.0" CACHE STRING "simdjson library version")
|
||||
set(SIMDJSON_LIB_SOVERSION "29" CACHE STRING "simdjson library soversion")
|
||||
|
||||
option(SIMDJSON_BUILD_STATIC_LIB "Build simdjson_static library along with simdjson (only makes sense if BUILD_SHARED_LIBS=ON)" OFF)
|
||||
if(SIMDJSON_BUILD_STATIC_LIB AND NOT BUILD_SHARED_LIBS)
|
||||
@@ -75,48 +75,10 @@ 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)
|
||||
option(BUILD_SHARED_LIBS "Build simdjson as a shared library" OFF)
|
||||
option(SIMDJSON_SINGLEHEADER "Disable singleheader generation" ON)
|
||||
else()
|
||||
option(SIMDJSON_INSTALL "Enable target install" ${BUILD_SHARED_LIBS})
|
||||
endif()
|
||||
|
||||
include(cmake/handle-deprecations.cmake)
|
||||
@@ -130,16 +92,13 @@ add_library(simdjson ${SIMDJSON_SOURCES})
|
||||
add_library(simdjson::simdjson ALIAS simdjson)
|
||||
set(SIMDJSON_LIBRARIES simdjson)
|
||||
|
||||
# Check for <bit> header compatibility
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx(bit SIMDJSON_HAS_BIT_HEADER)
|
||||
|
||||
# Enable precompiled headers for faster builds
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
|
||||
set(SIMDJSON_PRECOMPILE_HEADERS
|
||||
target_precompile_headers(simdjson PRIVATE
|
||||
<algorithm>
|
||||
<array>
|
||||
<atomic>
|
||||
<bit>
|
||||
<cassert>
|
||||
<cctype>
|
||||
<cerrno>
|
||||
@@ -152,12 +111,6 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
|
||||
<utility>
|
||||
<vector>
|
||||
)
|
||||
|
||||
if(SIMDJSON_HAS_BIT_HEADER)
|
||||
list(APPEND SIMDJSON_PRECOMPILE_HEADERS <bit>)
|
||||
endif()
|
||||
|
||||
target_precompile_headers(simdjson PRIVATE ${SIMDJSON_PRECOMPILE_HEADERS})
|
||||
endif()
|
||||
|
||||
if(SIMDJSON_BUILD_STATIC_LIB)
|
||||
@@ -202,37 +155,13 @@ if(MSVC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
if(SIMDJSON_STATIC_REFLECTION)
|
||||
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION MATCHES "^21")
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_CXX_COMPILER} --version
|
||||
OUTPUT_VARIABLE CLANG_VERSION_OUTPUT
|
||||
ERROR_VARIABLE CLANG_VERSION_ERROR
|
||||
RESULT_VARIABLE CLANG_VERSION_RESULT
|
||||
)
|
||||
if(CLANG_VERSION_RESULT EQUAL 0 AND CLANG_VERSION_OUTPUT MATCHES "https://github.com/bloomberg/clang-p2996.git")
|
||||
set(IS_BLOOMBERG_P2996_CLANG ON)
|
||||
message(STATUS "Using Bloomberg P2996 Clang fork")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# We would like to require C++26, but no compiler supports that!
|
||||
# This is a hack:
|
||||
if(IS_BLOOMBERG_P2996_CLANG)
|
||||
simdjson_add_props(
|
||||
target_compile_options PUBLIC
|
||||
-freflection -fexpansion-statements -stdlib=libc++ -std=c++26
|
||||
)
|
||||
else()
|
||||
simdjson_add_props(
|
||||
target_compile_options PUBLIC
|
||||
-freflection -std=c++26
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
simdjson_add_props(target_compile_features PUBLIC cxx_std_11)
|
||||
endif()
|
||||
@@ -254,12 +183,6 @@ 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
|
||||
@@ -293,89 +216,87 @@ endif()
|
||||
|
||||
# ---- Install rules ----
|
||||
|
||||
if(SIMDJSON_INSTALL)
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if(SIMDJSON_SINGLEHEADER)
|
||||
install(
|
||||
FILES singleheader/simdjson.h
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS simdjson
|
||||
EXPORT simdjsonTargets
|
||||
RUNTIME COMPONENT simdjson_Runtime
|
||||
LIBRARY COMPONENT simdjson_Runtime
|
||||
NAMELINK_COMPONENT simdjson_Development
|
||||
ARCHIVE COMPONENT simdjson_Development
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
configure_file(cmake/simdjson-config.cmake.in simdjson-config.cmake @ONLY)
|
||||
|
||||
write_basic_package_version_file(
|
||||
simdjson-config-version.cmake
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
|
||||
set(
|
||||
SIMDJSON_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/simdjson"
|
||||
CACHE STRING "CMake package config location relative to the install prefix"
|
||||
)
|
||||
mark_as_advanced(SIMDJSON_INSTALL_CMAKEDIR)
|
||||
|
||||
install(
|
||||
FILES
|
||||
"${PROJECT_BINARY_DIR}/simdjson-config.cmake"
|
||||
"${PROJECT_BINARY_DIR}/simdjson-config-version.cmake"
|
||||
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT simdjsonTargets
|
||||
NAMESPACE simdjson::
|
||||
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
|
||||
if(SIMDJSON_BUILD_STATIC_LIB)
|
||||
install(
|
||||
TARGETS simdjson_static
|
||||
EXPORT simdjson_staticTargets
|
||||
ARCHIVE COMPONENT simdjson_Development
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
install(
|
||||
EXPORT simdjson_staticTargets
|
||||
NAMESPACE simdjson::
|
||||
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
endif()
|
||||
|
||||
# pkg-config
|
||||
include(cmake/JoinPaths.cmake)
|
||||
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
if(SIMDJSON_ENABLE_THREADS)
|
||||
set(PKGCONFIG_CFLAGS "-DSIMDJSON_THREADS_ENABLED=1")
|
||||
if(CMAKE_THREAD_LIBS_INIT)
|
||||
set(PKGCONFIG_LIBS_PRIVATE "Libs.private: ${CMAKE_THREAD_LIBS_INIT}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
configure_file("simdjson.pc.in" "simdjson.pc" @ONLY)
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
|
||||
)
|
||||
if(SIMDJSON_SINGLEHEADER)
|
||||
install(
|
||||
FILES singleheader/simdjson.h
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS simdjson
|
||||
EXPORT simdjsonTargets
|
||||
RUNTIME COMPONENT simdjson_Runtime
|
||||
LIBRARY COMPONENT simdjson_Runtime
|
||||
NAMELINK_COMPONENT simdjson_Development
|
||||
ARCHIVE COMPONENT simdjson_Development
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
configure_file(cmake/simdjson-config.cmake.in simdjson-config.cmake @ONLY)
|
||||
|
||||
write_basic_package_version_file(
|
||||
simdjson-config-version.cmake
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
|
||||
set(
|
||||
SIMDJSON_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/simdjson"
|
||||
CACHE STRING "CMake package config location relative to the install prefix"
|
||||
)
|
||||
mark_as_advanced(SIMDJSON_INSTALL_CMAKEDIR)
|
||||
|
||||
install(
|
||||
FILES
|
||||
"${PROJECT_BINARY_DIR}/simdjson-config.cmake"
|
||||
"${PROJECT_BINARY_DIR}/simdjson-config-version.cmake"
|
||||
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT simdjsonTargets
|
||||
NAMESPACE simdjson::
|
||||
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
|
||||
if(SIMDJSON_BUILD_STATIC_LIB)
|
||||
install(
|
||||
TARGETS simdjson_static
|
||||
EXPORT simdjson_staticTargets
|
||||
ARCHIVE COMPONENT simdjson_Development
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
install(
|
||||
EXPORT simdjson_staticTargets
|
||||
NAMESPACE simdjson::
|
||||
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
|
||||
COMPONENT simdjson_Development
|
||||
)
|
||||
endif()
|
||||
|
||||
# pkg-config
|
||||
include(cmake/JoinPaths.cmake)
|
||||
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
if(SIMDJSON_ENABLE_THREADS)
|
||||
set(PKGCONFIG_CFLAGS "-DSIMDJSON_THREADS_ENABLED=1")
|
||||
if(CMAKE_THREAD_LIBS_INIT)
|
||||
set(PKGCONFIG_LIBS_PRIVATE "Libs.private: ${CMAKE_THREAD_LIBS_INIT}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
configure_file("simdjson.pc.in" "simdjson.pc" @ONLY)
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
|
||||
)
|
||||
|
||||
#
|
||||
# CPack
|
||||
#
|
||||
@@ -452,25 +373,18 @@ add_subdirectory(fuzz)
|
||||
#
|
||||
# Source files should be just ASCII
|
||||
#
|
||||
find_program(FIND_CMD find)
|
||||
find_program(FILE_CMD file)
|
||||
find_program(GREP_CMD grep)
|
||||
if(FIND_CMD AND FILE_CMD AND GREP_CMD)
|
||||
find_program(FIND find)
|
||||
find_program(FILE file)
|
||||
find_program(GREP grep)
|
||||
if(FIND AND FILE AND GREP)
|
||||
add_test(
|
||||
NAME just_ascii
|
||||
COMMAND sh -c "\
|
||||
non_ascii=$(${FIND_CMD} include src windows tools singleheader tests examples benchmark \
|
||||
-path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \
|
||||
-type f -exec ${FILE_CMD} '{}' \; | ${GREP_CMD} -v ASCII); \
|
||||
if [ -n \"$non_ascii\" ]; then \
|
||||
echo 'The following files contain non-ASCII characters:'; \
|
||||
echo \"$non_ascii\"; \
|
||||
exit 1; \
|
||||
fi"
|
||||
${FIND} include src windows tools singleheader tests examples benchmark \
|
||||
-path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \
|
||||
-type f -exec ${FILE} '{}' \; | ${GREP} -qv ASCII || exit 0 && exit 1"
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
)
|
||||
else()
|
||||
message(WARNING "just_ascii test disabled because required tools were not found: find='${FIND_CMD}', file='${FILE_CMD}', grep='${GREP_CMD}'")
|
||||
endif()
|
||||
|
||||
##
|
||||
|
||||
@@ -101,10 +101,3 @@ Getting Started Hacking
|
||||
|
||||
An overview of simdjson's directory structure, with pointers to architecture and design
|
||||
considerations and other helpful notes, can be found at [HACKING.md](HACKING.md).
|
||||
|
||||
|
||||
|
||||
AI Usage Policy
|
||||
---------------
|
||||
|
||||
Please also review our [AI Usage Policy](AI_USAGE_POLICY.md).
|
||||
|
||||
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "4.6.1"
|
||||
PROJECT_NUMBER = "4.2.4"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -65,7 +65,6 @@ Real-world usage
|
||||
- [GreptimeDB](https://github.com/GreptimeTeam/greptimedb)
|
||||
- [mamba](https://github.com/mamba-org/mamba)
|
||||
- [Ladybird Browser](https://ladybird.org)
|
||||
- [SereneDB](https://github.com/serenedb/serenedb)
|
||||
|
||||
|
||||
If you are planning to use simdjson in a product, please work from one of our releases.
|
||||
@@ -189,8 +188,6 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
|
||||
- [JSON::SIMD](https://metacpan.org/pod/JSON::SIMD): Perl bindings; fully-featured JSON module that uses simdjson for decoding.
|
||||
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
|
||||
- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port.
|
||||
- [mruby-fast-json](https://github.com/Asmod4n/mruby-fast-json): mruby binding with high API coverage.
|
||||
- [simdjson-dart](https://github.com/xaldarof/simdjson-dart): Dart bindings for the simdjson project.
|
||||
|
||||
About simdjson
|
||||
--------------
|
||||
@@ -201,7 +198,7 @@ CPU's multiple execution cores.
|
||||
|
||||
Our default front-end is called On-Demand, and we wrote a paper about it:
|
||||
|
||||
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](https://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
|
||||
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](http://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
|
||||
|
||||
Some people [enjoy reading the first (2019) simdjson paper](https://arxiv.org/abs/1902.08318): A description of the design
|
||||
and implementation of simdjson is in our research article:
|
||||
@@ -213,16 +210,10 @@ We have an in-depth paper focused on the UTF-8 validation:
|
||||
|
||||
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/).
|
||||
|
||||
For the video inclined, we had a talk at QCon San Francisco 2019<br />
|
||||
[](https://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
For the video inclined, <br />
|
||||
[](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
(It was the best voted talk, we're kinda proud of it.)
|
||||
|
||||
We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether you’re a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk!
|
||||
|
||||
[](https://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
|
||||
|
||||
|
||||
|
||||
Citing this work
|
||||
-----------------
|
||||
|
||||
@@ -272,7 +263,7 @@ This code is made available under the [Apache License 2.0](https://www.apache.or
|
||||
|
||||
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it is under the liberal (business-friendly) MIT license.
|
||||
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](https://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](http://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
|
||||
For efficient number serialization, we bundle Florian Loitsch's implementation of the Grisu2 algorithm for binary to decimal floating-point numbers. The implementation was slightly modified by JSON for Modern C++ library. Both Florian Loitsch's implementation and JSON for Modern C++ are provided under the MIT license.
|
||||
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
CXX=clang++ cmake -B buildreflect -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_BRANCH_HINTS=ON
|
||||
cmake --build buildreflect --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
|
||||
./buildreflect/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
|
||||
./buildreflect/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
|
||||
|
||||
|
||||
CXX=clang++ cmake -B buildreflecthints -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_BRANCH_HINTS=ON
|
||||
cmake --build buildreflecthints --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
|
||||
./buildreflecthints/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
|
||||
./buildreflecthints/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
|
||||
|
||||
|
||||
CXX=clang++ cmake -B buildreflectescaping -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_SIMD_ESCAPING=ON
|
||||
cmake --build buildreflectescaping --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
|
||||
./buildreflectescaping/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
|
||||
./buildreflectescaping/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
|
||||
|
||||
|
||||
|
||||
CXX=clang++ cmake -B buildreflectconsteval -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_CONSTEVAL=ON
|
||||
cmake --build buildreflectconsteval --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
|
||||
./buildreflectconsteval/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
|
||||
./buildreflectconsteval/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
|
||||
@@ -1,10 +1,9 @@
|
||||
add_subdirectory(dom)
|
||||
|
||||
|
||||
include_directories( . )
|
||||
include_directories( . linux )
|
||||
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)
|
||||
@@ -16,7 +15,6 @@ if (TARGET benchmark::benchmark)
|
||||
link_libraries(benchmark::benchmark)
|
||||
add_executable(bench_parse_call bench_parse_call.cpp)
|
||||
add_executable(bench_dom_api bench_dom_api.cpp)
|
||||
add_executable(bench_stream_formats bench_stream_formats.cpp)
|
||||
if(SIMDJSON_EXCEPTIONS)
|
||||
add_executable(bench_ondemand bench_ondemand.cpp)
|
||||
if(TARGET yyjson)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,6 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
||||
#include "json2msgpack/boostjson.h"
|
||||
|
||||
#include "partial_tweets/simdjson_ondemand.h"
|
||||
#include "partial_tweets/simdjson_ondemand_key_selector.h"
|
||||
#include "partial_tweets/simdjson_dom.h"
|
||||
#include "partial_tweets/yyjson.h"
|
||||
#if SIMDJSON_COMPETITION_ONDEMAND_SAJSON
|
||||
@@ -125,7 +124,6 @@ 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
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <string>
|
||||
#include "simdjson.h"
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
namespace {
|
||||
|
||||
enum class stream_case {
|
||||
ndjson_small,
|
||||
ndjson_large,
|
||||
rfc7464_small,
|
||||
rfc7464_large,
|
||||
comma_delimited_small,
|
||||
comma_delimited_large
|
||||
};
|
||||
|
||||
constexpr size_t TARGET_BYTES = 128 * 1000 * 1000;
|
||||
constexpr size_t SMALL_PAYLOAD = 16;
|
||||
constexpr size_t LARGE_PAYLOAD = 4096;
|
||||
constexpr size_t BATCH_SIZE = 1 << 20;
|
||||
|
||||
struct stream_dataset {
|
||||
padded_string json;
|
||||
size_t count{};
|
||||
};
|
||||
|
||||
std::string make_document(size_t id, size_t payload_size) {
|
||||
return std::string{"{\"id\":"} + std::to_string(id) +
|
||||
",\"name\":\"aaaaaaaa\",\"payload\":\"" +
|
||||
std::string(payload_size, 'x') + "\",\"flag\":true}";
|
||||
}
|
||||
|
||||
stream_dataset build_dataset(stream_case which) {
|
||||
const bool small = which == stream_case::ndjson_small ||
|
||||
which == stream_case::rfc7464_small ||
|
||||
which == stream_case::comma_delimited_small;
|
||||
const bool rfc = which == stream_case::rfc7464_small ||
|
||||
which == stream_case::rfc7464_large;
|
||||
const bool comma = which == stream_case::comma_delimited_small ||
|
||||
which == stream_case::comma_delimited_large;
|
||||
const size_t payload_size = small ? SMALL_PAYLOAD : LARGE_PAYLOAD;
|
||||
const size_t count = TARGET_BYTES / (payload_size + 48);
|
||||
std::string out;
|
||||
out.reserve(count * (payload_size + 64));
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (rfc) {
|
||||
out += char(0x1E);
|
||||
}
|
||||
if (comma && i > 0) {
|
||||
out += ',';
|
||||
}
|
||||
out += make_document(i, payload_size);
|
||||
if (!comma) {
|
||||
out += '\n';
|
||||
}
|
||||
}
|
||||
return {padded_string(out), count};
|
||||
}
|
||||
|
||||
const stream_dataset &get_dataset(stream_case which) {
|
||||
static const stream_dataset ndjson_small =
|
||||
build_dataset(stream_case::ndjson_small);
|
||||
static const stream_dataset ndjson_large =
|
||||
build_dataset(stream_case::ndjson_large);
|
||||
static const stream_dataset rfc_small =
|
||||
build_dataset(stream_case::rfc7464_small);
|
||||
static const stream_dataset rfc_large =
|
||||
build_dataset(stream_case::rfc7464_large);
|
||||
static const stream_dataset comma_small =
|
||||
build_dataset(stream_case::comma_delimited_small);
|
||||
static const stream_dataset comma_large =
|
||||
build_dataset(stream_case::comma_delimited_large);
|
||||
switch (which) {
|
||||
case stream_case::ndjson_small:
|
||||
return ndjson_small;
|
||||
case stream_case::ndjson_large:
|
||||
return ndjson_large;
|
||||
case stream_case::rfc7464_small:
|
||||
return rfc_small;
|
||||
case stream_case::rfc7464_large:
|
||||
return rfc_large;
|
||||
case stream_case::comma_delimited_small:
|
||||
return comma_small;
|
||||
case stream_case::comma_delimited_large:
|
||||
return comma_large;
|
||||
}
|
||||
return ndjson_small;
|
||||
}
|
||||
|
||||
void set_counters(benchmark::State &state, const stream_dataset &dataset) {
|
||||
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(dataset.json.size()));
|
||||
state.SetItemsProcessed(int64_t(state.iterations()) * int64_t(dataset.count));
|
||||
}
|
||||
|
||||
template <stream_case which, bool threaded = true>
|
||||
static void bench_ondemand(benchmark::State &state) {
|
||||
const auto &dataset = get_dataset(which);
|
||||
ondemand::parser parser;
|
||||
parser.threaded = threaded;
|
||||
stream_format format = stream_format::whitespace_delimited;
|
||||
if constexpr (which == stream_case::rfc7464_small ||
|
||||
which == stream_case::rfc7464_large) {
|
||||
format = stream_format::json_sequence;
|
||||
} else if constexpr (which == stream_case::comma_delimited_small ||
|
||||
which == stream_case::comma_delimited_large) {
|
||||
format = stream_format::comma_delimited;
|
||||
}
|
||||
for (const auto _ : state) {
|
||||
ondemand::document_stream docs;
|
||||
auto error = parser.iterate_many(dataset.json, BATCH_SIZE, format).get(docs);
|
||||
if (error) {
|
||||
state.SkipWithError(error_message(error));
|
||||
return;
|
||||
}
|
||||
uint64_t sum = 0;
|
||||
for (auto doc : docs) {
|
||||
ondemand::object obj;
|
||||
if ((error = doc.get_object().get(obj))) {
|
||||
state.SkipWithError(error_message(error));
|
||||
return;
|
||||
}
|
||||
uint64_t id;
|
||||
if ((error = obj["id"].get_uint64().get(id))) {
|
||||
state.SkipWithError(error_message(error));
|
||||
return;
|
||||
}
|
||||
sum += id;
|
||||
}
|
||||
benchmark::DoNotOptimize(sum);
|
||||
}
|
||||
set_counters(state, dataset);
|
||||
}
|
||||
|
||||
template <stream_case which>
|
||||
static void bench_dom(benchmark::State &state) {
|
||||
const auto &dataset = get_dataset(which);
|
||||
dom::parser parser;
|
||||
parser.threaded = true;
|
||||
stream_format format = stream_format::whitespace_delimited;
|
||||
if constexpr (which == stream_case::rfc7464_small ||
|
||||
which == stream_case::rfc7464_large) {
|
||||
format = stream_format::json_sequence;
|
||||
} else if constexpr (which == stream_case::comma_delimited_small ||
|
||||
which == stream_case::comma_delimited_large) {
|
||||
format = stream_format::comma_delimited;
|
||||
}
|
||||
for (const auto _ : state) {
|
||||
dom::document_stream docs;
|
||||
auto error = parser.parse_many(dataset.json, BATCH_SIZE, format).get(docs);
|
||||
if (error) {
|
||||
state.SkipWithError(error_message(error));
|
||||
return;
|
||||
}
|
||||
uint64_t sum = 0;
|
||||
for (auto doc : docs) {
|
||||
uint64_t id;
|
||||
if ((error = doc["id"].get(id))) {
|
||||
state.SkipWithError(error_message(error));
|
||||
return;
|
||||
}
|
||||
sum += id;
|
||||
}
|
||||
benchmark::DoNotOptimize(sum);
|
||||
}
|
||||
set_counters(state, dataset);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BENCHMARK(bench_ondemand<stream_case::ndjson_small>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_ondemand<stream_case::ndjson_large>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_ondemand<stream_case::rfc7464_small>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_ondemand<stream_case::rfc7464_large>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
// Non-threaded comma_delimited for comparison
|
||||
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small, false>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large, false>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
|
||||
BENCHMARK(bench_dom<stream_case::ndjson_small>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_dom<stream_case::ndjson_large>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_dom<stream_case::rfc7464_small>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_dom<stream_case::rfc7464_large>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_dom<stream_case::comma_delimited_small>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
BENCHMARK(bench_dom<stream_case::comma_delimited_large>)
|
||||
->UseRealTime()
|
||||
->DisplayAggregatesOnly(true);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
@@ -26,6 +25,7 @@ using namespace counters;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
@@ -204,8 +204,12 @@ 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 {
|
||||
@@ -217,8 +221,12 @@ 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!)
|
||||
@@ -231,8 +239,12 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -246,8 +258,12 @@ 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 {
|
||||
@@ -259,8 +275,12 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -358,6 +378,22 @@ 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);
|
||||
@@ -381,6 +417,7 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
|
||||
}
|
||||
printf("|\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Read options
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#ifndef _BENCHMARK_H_
|
||||
#define _BENCHMARK_H_
|
||||
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
|
||||
/*
|
||||
* Prints the best number of operations per cycle where
|
||||
|
||||
+11
-4
@@ -1,8 +1,7 @@
|
||||
#ifndef __BENCHMARKER_H
|
||||
#define __BENCHMARKER_H
|
||||
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <cassert>
|
||||
@@ -29,9 +28,11 @@ using namespace counters;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -422,12 +423,18 @@ struct benchmarker {
|
||||
stage.instructions() / static_cast<double>(stats->structurals),
|
||||
stage.instructions() / static_cast<double>(stage.cycles())
|
||||
);
|
||||
printf("%s%-13s: %7.0f branch misses (%6.2f%%)\n",
|
||||
#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",
|
||||
prefix,
|
||||
"Misses",
|
||||
stage.branch_misses(),
|
||||
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses())
|
||||
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
|
||||
stage.cache_misses(),
|
||||
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
|
||||
stage.cache_references()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
include_directories( .. )
|
||||
include_directories( .. ../linux )
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
@@ -25,6 +24,7 @@ using namespace counters;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linux-perf-events.h"
|
||||
#ifdef __linux__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
#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
|
||||
@@ -1,8 +1,7 @@
|
||||
#ifndef BENCHMARK_HELPERS_H
|
||||
#define BENCHMARK_HELPERS_H
|
||||
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
#include <atomic>
|
||||
|
||||
event_collector collector;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "simdjson.h"
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace json_benchmark {
|
||||
@@ -59,7 +58,11 @@ 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());
|
||||
@@ -67,7 +70,11 @@ 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();
|
||||
@@ -88,7 +95,11 @@ 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,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "json_benchmark/string_runner.h"
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#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
|
||||
@@ -103,12 +103,7 @@ error_code Sax::RunNoExcept(const padded_string &json) noexcept {
|
||||
|
||||
error_code Sax::Allocate(size_t new_capacity) {
|
||||
// string_capacity copied from document::allocate
|
||||
// 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);
|
||||
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
|
||||
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
|
||||
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
|
||||
if (capacity == 0) { // set max depth the first time only
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#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,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
#include "partial_tweets.h"
|
||||
|
||||
namespace partial_tweets {
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
struct simdjson_ondemand_key_selector {
|
||||
using StringType = std::string_view;
|
||||
|
||||
ondemand::parser parser{};
|
||||
|
||||
// Compile-time selectors — all PHF tables are static constexpr, so every
|
||||
// call to match_raw fully inlines.
|
||||
using tweet_sel_t = ondemand::key_selector<
|
||||
"created_at", "id", "text", "in_reply_to_status_id",
|
||||
"user", "retweet_count", "favorite_count">;
|
||||
using user_sel_t = ondemand::key_selector<"id", "screen_name">;
|
||||
|
||||
simdjson_inline uint64_t nullable_int(ondemand::value value) {
|
||||
if (value.is_null()) { return 0; }
|
||||
return value;
|
||||
}
|
||||
|
||||
simdjson_inline twitter_user<std::string_view> read_user(ondemand::object user) {
|
||||
twitter_user<std::string_view> out{};
|
||||
user.for_each<user_sel_t>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: out.id = uint64_t(v); break; // "id"
|
||||
case 1: out.screen_name = std::string_view(v); break; // "screen_name"
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
auto doc = parser.iterate(json);
|
||||
for (ondemand::object tw : doc.find_field("statuses")) {
|
||||
tweet<std::string_view> t{};
|
||||
tw.for_each<tweet_sel_t>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: t.created_at = std::string_view(v); break; // "created_at"
|
||||
case 1: t.id = uint64_t(v); break; // "id"
|
||||
case 2: t.result = std::string_view(v); break; // "text"
|
||||
case 3: t.in_reply_to_status_id = nullable_int(v); break; // "in_reply_to_status_id"
|
||||
case 4: t.user = read_user(v); break; // "user"
|
||||
case 5: t.retweet_count = uint64_t(v); break; // "retweet_count"
|
||||
case 6: t.favorite_count = uint64_t(v); break; // "favorite_count"
|
||||
}
|
||||
});
|
||||
result.emplace_back(std::move(t));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BENCHMARK_TEMPLATE(partial_tweets, simdjson_ondemand_key_selector)->UseManualTime();
|
||||
|
||||
} // namespace partial_tweets
|
||||
|
||||
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
@@ -10,7 +10,7 @@ namespace partial_tweets {
|
||||
// {
|
||||
// "created_at": "Sun Aug 31 00:29:15 +0000 2014",
|
||||
// "id": 505874924095815700,
|
||||
// "text": "@aym0566x ...",
|
||||
// "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
|
||||
// "in_reply_to_status_id": null,
|
||||
// "user": {
|
||||
// "id": 1186275104,
|
||||
|
||||
@@ -61,29 +61,22 @@ struct yyjson_base {
|
||||
};
|
||||
|
||||
struct yyjson : yyjson_base {
|
||||
// The document owns the string memory that result's string_views point into,
|
||||
// so it must outlive each run() (the verification diff happens after run()
|
||||
// returns). Free it on the next run() / at destruction, not before the views
|
||||
// are read.
|
||||
yyjson_doc *doc{};
|
||||
~yyjson() { if (doc != nullptr) { yyjson_doc_free(doc); } }
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
|
||||
doc = yyjson_read(json.data(), json.size(), 0);
|
||||
return yyjson_base::run(doc, result);
|
||||
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
|
||||
bool b = yyjson_base::run(doc, result);
|
||||
yyjson_doc_free(doc);
|
||||
return b;
|
||||
}
|
||||
};
|
||||
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
|
||||
|
||||
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
|
||||
struct yyjson_insitu : yyjson_base {
|
||||
// See the note on yyjson above: the document must outlive result's views.
|
||||
yyjson_doc *doc{};
|
||||
~yyjson_insitu() { if (doc != nullptr) { yyjson_doc_free(doc); } }
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
|
||||
doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
|
||||
return yyjson_base::run(doc, result);
|
||||
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
|
||||
bool b = yyjson_base::run(doc, result);
|
||||
yyjson_doc_free(doc);
|
||||
return b;
|
||||
}
|
||||
};
|
||||
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef BENCHMARK_HELPER_HPP
|
||||
#define BENCHMARK_HELPER_HPP
|
||||
#include <counters/event_counter.h>
|
||||
using namespace counters;
|
||||
#include "event_counter.h"
|
||||
#include <atomic>
|
||||
|
||||
inline event_collector &get_collector() {
|
||||
|
||||
@@ -240,6 +240,36 @@ else()
|
||||
add_compile_definitions(SIMDJSON_UTF8VALIDATION=1)
|
||||
endif()
|
||||
|
||||
option(
|
||||
SIMDJSON_ABLATION_NO_BRANCH_HINTS
|
||||
"Disable branch hints for ablation testing."
|
||||
OFF
|
||||
)
|
||||
if(SIMDJSON_ABLATION_NO_BRANCH_HINTS)
|
||||
add_compile_definitions(ABLATION_NO_BRANCH_HINTS=1)
|
||||
message(STATUS "ABLATION_NO_BRANCH_HINTS enabled")
|
||||
endif()
|
||||
|
||||
option(
|
||||
SIMDJSON_ABLATION_NO_SIMD_ESCAPING
|
||||
"Disable SIMD escaping for ablation testing."
|
||||
OFF
|
||||
)
|
||||
if(SIMDJSON_ABLATION_NO_SIMD_ESCAPING)
|
||||
add_compile_definitions(ABLATION_NO_SIMD_ESCAPING=1)
|
||||
message(STATUS "ABLATION_NO_SIMD_ESCAPING enabled")
|
||||
endif()
|
||||
|
||||
option(
|
||||
SIMDJSON_ABLATION_NO_CONSTEVAL
|
||||
"Disable consteval for ablation testing."
|
||||
OFF
|
||||
)
|
||||
if(SIMDJSON_ABLATION_NO_CONSTEVAL)
|
||||
add_compile_definitions(ABLATION_NO_CONSTEVAL=1)
|
||||
message(STATUS "ABLATION_NO_CONSTEVAL enabled")
|
||||
endif()
|
||||
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(fork unistd.h HAVE_POSIX_FORK)
|
||||
check_symbol_exists(wait sys/wait.h HAVE_POSIX_WAIT)
|
||||
|
||||
Vendored
+1
-9
@@ -12,7 +12,7 @@ cmake_dependent_option(SIMDJSON_GOOGLE_BENCHMARKS "compile the Google Benchmark
|
||||
if(SIMDJSON_GOOGLE_BENCHMARKS)
|
||||
CPMAddPackage(
|
||||
NAME google_benchmarks
|
||||
URL https://github.com/google/benchmark/archive/refs/tags/v1.9.5.zip
|
||||
URL https://github.com/google/benchmark/archive/refs/tags/v1.9.4.zip
|
||||
OPTIONS
|
||||
"BENCHMARK_ENABLE_TESTING OFF"
|
||||
"BENCHMARK_ENABLE_INSTALL OFF"
|
||||
@@ -20,14 +20,6 @@ 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
|
||||
|
||||
+95
-551
@@ -23,17 +23,11 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
* [2. Use `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20)
|
||||
* [3. Using static reflection (C++26)](#3-using-static-reflection-c26)
|
||||
+ [Special cases](#special-cases)
|
||||
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
|
||||
* [The simdjson::from shortcut (experimental, C++20)](#the-simdjsonfrom-shortcut-experimental-c20)
|
||||
* [Order-independent reflective deserialization (experimental)](#order-independent-reflective-deserialization-experimental)
|
||||
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
|
||||
- [UTF-8 validation (alone)](#utf-8-validation-alone)
|
||||
- [JSON Pointer](#json-pointer)
|
||||
- [JSONPath](#jsonpath)
|
||||
* [Using `for_each_at_path_with_wildcard` for JSONPath Queries (On-Demand)](#using-for_each_at_path_with_wildcard-for-jsonpath-queries-on-demand)
|
||||
+ [Example Usage](#example-usage)
|
||||
- [C++20 Ranges Support](#c20-ranges-support)
|
||||
- [Key selectors](#key-selectors)
|
||||
- [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection)
|
||||
- [Error handling](#error-handling)
|
||||
* [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
|
||||
@@ -45,7 +39,6 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
- [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
|
||||
- [Parsing numbers inside strings](#parsing-numbers-inside-strings)
|
||||
- [Dynamic Number Types](#dynamic-number-types)
|
||||
- [Infinity and NaN support](#infinity-and-nan-support)
|
||||
- [Raw strings from keys](#raw-strings-from-keys)
|
||||
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
|
||||
* [Raw JSON string for objects and arrays](#raw-json-string-for-objects-and-arrays)
|
||||
@@ -63,7 +56,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). We support [Fil-C, the memory-safe C/C++ compiler](https://fil-c.org).
|
||||
- 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).
|
||||
- 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.
|
||||
@@ -168,77 +161,54 @@ The basics: loading and parsing JSON documents
|
||||
----------------------------------------------
|
||||
|
||||
The simdjson library allows you to navigate and validate JSON documents ([RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html)).
|
||||
Your JSON document should be a valid Unicode (UTF-8) string.
|
||||
As required by the standard, your JSON document should be in a Unicode (UTF-8) string. The whole
|
||||
string, from the beginning to the end, needs to be valid: we do not attempt to tolerate bad
|
||||
inputs before or after a document.
|
||||
|
||||
To parse JSON, create a `ondemand::parser` and call its `iterate()` method on a padded input.
|
||||
The simplest way to load a JSON file is with `padded_string::load`:
|
||||
For efficiency reasons, simdjson requires a string with a few bytes (`simdjson::SIMDJSON_PADDING`)
|
||||
at the end, these bytes may be read but their content does not affect the parsing. In practice,
|
||||
it means that the JSON inputs should be stored in a memory region with `simdjson::SIMDJSON_PADDING`
|
||||
extra bytes at the end. You do not have to set these bytes to specific values though you may
|
||||
want to if you want to avoid runtime warnings with some sanitizers. We expect the user
|
||||
of the library to load the data (from disk or from the network) into a padded buffer. To make
|
||||
this easy, we provide the `padded_string::load` function which loads files from disk in a padded buffer.
|
||||
[You can similarly fetch a file from a URL to a padded string](https://github.com/simdjson/curltostring) using our `simdjson::padded_string_builder`. Advanced users may want to read the section Free Padding in [our performance notes](performance.md).
|
||||
|
||||
The simdjson library offers a tree-like [API](https://en.wikipedia.org/wiki/API), which you can
|
||||
access by creating a `ondemand::parser` and calling the `iterate()` method. The iterate method
|
||||
quickly indexes the input string and may detect some errors. The following example illustrates
|
||||
how to get started with an input JSON file (`"twitter.json"`):
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
auto json = padded_string::load("twitter.json");
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
auto json = padded_string::load("twitter.json"); // load JSON file 'twitter.json'.
|
||||
ondemand::document doc = parser.iterate(json); // position a pointer at the beginning of the JSON data
|
||||
```
|
||||
|
||||
For inline JSON strings, use the `_padded` suffix:
|
||||
(Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
|
||||
filenames: `padded_string::load(L"twitter.json")`.)
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
auto json = "[1,2,3]"_padded;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
```
|
||||
If you prefer not to create your own `ondemand::parser` instance, you can access
|
||||
a thread-local version by calling `ondemand::parser.get_parser()`.
|
||||
|
||||
If you are compiling with C++17 or better, you can use `simdjson::padded_input`
|
||||
which accepts any string-like input and handles padding automatically:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
std::string_view json = "[1,2,3]";
|
||||
simdjson::padded_input input(json);
|
||||
ondemand::document doc = parser.iterate(input);
|
||||
|
||||
// Also works with std::string, considering reserved capacity
|
||||
std::string json_str = "[1,2,3]";
|
||||
json_str.reserve(100); // Reserve extra space
|
||||
simdjson::padded_input input2(json_str); // May avoid copying
|
||||
ondemand::document doc2 = parser.iterate(input2);
|
||||
```
|
||||
|
||||
The simdjson library also accepts `std::string` instances directly---if the provided
|
||||
reference is non-const, it will allocate padding as needed:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
std::string json = "[1,2,3]";
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
```
|
||||
|
||||
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
|
||||
|
||||
|
||||
### Advanced input options
|
||||
|
||||
This section covers additional ways to provide JSON input to simdjson, including
|
||||
options for fine-grained control over padding and memory.
|
||||
|
||||
**Thread-local parser.** If you prefer not to create your own `ondemand::parser` instance, you can access
|
||||
a thread-local version by calling `ondemand::parser.get_parser()`:
|
||||
|
||||
```cpp
|
||||
ondemand::document doc = ondemand::parser.get_parser().iterate(json);
|
||||
```
|
||||
|
||||
A parser instance can only be used for one document at a time, so
|
||||
the thread-local parser is only applicable when you parse one
|
||||
However, you should be careful because a parser instance can only be used for one
|
||||
document at a time, thus it is only applicable when you are only parsing one
|
||||
document per thread at any one time.
|
||||
|
||||
**`padded_input` details (C++17+).** The actual padding only occurs when the JSON string ends near the boundary of a memory page, which is
|
||||
uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind
|
||||
might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input`
|
||||
on systems without a page size of at least 4096: virtually all systems qualify except for
|
||||
some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, a `simdjson::padded_input` instance will not copy the data and will only act
|
||||
as a view (it does not own the memory).
|
||||
You can also create a padded string---and call `iterate()`:
|
||||
|
||||
**User-managed buffers.** If you have a buffer of your own with enough padding already (`SIMDJSON_PADDING` extra bytes allocated), you can use `padded_string_view` to pass it in:
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
auto json = "[1,2,3]"_padded; // The _padded suffix creates a simdjson::padded_string instance
|
||||
ondemand::document doc = parser.iterate(json); // parse a string
|
||||
```
|
||||
|
||||
If you have a buffer of your own with enough padding already (SIMDJSON_PADDING extra bytes allocated), you can use `padded_string_view` to pass it in:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
@@ -247,99 +217,60 @@ strcpy(json, "[1]");
|
||||
ondemand::document doc = parser.iterate(json, strlen(json), sizeof(json));
|
||||
```
|
||||
|
||||
**Copying into a `padded_string`.** You can copy your data directly into a `simdjson::padded_string`:
|
||||
The simdjson library will also accept `std::string` instances. If the provided
|
||||
reference is non-const, it will allocate padding as needed.
|
||||
|
||||
You can copy your data directly on a `simdjson::padded_string` as follows:
|
||||
|
||||
```cpp
|
||||
const char * data = "my data"; // 7 bytes
|
||||
simdjson::padded_string my_padded_data(data, 7); // copies to a padded buffer
|
||||
```
|
||||
|
||||
Or from a `std::string`:
|
||||
Or as follows...
|
||||
|
||||
```cpp
|
||||
std::string data = "my data";
|
||||
simdjson::padded_string my_padded_data(data); // copies to a padded buffer
|
||||
```
|
||||
|
||||
**`std::string` and sanitizer warnings.** Whenever you pass an `std::string` reference to `parser::iterate`,
|
||||
the parser may access bytes beyond the end of
|
||||
You can then parse the JSON data from the `simdjson::padded_string` instance:
|
||||
|
||||
|
||||
```cpp
|
||||
ondemand::document doc = parser.iterate(my_padded_data);
|
||||
```
|
||||
|
||||
Whenever you pass an `std::string` reference to `parser::iterate`,
|
||||
the parser will access the bytes beyond the end of
|
||||
the string but before the end of the allocated memory (`std::string::capacity()`).
|
||||
Sanitizers that check for reading uninitialized bytes may produce warnings.
|
||||
You can safely ignore these warnings, or call `simdjson::pad(std::string&)` to pad the
|
||||
string explicitly:
|
||||
If you are using a sanitizer that checks for reading uninitialized bytes or `std::string`'s
|
||||
container-overflow checks, you may encounter sanitizer warnings.
|
||||
You can safely ignore these warnings. Or you can call `simdjson::pad(std::string&)` to pad the
|
||||
string with `SIMDJSON_PADDING` spaces: this function returns a `simdjson::padding_string_view` which can be be passed to the parser's iterator function:
|
||||
|
||||
```cpp
|
||||
std::string json = "[1]";
|
||||
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.
|
||||
We recommend against creating many `std::string` or many `std::padding_string` instances in your application to store your JSON data.
|
||||
Consider reusing the same buffers and limiting memory allocations.
|
||||
|
||||
**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:
|
||||
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
|
||||
|
||||
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`.
|
||||
Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples:
|
||||
|
||||
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.
|
||||
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
|
||||
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo)
|
||||
|
||||
The availability of the class can be tested with the preprocessor macro
|
||||
`SIMDJSON_HAS_PADDED_MEMORY_MAP`.
|
||||
|
||||
```cpp
|
||||
#ifdef _WIN32
|
||||
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
// ...
|
||||
simdjson::padded_memory_map map(myfilename);
|
||||
if (!map.is_valid()) { /* handle error */ }
|
||||
simdjson::padded_string_view view = map.view();
|
||||
ondemand::document doc = parser.iterate(view);
|
||||
```
|
||||
|
||||
**Windows-specific notes.** Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
|
||||
filenames: `padded_string::load(L"twitter.json")`. Windows users who need to read files with
|
||||
*Windows-specific*: Windows users who need to read files with
|
||||
non-ANSI characters in the name should set their code page to
|
||||
UTF-8 (65001). This should be the default with Windows 11 and better.
|
||||
Further, they may use the AreFileApisANSI function to determine whether
|
||||
the filename is interpreted using the ANSI or the system default OEM
|
||||
codepage, and they may call SetFileApisToOEM accordingly.
|
||||
|
||||
Some users may want to browse code along with the compiled assembly:
|
||||
|
||||
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
|
||||
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo)
|
||||
|
||||
**Summary of input types:**
|
||||
|
||||
|
||||
| Input Type / Method | Padding Requirement | How Padding is Handled | Ownership / Copying | Notes / Warnings |
|
||||
|----------------------------------------------|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------|----------------------------------------------------------------------------------|
|
||||
| `padded_string::load("file.json")` | Automatic (SIMDJSON_PADDING extra bytes) | Library allocates padded buffer and loads file into it | Owned by `padded_string` | Recommended for files; safest and simplest. |
|
||||
| `"...json..."_padded` literal | Automatic (built-in padding) | Creates `padded_string` with padding | Owned by `padded_string` | Convenient for small hardcoded JSON. |
|
||||
| `simdjson::padded_input` (C++17+) | Automatic when needed | Adds padding **only** if the string ends near a memory page boundary. For `std::string`, considers `capacity()` | Usually a non-owning view (no copy most times) | Safe on standard OS (page size ≥ 4096). May trigger sanitizer/valgrind warnings (harmless). Avoid on niche embedded systems. |
|
||||
| User buffer with explicit padding | Must have at least `SIMDJSON_PADDING` extra allocated bytes after JSON content | Pass via `iterate(ptr, json_length, total_allocated_size)` or `padded_string_view` | User-owned (no copy) | Use `char buf[len + SIMDJSON_PADDING]`. Library reads (but never writes) into padding. |
|
||||
| `std::string` (non-const) | Library checks `capacity()` | If insufficient, library may allocate a padded copy | May copy (depends on capacity) | Can trigger sanitizer warnings on uninitialized bytes. Use `simdjson::pad(json)` to avoid. |
|
||||
| `simdjson::pad(std::string&)` | Adds padding if needed | Returns `padded_string_view` pointing to the (possibly resized) string | References original string | Recommended to silence sanitizers when using `std::string`. |
|
||||
| `padded_string(data, length)` or `padded_string(std::string)` | Automatic (copies into padded buffer) | Explicit copy into owned padded buffer | Owned by `padded_string` | Safe when you want full ownership and padding guaranteed. |
|
||||
| `padded_string_view` (manual) | User guarantees `SIMDJSON_PADDING` extra bytes after the viewed length | User provides pointer + length + capacity | Non-owning view | Low-level; requires careful buffer management. |
|
||||
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping / padded read | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Always available on POSIX (zero-copy `mmap`). On Windows, opt-in via `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON` (requires Windows 10 1803+ and links `onecore.lib`) and `#include <windows.h>` before simdjson; uses `CreateFileMapping2` + `MapViewOfFile3`. |
|
||||
|
||||
|
||||
Documents are iterators
|
||||
-----------------------
|
||||
|
||||
@@ -443,10 +374,6 @@ builds to disable additional runtime testing and get the best performance. We
|
||||
disable these checks on a best-effort basis but the C++ standard does not provide
|
||||
a direct way to check for a release build.
|
||||
|
||||
|
||||
Warnign: Mixing debug and release simdjson code is unsafe: you either build all your code
|
||||
using simdjson in release mode or all of it in debug mode.
|
||||
|
||||
Using the parsed JSON
|
||||
---------------------
|
||||
|
||||
@@ -459,7 +386,7 @@ We also have a generic ephemeral type (`simdjson::ondemand::value`) which repres
|
||||
array or object, or scalar type (`double`, `uint64_t`, `int64_t`, `bool`, `null`, string) inside
|
||||
an array or an object. Both generic types (`simdjson::ondemand::document` and
|
||||
`simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing indicating the type (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`,
|
||||
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens (such as the `NaN` token in `{"key":NaN}` when using the default strict parsing behavior). A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
|
||||
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens such as the `NaN` token in `{"key":NaN}`. A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
|
||||
is only valid temporarily, as soon as you access other values, other keys in objects, etc.
|
||||
it becomes invalid: you should therefore consume the value immediately by converting it to a
|
||||
scalar type, an array or an object.
|
||||
@@ -490,7 +417,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
|
||||
* **Extracting Values:** You can cast a JSON element to a native type:
|
||||
`double(element)`. This works for `std::string_view`, double, uint64_t, int64_t, bool,
|
||||
ondemand::object and ondemand::array. We also have explicit methods such as `get_string()`, `get_double()`,
|
||||
`get_uint64()`, `get_int64()`, `get_uint32()`, `get_int32()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method,
|
||||
`get_uint64()`, `get_int64()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method,
|
||||
the number, string or boolean will be parsed, or the initial `{` or `[` will be verified for `ondemand::object` and `ondemand::array`. An exception may be thrown if
|
||||
the cast is not possible: the error code is `simdjson::INCORRECT_TYPE` (see [Error handling](#error-handling)). Importantly, when getting an ondemand::object or ondemand::array instance, its content is
|
||||
not validated: you are only guaranteed that the corresponding initial character (`{` or `[`) is present. Thus,
|
||||
@@ -500,8 +427,8 @@ support for users who avoid exceptions. See [the simdjson error handling documen
|
||||
pass `true` (`get_string(true)`) as a parameter to get replacement characters where errors
|
||||
occur. If you somehow need to access non-UTF-8 strings in a lossless manner
|
||||
(e.g., if you strings contain unpaired surrogates), you may use the `get_wobbly_string()` function to get a string in the [WTF-8 format](https://simonsapin.github.io/wtf-8).
|
||||
When calling `get_uint64()`, `get_int64()`, `get_uint32()` or `get_int32()`, if the number does not fit in the
|
||||
corresponding integer type, it is also considered an error (`NUMBER_OUT_OF_RANGE`). When parsing numbers or other scalar values, the library checks
|
||||
When calling `get_uint64()` and `get_int64()`, if the number does not fit in a corresponding
|
||||
64-bit integer type, it is also considered an error. When parsing numbers or other scalar values, the library checks
|
||||
that the value is followed by an expected character, thus you *may* get a number parsing error when accessing the digits
|
||||
as an integer in the following strings: `{"number":12332a`, `{"number":12332\0`, `{"number":12332` (the digits appear at the end). We always abide by the [RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html) JSON specification so that, for example, numbers prefixed by the `+` sign are in error.
|
||||
|
||||
@@ -1354,6 +1281,10 @@ You can also use the custom `Car` type as part of a template such as `std::vecto
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc = parser.iterate(json);
|
||||
std::vector<Car> cars(doc);
|
||||
// visual studio users need an explicit call:
|
||||
// std::vector<Car> cars = doc.get<std::vector<Car>>();
|
||||
// because the compiler does not know whether to convert
|
||||
// doc to an unsigned int or to a vector.
|
||||
for(Car& c : cars) {
|
||||
std::cout << c.year << std::endl;
|
||||
}
|
||||
@@ -1447,10 +1378,6 @@ 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. Alternatively, [key selectors](#key-selectors)
|
||||
let you extract a fixed, known set of fields in a single pass, independently of the
|
||||
order in which they appear in the document.
|
||||
|
||||
|
||||
### 3. Using static reflection (C++26)
|
||||
@@ -1527,12 +1454,6 @@ 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. Alternatively, [key selectors](#key-selectors)
|
||||
let you extract a fixed, known set of fields in a single pass, independently of the
|
||||
order in which they appear in the document.
|
||||
|
||||
#### Special cases
|
||||
|
||||
However, there are instances where the construction cannot
|
||||
@@ -1599,51 +1520,6 @@ You can also automatically serialize the `Car` instance to a JSON string, see
|
||||
our [Builder documentation](builder.md).
|
||||
|
||||
|
||||
#### Renaming and skipping fields with annotations
|
||||
|
||||
**This is experimental: the syntax may change slightly in the future.**
|
||||
|
||||
C++26 annotations provide a convenient way to customize (de)serialization
|
||||
without writing `tag_invoke` functions. You can rename the JSON key that
|
||||
corresponds to a C++ data member, or skip a member entirely.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```cpp
|
||||
// rename cppFieldName (in C++) to json_key_name (in JSON)
|
||||
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
|
||||
// do not serialize or deserialize this field
|
||||
[[= simdjson::skip]] int internalState;
|
||||
```
|
||||
|
||||
Full examples:
|
||||
|
||||
```cpp
|
||||
struct RenamedFields {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
struct SkippedField {
|
||||
std::string name = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
};
|
||||
|
||||
struct MixedAnnotations {
|
||||
[[= simdjson::rename<"user_name">]] std::string userName = "";
|
||||
[[= simdjson::skip]] int sessionToken = 0;
|
||||
int age = 0;
|
||||
};
|
||||
```
|
||||
|
||||
- Serialization via `simdjson::to_json(r)` or `builder << r` will use the renamed
|
||||
keys and omit skipped fields.
|
||||
- Deserialization via `doc.get<RenamedFields>()` will map the JSON keys back
|
||||
to the C++ fields. Skipped fields are never written during deserialization
|
||||
(they keep their default-initialized value), and keys matching skipped fields
|
||||
in the JSON input are ignored.
|
||||
|
||||
|
||||
### The simdjson::from shortcut (experimental, C++20)
|
||||
|
||||
@@ -1655,10 +1531,6 @@ 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;
|
||||
@@ -1684,56 +1556,6 @@ std::map<std::string, std::string> obj =
|
||||
|
||||
The `simdjson::from` construction is EXPERIMENTAL and subject to changes.
|
||||
|
||||
### Order-independent reflective deserialization (experimental)
|
||||
|
||||
> **Experimental and opt-in.** This feature is disabled by default. Enable it by
|
||||
> defining the macro `SIMDJSON_USE_KEY_SELECTOR_REFLECTION=1` before including
|
||||
> simdjson (e.g. as a compiler flag `-DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1`).
|
||||
> It requires C++26 static reflection (`SIMDJSON_STATIC_REFLECTION`).
|
||||
|
||||
By default, reflective deserialization (`doc.get<T>()` / `simdjson::from`) reads
|
||||
each struct member with an ordered field lookup. This is fastest when the JSON
|
||||
keys appear in the same order as the struct's members, which is the common case.
|
||||
|
||||
When you genuinely cannot rely on the JSON key order — for example when the data
|
||||
comes from a producer that emits members in an arbitrary or varying order — the
|
||||
ordered lookups degrade, because each out-of-order key forces a rescan of the
|
||||
object. For that situation simdjson offers an optional, order-independent
|
||||
strategy: when `SIMDJSON_USE_KEY_SELECTOR_REFLECTION=1` is defined, the
|
||||
reflective deserializer builds a compile-time [key selector](#key-selectors)
|
||||
from the struct's members and walks each object **once** with
|
||||
`object::for_each`, classifying every key through a perfect hash regardless of
|
||||
its position.
|
||||
|
||||
```cpp
|
||||
// Compile this translation unit with -DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
|
||||
struct Tweet {
|
||||
uint64_t id;
|
||||
std::string text;
|
||||
uint64_t retweet_count;
|
||||
};
|
||||
|
||||
// Keys here are NOT in declaration order, yet deserialization succeeds.
|
||||
auto json = R"({ "retweet_count": 7, "id": 12345, "text": "hello" })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
Tweet t;
|
||||
auto error = doc.get(t); // uses the key-selector path under the macro
|
||||
```
|
||||
|
||||
**This is not a universal speedup — run your own benchmarks before adopting it.**
|
||||
In our measurements the key-selector path is roughly on par with the default on
|
||||
small structs (e.g. the Twitter user/tweet objects) but **slower** on documents
|
||||
dominated by many small nested objects (e.g. the CITM catalog), because walking
|
||||
every field of every object and hashing each key costs more than ordered,
|
||||
short-circuiting lookups when the keys *are* in order. Only consider enabling the
|
||||
macro when (a) your inputs really do present keys out of order, and (b) your own
|
||||
benchmarks on your own data show a win. Otherwise, leave it off.
|
||||
|
||||
|
||||
Minifying JSON strings without parsing
|
||||
----------------------
|
||||
|
||||
@@ -1949,15 +1771,15 @@ int64_t x = obj.at_path("$.c.foo.a[1]"); // 20
|
||||
x = obj.at_path("$.d.foo2.a.2"); // 30
|
||||
```
|
||||
|
||||
## Using `for_each_at_path_with_wildcard` for JSONPath Queries (On-Demand)
|
||||
## Using `at_path_with_wildcard` for JSONPath Queries (On-Demand)
|
||||
|
||||
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 `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 `*` 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.
|
||||
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.
|
||||
|
||||
### Example Usage
|
||||
|
||||
Here is an example demonstrating the use of `for_each_at_path_with_wildcard`:
|
||||
Here is an example demonstrating the use of `at_path_with_wildcard`:
|
||||
|
||||
```cpp
|
||||
simdjson::padded_string json_string = R"(
|
||||
@@ -1986,248 +1808,31 @@ ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json_string);
|
||||
|
||||
// Fetch all fields in the address object
|
||||
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;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all phone numbers
|
||||
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;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
|
||||
|
||||
## C++20 Ranges Support
|
||||
|
||||
When compiling with C++20 (or later), you can use `std::ranges` with the On-Demand API
|
||||
via the `get_range()` helper. This enables use of range adaptors such as `std::views::transform`.
|
||||
|
||||
```cpp
|
||||
#include "simdjson.h"
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
auto json = R"([
|
||||
{ "name": "Alice", "age": 30 },
|
||||
{ "name": "Bob", "age": 25 },
|
||||
{ "name": "Carol", "age": 35 }
|
||||
])"_padded;
|
||||
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
auto arr = doc.get_array();
|
||||
|
||||
// Use std::views::transform to extract names
|
||||
auto names = ondemand::get_range(arr)
|
||||
| std::views::transform([](auto elem) -> std::string {
|
||||
return std::string(std::string_view(elem["name"]));
|
||||
});
|
||||
|
||||
for (auto name : names) {
|
||||
std::cout << name << std::endl; // Alice, Bob, Carol
|
||||
}
|
||||
```
|
||||
|
||||
The `get_range()` and `get_key_value_range()` functions wrap an `ondemand::array`
|
||||
or `ondemand::object` in a `std::ranges::view` that satisfies `std::ranges::input_range`.
|
||||
They work with both exception and non-exception code:
|
||||
|
||||
```cpp
|
||||
// With exceptions:
|
||||
auto range = ondemand::get_range(doc.get_array());
|
||||
|
||||
// Without exceptions:
|
||||
ondemand::array arr;
|
||||
if (doc.get_array().get(arr) == SUCCESS) {
|
||||
auto range = ondemand::get_range(arr);
|
||||
for (auto elem : range) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
Object iteration uses `get_key_value_range()` and yields `simdjson_result<ondemand::field>` elements:
|
||||
|
||||
```cpp
|
||||
auto obj = doc.get_object();
|
||||
for (auto field_result : ondemand::get_key_value_range(obj)) {
|
||||
std::cout << field_result.key() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
The range wrappers are zero-cost: they forward directly to the underlying
|
||||
On-Demand iterators with no value buffering or extra per-element overhead.
|
||||
|
||||
## Key selectors
|
||||
|
||||
> **Experimental.** Key selectors are an experimental feature: the API may change
|
||||
> in a future release.
|
||||
|
||||
When you need to extract a known, fixed set of fields from a JSON object and you
|
||||
do not care about the order in which they appear in the document, *key selectors*
|
||||
let you do it in a single pass: each selected key is mapped to a small integer
|
||||
index, and you dispatch on that index (typically with a `switch`) instead of
|
||||
repeatedly looking up keys by string.
|
||||
|
||||
**Requirements.** Key selectors rely on C++20 features (concepts and class-type
|
||||
non-type template parameters), so they are only available when simdjson is
|
||||
compiled in C++20 mode or later. When that support is present, the macro
|
||||
`SIMDJSON_SUPPORTS_CONCEPTS` is defined.
|
||||
|
||||
Key selection works with hashing.
|
||||
A *hash function* maps keys (here, JSON field names such as `"id"` or `"name"`)
|
||||
to small integers. A *perfect* hash function is one that, for a fixed and known
|
||||
set of keys, maps each key to a distinct slot with no collisions, so a single
|
||||
hash computation plus one comparison suffices to identify a key, there is no
|
||||
probing and no collision chains.
|
||||
|
||||
Because the set of keys is known at compile time, the simdjson library builds the perfect
|
||||
hash function during compilation (using `consteval`). All of its lookup tables
|
||||
become `static constexpr` data, which the compiler treats as constants at every
|
||||
call site and can fully inline. At run time, recognizing a field name reduces to:
|
||||
scan its length, compute a hash from a couple of bytes, and perform one
|
||||
length-and-bytes comparison, branch-light and SIMD-accelerated.
|
||||
|
||||
You declare a selector type from a list of string literals:
|
||||
|
||||
```cpp
|
||||
using sel = simdjson::ondemand::key_selector<"id", "name", "email">;
|
||||
```
|
||||
|
||||
`sel` is a stateless type. Each key is assigned a fixed index, in declaration
|
||||
order: `"id"` is 0, `"name"` is 1, `"email"` is 2. The type exposes a couple of
|
||||
compile-time helpers:
|
||||
|
||||
- `sel::size()` — the number of keys in the selector (here, 3);
|
||||
- `sel::key_at(i)` — the key text at index `i`.
|
||||
|
||||
You then walk an object with `object::for_each<sel>(callback)`. The callback is
|
||||
invoked once for each field whose key belongs to the selector, **in JSON order**,
|
||||
with two arguments: the selector index of the matched key, and the field's value
|
||||
(an `ondemand::value` that you must consume inside the callback, before the next
|
||||
field is visited, as usual with On Demand). Fields whose keys are not in the
|
||||
selector are skipped without being parsed; duplicate keys are ignored after the
|
||||
first match; and iteration stops as soon as every selector key has matched or the
|
||||
object ends. The `for_each` call returns a `simdjson::error_code` (`SUCCESS`, or the first
|
||||
error encountered while walking the object). Your callback may itself return a
|
||||
`simdjson::error_code`: when it does, the walk stops at the first non-`SUCCESS`
|
||||
result and `for_each` returns it. That is the recommended way to report a
|
||||
value-parsing error (e.g. a type mismatch) from inside the callback.
|
||||
|
||||
Because the selector index is a small integer, a `switch` is the natural way to
|
||||
dispatch on the matched field.
|
||||
|
||||
|
||||
Key selectors are subject to a few compile-time restrictions:
|
||||
|
||||
- Key length. We currently limit keys to at most 31 characters long.
|
||||
A longer key produces a compile-time error. This limitations could be
|
||||
eased in the future but we expect longer keys to be unusual.
|
||||
- Number of keys. The hard limit is 255 keys, but the compile-time perfect-hash
|
||||
construction may fail (again, a compile-time error) for large or awkward key sets,
|
||||
and compilation time grows with the number of keys. For compilation speed,
|
||||
you may use precompiled headers if you have dozens of keys.
|
||||
- Key contents. Keys must be distinct, non-empty, and must not contain a
|
||||
backslash, a double quote, or a null byte. Matching is performed against the
|
||||
raw, unescaped JSON key bytes, so a selector key has to equal the key exactly
|
||||
as it appears in the document (no JSON escape processing is applied).
|
||||
|
||||
This example uses exceptions (see [Disabling exceptions](#disabling-exceptions)
|
||||
for the error-code style). The `"age"` field is not selected, so it is skipped.
|
||||
|
||||
```cpp
|
||||
using namespace simdjson;
|
||||
auto json = R"({ "name": "Daniel", "age": 42, "city": "Montreal" })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object obj = doc.get_object();
|
||||
|
||||
using fields = ondemand::key_selector<"name", "city">;
|
||||
|
||||
std::string_view name, city;
|
||||
obj.for_each<fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: name = std::string_view(v); break; // "name"
|
||||
case 1: city = std::string_view(v); break; // "city"
|
||||
}
|
||||
});
|
||||
// name == "Daniel", city == "Montreal"
|
||||
```
|
||||
|
||||
Key selectors work on any object and we do not have
|
||||
to include all keys. Here the `"verified"` field is skipped.
|
||||
|
||||
```cpp
|
||||
using namespace simdjson;
|
||||
auto json = R"({ "user": { "id": 1186275104, "screen_name": "ayuu0123", "verified": false } })"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object user = doc["user"].get_object();
|
||||
|
||||
using user_fields = ondemand::key_selector<"id", "screen_name">;
|
||||
|
||||
uint64_t id = 0;
|
||||
std::string_view handle;
|
||||
user.for_each<user_fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: id = uint64_t(v); break; // "id"
|
||||
case 1: handle = std::string_view(v); break; // "screen_name"
|
||||
}
|
||||
});
|
||||
// id == 1186275104, handle == "ayuu0123"
|
||||
```
|
||||
|
||||
|
||||
A selected value can be any JSON value, including a nested object. Because the
|
||||
value is consumed inside the callback, you can simply turn it into an
|
||||
`ondemand::object` and call `for_each` again with another selector. As always
|
||||
with On Demand, the inner object must be fully consumed before the outer
|
||||
iteration continues, which the nested `for_each` does for you.
|
||||
|
||||
```cpp
|
||||
using namespace simdjson;
|
||||
auto json = R"({
|
||||
"id": 42,
|
||||
"author": { "name": "Daniel", "handle": "lemire" },
|
||||
"title": "On Demand"
|
||||
})"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc = parser.iterate(json);
|
||||
ondemand::object obj = doc.get_object();
|
||||
|
||||
using post_fields = ondemand::key_selector<"id", "author", "title">;
|
||||
using author_fields = ondemand::key_selector<"name", "handle">;
|
||||
|
||||
uint64_t id = 0;
|
||||
std::string_view title, author_name, author_handle;
|
||||
obj.for_each<post_fields>([&](std::size_t i, ondemand::value v) {
|
||||
switch (i) {
|
||||
case 0: id = uint64_t(v); break; // "id"
|
||||
case 1: { // "author" is itself an object
|
||||
ondemand::object author = v.get_object();
|
||||
author.for_each<author_fields>([&](std::size_t j, ondemand::value av) {
|
||||
switch (j) {
|
||||
case 0: author_name = std::string_view(av); break; // "name"
|
||||
case 1: author_handle = std::string_view(av); break; // "handle"
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2: title = std::string_view(v); break; // "title"
|
||||
}
|
||||
});
|
||||
// id == 42, title == "On Demand", author_name == "Daniel", author_handle == "lemire"
|
||||
```
|
||||
|
||||
|
||||
## Compile-Time JSONPath and JSON Pointer (C++26 Reflection)
|
||||
|
||||
The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression
|
||||
@@ -2912,7 +2517,7 @@ Market: btce Price: 432.89 Volume: 8561.06
|
||||
*/
|
||||
```
|
||||
|
||||
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value when using the default strict parsing behavior:
|
||||
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value.
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
@@ -2927,7 +2532,7 @@ if (error) {
|
||||
error = value.get_string().get(view);
|
||||
if (error) { /* Handle error */ }
|
||||
else if (view == "Infinity") {
|
||||
d = std::numeric_limits<double>::infinity();
|
||||
d = std::numeric_limits::infinity();
|
||||
}
|
||||
else { /* Handle wrong value */ }
|
||||
}
|
||||
@@ -2935,15 +2540,6 @@ if (error) {
|
||||
It is also important to note that when dealing an invalid number inside a string, simdjson will report a `NUMBER_ERROR` error if the string begins with a number whereas simdjson
|
||||
will report an `INCORRECT_TYPE` error otherwise.
|
||||
|
||||
When `SIMDJSON_ENABLE_NAN_INF` is enabled, simdjson can parse `"Infinity"`, `"-Infinity"`, and `"NaN"` from a string using `get_double_in_string` without needing extra error handling:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
// Get "change"/"Infinity" key/value pair as a double.
|
||||
double d = doc["ticker"]["change"].get_double_in_string();
|
||||
```
|
||||
|
||||
The `*_in_string` methods can also be called on a single document instance:
|
||||
e.g., when your document consist solely of a quoted number.
|
||||
|
||||
@@ -3083,53 +2679,6 @@ This code prints the following:
|
||||
'99999999999999999999999 '
|
||||
```
|
||||
|
||||
|
||||
Infinity and NaN support
|
||||
------------------------------
|
||||
|
||||
The JSON specification does not support `Infinity` and `NaN` literals. However, some engineers use literal `Infinity` and `NaN` tokens when serializing floating-point values to JSON.
|
||||
|
||||
The simdjson library achieves maximum JSON parsing performance by adhering to a strict interpretation of the JSON specification. Therefore strict parsing is enabled by default - `Infinity` and `NaN` literals are not parsed as valid JSON.
|
||||
|
||||
Users can opt-in to parsing `Infinity`, `-Infinity`, and `NaN` as `double` values by setting the `SIMDJSON_ENABLE_NAN_INF` flag to `ON` when building simdjson: `cmake -B build -D SIMDJSON_ENABLE_NAN_INF=ON` and setting `SIMDJSON_ENABLE_NAN_INF` to 1 before including `"simdjson.h"`. When enabled, `Infinity`, `-Infinity`, `Inf`, `-Inf`, and `NaN` literals are case-insensitively matched and parsed as `double`:
|
||||
|
||||
```cpp
|
||||
// The SIMDJSON_ENABLE_NAN_INF flag also needs to be set before including simdjson.h
|
||||
#define SIMDJSON_ENABLE_NAN_INF 1
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
|
||||
// ...
|
||||
|
||||
ondemand::parser parser;
|
||||
auto inf_nan_literals = "[Infinity, -Infinity, NaN, inf, -inf, nan]"_padded;
|
||||
ondemand::document doc = parser.iterate(inf_nan_literals);
|
||||
|
||||
// Parse and iterate through the array of literal inf/nan values.
|
||||
for (ondemand::value val: doc.get_array()) {
|
||||
ondemand::number num = val.get_number();
|
||||
ondemand::number_type t = num.get_number_type();
|
||||
|
||||
if (t == ondemand::number_type::floating_point_number) {
|
||||
std::cout << "Parsed floating-point number: " << num.get_double() << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to parse." << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
produces the following output:
|
||||
|
||||
```
|
||||
Parsed floating-point number: inf
|
||||
Parsed floating-point number: -inf
|
||||
Parsed floating-point number: nan
|
||||
Parsed floating-point number: inf
|
||||
Parsed floating-point number: -inf
|
||||
Parsed floating-point number: nan
|
||||
```
|
||||
|
||||
|
||||
Raw strings from keys
|
||||
-----------
|
||||
|
||||
@@ -3228,7 +2777,7 @@ std::string_view noquote(std::string_view v) { return {v.data()+1, v.find_last_o
|
||||
|
||||
|
||||
The `raw_json_token()` method can enable you to provide fallbacks when parsing fails.
|
||||
Consider the following example under the default strict parsing behavior (`NaN` parsing is disabled):
|
||||
Consider the following example.
|
||||
|
||||
```cpp
|
||||
padded_string json = "{\"key\": NaN}"_padded;
|
||||
@@ -3250,9 +2799,6 @@ Consider the following example under the default strict parsing behavior (`NaN`
|
||||
The NaN is not supported in JSON. However, in the On-Demand API, you can check
|
||||
the string corresponding to the JSON token and determine how to handle it.
|
||||
|
||||
In this particular case, `SIMDJSON_ENABLE_NAN_INF` can be enabled to parse `Infinity` and `NaN` tokens.
|
||||
However other tokens will still need to be handled via the `raw_json_token()` method.
|
||||
|
||||
### Raw JSON string for objects and arrays
|
||||
|
||||
If your value is an array or an object, `raw_json_token()` returns effectively a single
|
||||
@@ -3276,10 +2822,8 @@ simdjson::ondemand::array arr = doc.get_array();
|
||||
string_view token = arr.raw_json(); // gives you `[1,2,3]`
|
||||
```
|
||||
|
||||
Because `raw_json()` consumes the object or the array, if you want both the raw
|
||||
substring and later field access on the same instance, call `reset()` on that
|
||||
object or array (as below). To re-parse the whole document from the start,
|
||||
use `document::rewind()` instead (see [Rewinding](#rewinding)).
|
||||
Because `raw_json()` consumes to object or the array, if you want to both have
|
||||
access to the raw string, and also use the array or object, you should call `reset()`.
|
||||
|
||||
```cpp
|
||||
simdjson::ondemand::parser parser;
|
||||
@@ -3287,7 +2831,7 @@ simdjson::padded_string docdata = R"({"value":123})"_padded;
|
||||
simdjson::ondemand::document doc = parser.iterate(docdata);
|
||||
simdjson::ondemand::object obj = doc.get_object();
|
||||
string_view token = obj.raw_json(); // gives you `{"value":123}`
|
||||
obj.reset(); // re-open the same object for further iteration
|
||||
obj.reset(); // revise the object
|
||||
uint64_t x = obj["value"]; // gives me 123
|
||||
```
|
||||
|
||||
@@ -3828,7 +3372,7 @@ Performance tips
|
||||
std::string_view year = data["year"];
|
||||
std::string_view rating = data["rating"];
|
||||
```
|
||||
- You will get better performance if you seek the keys in the order in which they appear in the document. So if processing `{"a":1, "b":2, "c":3}`, do `value1 = data["a"]; value2 = data["b"]; value3 data["c"];` and not `value2 = data["b"]; value1 = data["a"]; value3 data["c"];`. Of course, it is not always possible to know for sure in which order the keys appear. See also [key selectors](#key-selectors), which extract a fixed, known set of fields in a single pass regardless of their order in the document.
|
||||
- You will get better performance if you seek the keys in the order in which they appear in the document. So if processing `{"a":1, "b":2, "c":3}`, do `value1 = data["a"]; value2 = data["b"]; value3 data["c"];` and not `value2 = data["b"]; value1 = data["a"]; value3 data["c"];`. Of course, it is not always possible to know for sure in which order the keys appear.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ speed and high convenience.
|
||||
* [Overview: string_builder](#overview--string-builder)
|
||||
* [Example: string_builder](#example--string-builder)
|
||||
* [C++26 static reflection](#c--26-static-reflection)
|
||||
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
|
||||
+ [Without `string_buffer` instance](#without--string-buffer--instance)
|
||||
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
|
||||
+ [Pretty formatted (fractured JSON)](#pretty-formatted-fractured-json)
|
||||
@@ -261,48 +260,6 @@ automatically. In most cases, it should work automatically:
|
||||
```
|
||||
|
||||
|
||||
#### Renaming and skipping fields with annotations
|
||||
|
||||
|
||||
**This is experimental: the syntax may change slightly in the future.**
|
||||
|
||||
When using C++26 static reflection for automatic serialization (and deserialization),
|
||||
you can annotate your struct members to rename the corresponding JSON keys or to
|
||||
exclude fields from the JSON representation.
|
||||
|
||||
|
||||
The syntax is:
|
||||
|
||||
```cpp
|
||||
// rename cppFieldName (in C++) to json_key_name (in JSON)
|
||||
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
|
||||
// do not serialize or deserialize this field
|
||||
[[= simdjson::skip]] int internalState;
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
int age = 0;
|
||||
};
|
||||
```
|
||||
|
||||
Serialization then produces:
|
||||
|
||||
```cpp
|
||||
Person p{"Alice", "Smith", 999, 30};
|
||||
std::string json = simdjson::to_json(p);
|
||||
// json == R"({"first_name":"Alice","last_name":"Smith","age":30})"
|
||||
// Note: internalCache is omitted entirely.
|
||||
```
|
||||
|
||||
The `skip` annotation also affects deserialization: the field keeps its default value
|
||||
and any corresponding key in the input JSON is ignored.
|
||||
|
||||
### Without `string_buffer` instance
|
||||
|
||||
In some instances, you might want to create a string directly from your own data type.
|
||||
|
||||
@@ -32,21 +32,6 @@ your code with the `SIMDJSON_STATIC_REFLECTION` macro set:
|
||||
The `simdjson::compile_time::parse_json` function parses a JSON document at **compile time** and returns a `constexpr` structure reflecting its content. We support the full range of JSON values, which are mapped to C++ types as in
|
||||
the following table.
|
||||
|
||||
For convenience, you can also use the `""_json` user-defined literal operator, which is available in the `simdjson::literals` namespace:
|
||||
|
||||
```cpp
|
||||
using namespace simdjson::literals;
|
||||
|
||||
constexpr auto cfg = R"(
|
||||
{
|
||||
"port": 8080,
|
||||
"host": "localhost"
|
||||
}
|
||||
)"_json;
|
||||
```
|
||||
|
||||
Alternatively, you can use the qualified name `simdjson::literals::operator""_json`.
|
||||
|
||||
|
||||
| JSON type | C++ type |
|
||||
|----------------|----------------------------------|
|
||||
@@ -76,8 +61,6 @@ You can do so, at compile-time, as follows:
|
||||
|
||||
|
||||
```cpp
|
||||
using namespace simdjson::literals;
|
||||
|
||||
constexpr auto cfg = R"(
|
||||
|
||||
{
|
||||
@@ -96,8 +79,6 @@ constexpr auto cfg = R"(
|
||||
You can nest objects and arrays:
|
||||
|
||||
```cpp
|
||||
using namespace simdjson::literals;
|
||||
|
||||
constexpr auto data = R"(
|
||||
|
||||
{
|
||||
@@ -117,8 +98,6 @@ constexpr auto data = R"(
|
||||
Top-level arrays are allowed:
|
||||
|
||||
```cpp
|
||||
using namespace simdjson::literals;
|
||||
|
||||
constexpr auto arr = R"(
|
||||
|
||||
[1, 2, 3]
|
||||
@@ -137,8 +116,6 @@ want to check that it conforms to your expectation. You can do so with concepts.
|
||||
Let us consider this example:
|
||||
|
||||
```cpp
|
||||
using namespace simdjson::literals;
|
||||
|
||||
constexpr auto config = R"(
|
||||
|
||||
[
|
||||
|
||||
+1
-70
@@ -125,42 +125,6 @@ Further, they may use the AreFileApisANSI function to determine whether
|
||||
the filename is interpreted using the ANSI or the system default OEM
|
||||
codepage, and they may call SetFileApisToOEM accordingly.
|
||||
|
||||
|
||||
**Advanced feature:**
|
||||
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view`
|
||||
from a file on disk without copying the file contents into your own buffer.
|
||||
On POSIX systems (Linux, macOS, BSD, ...) it uses `mmap` for true zero-copy
|
||||
access. On Windows it is available as an **opt-in** feature and requires:
|
||||
|
||||
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
|
||||
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
|
||||
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
|
||||
and linking `onecore.lib` manually if you are consuming simdjson as a
|
||||
pre-built library.
|
||||
2. `#include <windows.h>` before `#include "simdjson.h"` in every
|
||||
translation unit where you want to use `padded_memory_map`.
|
||||
|
||||
When enabled on Windows, the implementation uses `CreateFileMapping2` and
|
||||
`MapViewOfFile3` for true zero-copy mapping whenever the file does not end
|
||||
within `SIMDJSON_PADDING` bytes of a page boundary; otherwise it falls back
|
||||
to reading the file into a padded heap buffer. If those requirements are
|
||||
not met, the class is not declared and the code below will fail to compile.
|
||||
|
||||
```cpp
|
||||
#ifdef _WIN32
|
||||
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
// ...
|
||||
simdjson::padded_memory_map map(TWITTER_JSON);
|
||||
if (!map.is_valid()) { /* handle error */ }
|
||||
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
|
||||
ondemand::document doc = parser.iterate(view); // parse the JSON
|
||||
```
|
||||
|
||||
Using memory-file mapping requires some care. The file should not be modified while you are
|
||||
accessing it.
|
||||
|
||||
Using the Parsed JSON
|
||||
---------------------
|
||||
|
||||
@@ -188,23 +152,6 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to `1` when building simdjson, you can get that `-0` is mapped to `-0.0`
|
||||
as in JavaScript. You can get the desired effect by building simdjson with cmake setting the
|
||||
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to on: `cmake -B build -D SIMDJSON_MINUS_ZERO_AS_FLOAT=ON`.
|
||||
* **Big Integer Support (opt-in):** By default, integers that exceed the 64-bit range cause parsing to fail with `BIGINT_ERROR`. You can opt in to big integer support so that these numbers are stored as raw digit strings on the tape instead:
|
||||
```cpp
|
||||
simdjson::dom::parser parser;
|
||||
parser.number_as_string(true); // opt-in, default false
|
||||
simdjson::dom::element doc;
|
||||
auto error = parser.parse("[1, 123456789012345678901]"_padded).get(doc);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
for (simdjson::dom::element elem : doc) {
|
||||
if (elem.is_bigint()) {
|
||||
std::string_view digits;
|
||||
error = elem.get_bigint().get(digits);
|
||||
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
|
||||
std::cout << "big integer: " << digits << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
When enabled, big integers have type `element_type::BIGINT`. Calling `get_int64()`, `get_uint64()`, or `get_double()` on a big integer returns `INCORRECT_TYPE`. Normal numbers (int64, uint64, double) are unaffected.
|
||||
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`.
|
||||
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
|
||||
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
|
||||
@@ -217,7 +164,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
|
||||
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
|
||||
with the `size()` method.
|
||||
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
|
||||
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::STRING`, `simdjson::dom::element_type::BOOL`, `simdjson::dom::element_type::NULL_VALUE` or, `simdjson::dom::element_type::BIGINT` (when big integer support is enabled).
|
||||
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::STRING`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`.
|
||||
* **Output to streams and strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`) or a prettified string version (`simdjson::prettify(element)`). Numbers are serialized as 64-bit floating-point numbers (`double`).
|
||||
|
||||
### Examples
|
||||
@@ -790,9 +737,6 @@ 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
|
||||
@@ -905,19 +849,6 @@ simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len,
|
||||
|
||||
Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes.
|
||||
|
||||
If you are compiling your project with C++17 or better, you can use a `simdjson::padded_input`:
|
||||
|
||||
```cpp
|
||||
simdjson::dom::parser parser;
|
||||
std::string_view json = "[1,2,3]";
|
||||
simdjson::padded_input input(json); // Automatically pads if needed
|
||||
simdjson::dom::element element = parser.parse(input);
|
||||
```
|
||||
|
||||
The actual padding only occurs if the JSON string ends near the boundary of a memory page, which is uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input` on systems without a page size of at least 4096: virtually all systems qualify except for some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, an `simdjson::padded_input` instance will not copy the data and will only act
|
||||
as a view (it does not own the memory).
|
||||
|
||||
|
||||
Performance Tips
|
||||
---------------------
|
||||
|
||||
|
||||
@@ -33,9 +33,6 @@ compiles *all* the implementations into the executable. On Intel, it will includ
|
||||
(icelake, haswell, westmere and fallback), on 64-bit ARM it will include just one since running dispatching is unnecessary, and on PPC
|
||||
it will include 2 (ppc64 and fallback).
|
||||
|
||||
On Loongson processors, LASX runtime dispatching is only enabled on GCC 15+, not on LLVM or older versions of GCC.
|
||||
Thus unless you compile specifically for LASX or use GCC 15+, you will not benefit from LASX support.
|
||||
|
||||
If you know more about where you're going to run and want to save the space, you can disable any of
|
||||
these implementations at compile time with `-DSIMDJSON_IMPLEMENTATION_X=0` (where X is ICELAKE, HASWELL,
|
||||
WESTMERE, ARM64, PPC64, LSX, LASX and FALLBACK).
|
||||
|
||||
+29
-361
@@ -22,12 +22,10 @@ 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)
|
||||
- [C++20 features](#c20-features)
|
||||
- [C++26 features (static reflection)](#c26-features-static-reflection)
|
||||
|
||||
Motivation
|
||||
-----------
|
||||
@@ -134,7 +132,7 @@ E.g., `[1,2]{"32":1}` is recognized as two documents.
|
||||
Some official formats **(non-exhaustive list)**:
|
||||
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/)
|
||||
- [JSON lines (JSONL)](http://jsonlines.org/)
|
||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
|
||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson!
|
||||
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
||||
|
||||
API
|
||||
@@ -156,86 +154,6 @@ for (auto doc : docs) {
|
||||
|
||||
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
|
||||
|
||||
|
||||
Streaming directly from a memory-mapped file
|
||||
--------------------------------------------
|
||||
|
||||
When your input is a large NDJSON / JSON-lines file on disk, the most efficient
|
||||
way to feed `iterate_many` is to use `simdjson::padded_memory_map`. It returns
|
||||
a `padded_string_view` with the right amount of trailing padding, so you can
|
||||
hand it straight to `iterate_many` without ever copying the file contents into
|
||||
your own buffer.
|
||||
|
||||
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
|
||||
default. On Windows it is an **opt-in** feature with the following
|
||||
requirements:
|
||||
|
||||
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
|
||||
you consume simdjson as a pre-built library — define
|
||||
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
|
||||
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
|
||||
add `onecore.lib` to your link line yourself. The Windows
|
||||
implementation uses the modern memory APIs `CreateFileMapping2` /
|
||||
`MapViewOfFile3`, which are available starting with that version of
|
||||
Windows and are exported by `onecore.lib`.
|
||||
2. `#include <windows.h>` before `#include "simdjson.h"` in every
|
||||
translation unit where you want to use `padded_memory_map`. simdjson
|
||||
deliberately does not pull in `<windows.h>` itself, so the class is
|
||||
only declared when the Win32 types are already visible.
|
||||
|
||||
If either requirement is not met on Windows, the `padded_memory_map` class is
|
||||
not declared at all and any code that references it fails to compile with an
|
||||
"unknown identifier" error. The availability of the class can be tested with
|
||||
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
|
||||
|
||||
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
|
||||
memory with zero copies. On Windows (when enabled), it uses
|
||||
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
|
||||
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
|
||||
boundary; for those rare cases, it transparently falls back to reading
|
||||
the file into a heap-allocated padded buffer so that the returned view
|
||||
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
|
||||
|
||||
```cpp
|
||||
#ifdef _WIN32
|
||||
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
|
||||
// ...
|
||||
|
||||
simdjson::padded_memory_map map("huge_stream.ndjson");
|
||||
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
|
||||
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(map.view()).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
|
||||
for (auto doc : stream) {
|
||||
// process each JSON document in the stream
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
|
||||
only valid while the `padded_memory_map` instance is alive, so keep `map`
|
||||
alive for as long as you are iterating the stream.
|
||||
|
||||
The file must not be modified while the memory map is in use. If you need a
|
||||
fully independent copy of the data, use `simdjson::padded_string::load(...)`
|
||||
instead.
|
||||
|
||||
If you prefer single-document parsing on a memory-mapped file, the same
|
||||
pattern applies to `parser.iterate(...)`:
|
||||
|
||||
```cpp
|
||||
simdjson::padded_memory_map map(myfilename);
|
||||
if (!map.is_valid()) { /* handle error */ }
|
||||
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
|
||||
ondemand::document doc = parser.iterate(view); // parse the JSON
|
||||
```
|
||||
|
||||
## Use cases
|
||||
|
||||
From [jsonlines.org](http://jsonlines.org/examples/):
|
||||
@@ -346,131 +264,39 @@ Importantly, you should only call `truncated_bytes()` after iterating through al
|
||||
Comma-separated documents
|
||||
-----------
|
||||
|
||||
To parse comma-separated documents like `{"a":1},{"b":2},{"c":3}`, use the `stream_format::comma_delimited` parameter:
|
||||
We also support comma-separated documents, but with some performance limitations. The `iterate_many` function takes in an option to allow parsing of comma separated documents (which defaults on false). In this mode, the entire buffer is processed in one batch. Therefore, the total size of the document should not exceed the maximal capacity of the parser (4 GB). This mode also effectively disallow multithreading. It is therefore mostly suitable for not "very large" inputs. In this mode, the batch_size parameter
|
||||
is effectively ignored, as it is set to at least the document size.
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : stream) {
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
// Prints: {"a":1}
|
||||
// {"b":2}
|
||||
// {"c":3}
|
||||
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream doc_stream;
|
||||
// We pass '32' as the batch size, but it is a bogus parameter because, since
|
||||
// we pass 'true' to the allow_comma parameter, the batch size will be set to at least
|
||||
// the document size.
|
||||
auto error = parser.iterate_many(json, 32, true).get(doc_stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : doc_stream) {
|
||||
std::cout << doc.type() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
This will print:
|
||||
|
||||
```
|
||||
|
||||
Whitespace around the commas is allowed:
|
||||
```cpp
|
||||
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
|
||||
number
|
||||
number
|
||||
number
|
||||
number
|
||||
string
|
||||
string
|
||||
string
|
||||
object
|
||||
array
|
||||
```
|
||||
|
||||
Nested commas inside objects and arrays are preserved:
|
||||
```cpp
|
||||
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
|
||||
// Correctly parses as 2 documents, not 6
|
||||
```
|
||||
|
||||
Mixed document types are supported:
|
||||
```cpp
|
||||
auto json = R"(1, 2, 3, 4, "a", "b", "c", {"hello": "world"}, [1, 2, 3])"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream doc_stream;
|
||||
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited).get(doc_stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : doc_stream) {
|
||||
std::cout << doc.type() << std::endl;
|
||||
}
|
||||
// Prints: number number number number string string string object array
|
||||
```
|
||||
|
||||
Extra top-level separators are tolerated for compatibility with the legacy
|
||||
`allow_comma_separated` behavior. For example, leading commas, trailing commas,
|
||||
and repeated commas are treated as empty separators rather than documents.
|
||||
|
||||
### Legacy `allow_comma_separated` parameter (deprecated)
|
||||
|
||||
The `allow_comma_separated` boolean parameter is deprecated. When set to `true`, it now internally maps to `stream_format::comma_delimited`.
|
||||
|
||||
The old single-batch limitation no longer applies - comma-delimited parsing now supports multi-batch processing and threading for optimal performance on large files.
|
||||
|
||||
JSON Text Sequences (RFC 7464)
|
||||
------------------------------
|
||||
|
||||
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
|
||||
|
||||
Example input:
|
||||
```
|
||||
<RS>{"name":"doc1"}<LF>
|
||||
<RS>{"name":"doc2"}<LF>
|
||||
<RS>{"name":"doc3"}<LF>
|
||||
```
|
||||
|
||||
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
|
||||
|
||||
```cpp
|
||||
// Build input with RS (0x1E) and LF (0x0A) delimiters
|
||||
std::string input_str;
|
||||
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
|
||||
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
|
||||
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
|
||||
simdjson::padded_string input(input_str);
|
||||
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::json_sequence).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : stream) {
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
The `stream_format` enum has the following values:
|
||||
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
|
||||
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
|
||||
- `stream_format::comma_delimited`: Comma-separated JSON documents
|
||||
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
|
||||
|
||||
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
|
||||
|
||||
JSON Array As A Document Stream
|
||||
-------------------------------
|
||||
|
||||
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
|
||||
|
||||
```cpp
|
||||
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited_array).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : stream) {
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
// Prints: {"a":1}
|
||||
// {"b":2}
|
||||
// {"c":3}
|
||||
```
|
||||
|
||||
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
|
||||
|
||||
```cpp
|
||||
// All of these work:
|
||||
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
|
||||
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
|
||||
auto c = R"([])"_padded; // empty array → 0 docs
|
||||
```
|
||||
|
||||
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `iterate_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
|
||||
|
||||
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
|
||||
|
||||
|
||||
C++20 features
|
||||
--------------------
|
||||
@@ -578,161 +404,3 @@ Otherwise you may use this longer version for explicit handling of errors:
|
||||
cars.push_back(c);
|
||||
}
|
||||
```
|
||||
|
||||
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||
in the order they appear in the JSON document.
|
||||
|
||||
C++26 features (static reflection)
|
||||
-----------------------------------
|
||||
|
||||
If you have a C++26 compatible compiler with [P2996](https://wg21.link/P2996)
|
||||
static reflection support, you can compile the simdjson library with the
|
||||
`SIMDJSON_STATIC_REFLECTION` macro set to `1`. When this is the case, simdjson
|
||||
can deserialize a stream of JSON documents directly into your own structures
|
||||
**without** writing any `tag_invoke` function. The library inspects the
|
||||
non-static public members of your type at compile time and produces the
|
||||
parsing code automatically.
|
||||
|
||||
```cpp
|
||||
#define SIMDJSON_STATIC_REFLECTION 1
|
||||
#include "simdjson.h"
|
||||
```
|
||||
|
||||
Consider the same `Car` structure used in the C++20 example, but **without**
|
||||
any `tag_invoke` glue:
|
||||
|
||||
```cpp
|
||||
struct Car {
|
||||
std::string make;
|
||||
std::string model;
|
||||
int year;
|
||||
std::vector<double> tire_pressure;
|
||||
};
|
||||
```
|
||||
|
||||
With C++26 static reflection enabled, you can iterate a stream of cars and
|
||||
push them into a `std::vector<Car>` directly:
|
||||
|
||||
```cpp
|
||||
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||
"tire_pressure": [ 40.1, 39.9 ] }
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||
"tire_pressure": [ 30.1, 31.0 ] }
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(json).get(stream);
|
||||
if (error) { /* handle error */ }
|
||||
std::vector<Car> cars;
|
||||
for (auto doc : stream) {
|
||||
Car c;
|
||||
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||
cars.push_back(c);
|
||||
}
|
||||
```
|
||||
|
||||
This works for every `stream_format` value supported by `iterate_many`. The
|
||||
following examples each parse the same three cars, but laid out using a
|
||||
different streaming convention.
|
||||
|
||||
### Whitespace-delimited (default, NDJSON / JSON Lines)
|
||||
|
||||
```cpp
|
||||
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||
"tire_pressure": [ 40.1, 39.9 ] }
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||
"tire_pressure": [ 30.1, 31.0 ] }
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::whitespace_delimited).get(stream);
|
||||
if (error) { /* handle error */ }
|
||||
std::vector<Car> cars;
|
||||
for (auto doc : stream) {
|
||||
cars.push_back((Car)doc); // throws on error
|
||||
}
|
||||
```
|
||||
|
||||
### Comma-delimited documents
|
||||
|
||||
```cpp
|
||||
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||
"tire_pressure": [ 40.1, 39.9 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||
"tire_pressure": [ 30.1, 31.0 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited).get(stream);
|
||||
if (error) { /* handle error */ }
|
||||
std::vector<Car> cars;
|
||||
for (auto doc : stream) {
|
||||
Car c;
|
||||
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||
cars.push_back(c);
|
||||
}
|
||||
```
|
||||
|
||||
### A single JSON array as a stream of documents
|
||||
|
||||
When the input is a single JSON array, you can stream its elements one at a
|
||||
time without materializing the entire array as a `std::vector` upfront:
|
||||
|
||||
```cpp
|
||||
auto json = R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||
"tire_pressure": [ 40.1, 39.9 ] },
|
||||
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||
"tire_pressure": [ 30.1, 31.0 ] },
|
||||
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||
"tire_pressure": [ 29.8, 30.0 ] } ] )"_padded;
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited_array).get(stream);
|
||||
if (error) { /* handle error */ }
|
||||
std::vector<Car> cars;
|
||||
for (auto doc : stream) {
|
||||
Car c;
|
||||
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||
cars.push_back(c);
|
||||
}
|
||||
```
|
||||
|
||||
### JSON Text Sequences (RFC 7464)
|
||||
|
||||
```cpp
|
||||
// Build input with RS (0x1E) and LF (0x0A) delimiters
|
||||
std::string input_str;
|
||||
auto append = [&](std::string_view doc) {
|
||||
input_str += '\x1e'; input_str += doc; input_str += '\x0a';
|
||||
};
|
||||
append(R"({ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9 ] })");
|
||||
append(R"({ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0 ] })");
|
||||
append(R"({ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0 ] })");
|
||||
simdjson::padded_string input(input_str);
|
||||
|
||||
ondemand::parser parser;
|
||||
ondemand::document_stream stream;
|
||||
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::json_sequence).get(stream);
|
||||
if (error) { /* handle error */ }
|
||||
std::vector<Car> cars;
|
||||
for (auto doc : stream) {
|
||||
Car c;
|
||||
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||
cars.push_back(c);
|
||||
}
|
||||
```
|
||||
|
||||
In every case, the user-defined type (`Car` here) does not need a hand-written
|
||||
`tag_invoke` overload: the library generates the deserialization code from the
|
||||
type's public data members at compile time.
|
||||
|
||||
|
||||
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||
in the order they appear in the JSON document.
|
||||
+1
-194
@@ -18,7 +18,6 @@ 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)
|
||||
@@ -133,7 +132,7 @@ Whitespace Characters:
|
||||
Some official formats **(non-exhaustive list)**:
|
||||
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec)
|
||||
- [JSON lines (JSONL)](http://jsonlines.org/)
|
||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
|
||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson!
|
||||
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
||||
|
||||
API
|
||||
@@ -218,86 +217,6 @@ got full document at 29
|
||||
```
|
||||
|
||||
|
||||
|
||||
Streaming directly from a memory-mapped file
|
||||
--------------------------------------------
|
||||
|
||||
When your input is a large NDJSON / JSON-lines file on disk, the most
|
||||
efficient way to feed `parse_many` is to use `simdjson::padded_memory_map`.
|
||||
It returns a `padded_string_view` with the right amount of trailing padding,
|
||||
so you can pass it directly to `parse_many` without copying the file content
|
||||
into your own buffer first.
|
||||
|
||||
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
|
||||
default. On Windows it is an **opt-in** feature with the following
|
||||
requirements:
|
||||
|
||||
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
|
||||
you consume simdjson as a pre-built library — define
|
||||
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
|
||||
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
|
||||
add `onecore.lib` to your link line yourself. The Windows
|
||||
implementation uses the modern memory APIs `CreateFileMapping2` /
|
||||
`MapViewOfFile3`, which are available starting with that version of
|
||||
Windows and are exported by `onecore.lib`.
|
||||
2. `#include <windows.h>` before `#include "simdjson.h"` in every
|
||||
translation unit where you want to use `padded_memory_map`. simdjson
|
||||
deliberately does not pull in `<windows.h>` itself, so the class is
|
||||
only declared when the Win32 types are already visible.
|
||||
|
||||
If either requirement is not met on Windows, the `padded_memory_map` class is
|
||||
not declared at all and any code that references it fails to compile with an
|
||||
"unknown identifier" error. The availability of the class can be tested with
|
||||
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
|
||||
|
||||
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
|
||||
memory with zero copies. On Windows (when enabled), it uses
|
||||
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
|
||||
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
|
||||
boundary; for those rare cases, it transparently falls back to reading
|
||||
the file into a heap-allocated padded buffer so that the returned view
|
||||
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
|
||||
|
||||
```cpp
|
||||
#ifdef _WIN32
|
||||
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
|
||||
#endif
|
||||
#include "simdjson.h"
|
||||
|
||||
// ...
|
||||
|
||||
simdjson::padded_memory_map map("huge_stream.ndjson");
|
||||
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
|
||||
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::document_stream stream;
|
||||
auto error = parser.parse_many(map.view()).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
|
||||
for (auto doc : stream) {
|
||||
// process each JSON document in the stream
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
|
||||
only valid while the `padded_memory_map` instance is alive, so keep `map`
|
||||
alive for as long as you are iterating the stream.
|
||||
|
||||
The file must not be modified while the memory map is in use. If you need a
|
||||
fully independent copy of the data, use `simdjson::padded_string::load(...)`
|
||||
instead.
|
||||
|
||||
If you prefer single-document parsing on a memory-mapped file, the same
|
||||
pattern applies to `parser.parse(...)`:
|
||||
|
||||
```cpp
|
||||
simdjson::padded_memory_map map(myfilename);
|
||||
if (!map.is_valid()) { /* handle error */ }
|
||||
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
|
||||
simdjson::dom::element doc = parser.parse(view); // parse the JSON
|
||||
```
|
||||
|
||||
Incomplete streams
|
||||
-----------
|
||||
|
||||
@@ -320,115 +239,3 @@ Consider the following example where a truncated document (`{"key":"intentionall
|
||||
|
||||
|
||||
Importantly, you should only call `truncated_bytes()` after iterating through all of the documents since the stream cannot tell whether there are truncated documents at the very end when it may not have accessed that part of the data yet.
|
||||
|
||||
JSON Text Sequences (RFC 7464)
|
||||
------------------------------
|
||||
|
||||
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
|
||||
|
||||
Example input:
|
||||
```
|
||||
<RS>{"name":"doc1"}<LF>
|
||||
<RS>{"name":"doc2"}<LF>
|
||||
<RS>{"name":"doc3"}<LF>
|
||||
```
|
||||
|
||||
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
|
||||
|
||||
```cpp
|
||||
// Build input with RS (0x1E) and LF (0x0A) delimiters
|
||||
std::string input_str;
|
||||
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
|
||||
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
|
||||
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
|
||||
simdjson::padded_string input(input_str);
|
||||
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::document_stream stream;
|
||||
auto error = parser.parse_many(input, simdjson::dom::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::json_sequence).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : stream) {
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
The `stream_format` enum has the following values:
|
||||
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
|
||||
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
|
||||
- `stream_format::comma_delimited`: Comma-separated JSON documents
|
||||
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
|
||||
|
||||
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
|
||||
|
||||
Comma-Separated Documents
|
||||
-------------------------
|
||||
|
||||
Some systems produce JSON documents separated by commas, like `{"a":1},{"b":2},{"c":3}`. This is common when extracting elements from a JSON array or when APIs return comma-separated results.
|
||||
|
||||
To parse comma-separated documents, use the `stream_format::comma_delimited` parameter:
|
||||
|
||||
```cpp
|
||||
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::document_stream stream;
|
||||
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : stream) {
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
// Prints: {"a":1}
|
||||
// {"b":2}
|
||||
// {"c":3}
|
||||
```
|
||||
|
||||
Whitespace around the commas is allowed:
|
||||
```cpp
|
||||
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
|
||||
```
|
||||
|
||||
Nested commas inside objects and arrays are preserved:
|
||||
```cpp
|
||||
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
|
||||
// Correctly parses as 2 documents, not 6
|
||||
```
|
||||
|
||||
Extra top-level separators are tolerated for compatibility with the legacy
|
||||
On-Demand comma-separated mode. Leading commas, trailing commas, and repeated
|
||||
commas are treated as empty separators rather than documents.
|
||||
|
||||
Unlike the legacy `allow_comma_separated` parameter, `stream_format::comma_delimited` supports multi-batch processing and threading for optimal performance on large files.
|
||||
|
||||
JSON Array As A Document Stream
|
||||
-------------------------------
|
||||
|
||||
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
|
||||
|
||||
```cpp
|
||||
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
|
||||
simdjson::dom::parser parser;
|
||||
simdjson::dom::document_stream stream;
|
||||
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
|
||||
simdjson::stream_format::comma_delimited_array).get(stream);
|
||||
if (error) { std::cerr << error << std::endl; return; }
|
||||
for (auto doc : stream) {
|
||||
std::cout << doc << std::endl;
|
||||
}
|
||||
// Prints: {"a":1}
|
||||
// {"b":2}
|
||||
// {"c":3}
|
||||
```
|
||||
|
||||
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
|
||||
|
||||
```cpp
|
||||
// All of these work:
|
||||
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
|
||||
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
|
||||
auto c = R"([])"_padded; // empty array → 0 docs
|
||||
```
|
||||
|
||||
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `parse_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
|
||||
|
||||
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
|
||||
|
||||
+4
-26
@@ -204,31 +204,9 @@ but can be significantly larger. E.g., Apple systems favour pages spanning 16 ki
|
||||
|
||||
In effect, it means that you can almost always read a few bytes beyond your current buffer---without
|
||||
allocating extra memory. However, tools such as valgrind or memory sanitizers will flag such behavior as unsafe.
|
||||
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`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
|
||||
|
||||
Example usage:
|
||||
|
||||
```cpp
|
||||
std::string_view json = get_json_data();
|
||||
simdjson::padded_input input(json); // Automatically pads if needed
|
||||
auto result = parser.parse(input);
|
||||
|
||||
// Also works with std::string, considering capacity
|
||||
std::string json_str = get_json_string();
|
||||
json_str.reserve(json_str.size() + 100); // Reserve extra space
|
||||
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
|
||||
auto result2 = parser.parse(input2);
|
||||
```
|
||||
|
||||
This simplifies padding management compared to manually checking and allocating.
|
||||
|
||||
|
||||
More generally, the following code provides a portable example.
|
||||
|
||||
Nevertheless, you can still make sure of this capability in your code if you are an expert
|
||||
programmer and you are willing to silence sanitizer warnings. The following code provides
|
||||
a portable example.
|
||||
|
||||
|
||||
The conditional compilation checks for the `_MSC_VER` macro (indicating Microsoft Visual Studio)
|
||||
@@ -272,7 +250,7 @@ long page_size() {
|
||||
}
|
||||
|
||||
// Returns true if the buffer + len + simdjson::SIMDJSON_PADDING crosses the
|
||||
// page boundary. Assumes len != 0.
|
||||
// page boundary.
|
||||
bool need_allocation(const char *buf, size_t len) {
|
||||
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size())
|
||||
+ simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size()));
|
||||
|
||||
@@ -93,14 +93,6 @@ Float values are represented as two 64-bit tape elements:
|
||||
|
||||
Performance consideration: We store numbers of the main tape because we believe that locality of reference is helpful for performance.
|
||||
|
||||
## Big Integers
|
||||
|
||||
When a JSON integer exceeds the 64-bit range (both signed and unsigned), it is classified as a big integer. By default, parsing returns `BIGINT_ERROR`. When `parser.number_as_string(true)` is set, the raw digits are stored on the string tape using the same format as strings (4-byte little-endian length prefix, followed by the UTF-8 digit bytes, followed by a null terminator).
|
||||
|
||||
A big integer is represented on the main tape as the 64-bit tape element `('Z' << 56) + x` where the payload `x` is the location on the string tape of the null-terminated digit string.
|
||||
|
||||
For example, the JSON value `99999999999999999999` would be stored as the string `"99999999999999999999"` on the string tape, with a `'Z'` tag on the main tape. Negative big integers include the leading minus sign.
|
||||
|
||||
## Root node
|
||||
|
||||
Each JSON document will have two special 64-bit tape elements representing a root node, one at the beginning and one at the end.
|
||||
|
||||
@@ -54,9 +54,6 @@ static void print_json(std::ostream& os, simdjson::dom::element element) noexcep
|
||||
case simdjson::dom::element_type::NULL_VALUE:
|
||||
os << "null" << endl;
|
||||
break;
|
||||
case simdjson::dom::element_type::BIGINT:
|
||||
os << element.get_bigint().value_unsafe() << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#ifndef SIMDJSON_ANNOTATIONS_H
|
||||
#define SIMDJSON_ANNOTATIONS_H
|
||||
|
||||
/**
|
||||
* @file annotations.h
|
||||
* @brief Provides compile-time annotations for simdjson structures.
|
||||
* This header defines annotations that can be applied to data members of structures
|
||||
* to control how they are serialized/deserialized with simdjson.
|
||||
* This is currently experimental and subject to change (syntax and semantics may evolve).
|
||||
*/
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
#include <meta>
|
||||
#include <string_view>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// Structural compile-time string — char array avoids the pointer-based
|
||||
// 'reflect_constant failed' that occurs with const char* / string_view members.
|
||||
template <size_t N>
|
||||
struct fixed_string {
|
||||
char data[N];
|
||||
|
||||
consteval fixed_string(const char (&s)[N]) noexcept {
|
||||
for (size_t i = 0; i < N; ++i) { data[i] = s[i]; }
|
||||
}
|
||||
|
||||
consteval std::string_view view() const noexcept { return {data, N - 1}; }
|
||||
|
||||
consteval bool operator==(const fixed_string&) const noexcept = default;
|
||||
};
|
||||
|
||||
// Usage: [[= simdjson::rename<"first_name">]] std::string firstName;
|
||||
namespace detail {
|
||||
template <fixed_string Name>
|
||||
struct rename_t {
|
||||
static constexpr auto name = Name;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <fixed_string Name>
|
||||
inline constexpr detail::rename_t<Name> rename{};
|
||||
|
||||
// Usage: [[= simdjson::skip]] int internalCache;
|
||||
namespace detail {
|
||||
struct skip_tag {};
|
||||
} // namespace detail
|
||||
|
||||
inline constexpr detail::skip_tag skip{};
|
||||
|
||||
// Returns the JSON key for a reflected data member.
|
||||
template <auto dm>
|
||||
consteval const char* get_json_key_name() {
|
||||
template for (constexpr auto ann :
|
||||
std::define_static_array(std::meta::annotations_of(dm))) {
|
||||
constexpr auto ann_type = std::meta::type_of(ann);
|
||||
if constexpr (std::meta::has_template_arguments(ann_type) &&
|
||||
std::meta::template_of(ann_type) == ^^detail::rename_t) {
|
||||
constexpr auto args =
|
||||
std::define_static_array(std::meta::template_arguments_of(ann_type));
|
||||
return std::define_static_string([:args[0]:].view());
|
||||
}
|
||||
}
|
||||
return std::define_static_string(std::meta::identifier_of(dm));
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_ANNOTATIONS_H
|
||||
@@ -21,10 +21,6 @@ SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS
|
||||
|
||||
/** The maximum document size supported by simdjson. */
|
||||
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
|
||||
/** The maximum depth of nested objects and arrays supported by simdjson.
|
||||
A depth of SIMDJSON_MAXSIZE_BYTES/2 is not reasonable and would be
|
||||
adversarial, but it serves as an upper bound for validation purposes. */
|
||||
constexpr size_t SIMDJSON_MAX_DEPTH = SIMDJSON_MAXSIZE_BYTES/2;
|
||||
|
||||
/**
|
||||
* The amount of padding needed in a buffer to parse JSON.
|
||||
@@ -50,21 +46,6 @@ struct padded_string;
|
||||
class padded_string_view;
|
||||
enum class stage1_mode;
|
||||
|
||||
/**
|
||||
* Stream format for parse_many/iterate_many.
|
||||
*/
|
||||
enum class stream_format {
|
||||
whitespace_delimited, ///< Whitespace-delimited JSON documents (default, includes NDJSON/JSONL)
|
||||
json_sequence, ///< RFC 7464 JSON text sequences (RS-delimited)
|
||||
comma_delimited, ///< Comma-separated JSON documents (e.g., `{...},{...},{...}`)
|
||||
comma_delimited_array ///< A single JSON array whose elements are iterated as
|
||||
///< comma-separated documents (e.g., `[{...},{...},{...}]`).
|
||||
///< The parser strips the outer `[` / `]` plus any
|
||||
///< surrounding JSON whitespace (space, tab, LF, CR)
|
||||
///< and then behaves like `comma_delimited` over the
|
||||
///< remaining bytes.
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -30,10 +30,6 @@ 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__)
|
||||
@@ -49,6 +45,9 @@ 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
|
||||
@@ -56,7 +55,6 @@ 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
|
||||
@@ -97,7 +95,6 @@ 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))
|
||||
@@ -174,15 +171,6 @@ 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
|
||||
|
||||
@@ -266,12 +266,6 @@ consteval std::pair<double, size_t> parse_double(const char *src,
|
||||
}
|
||||
} // namespace number_parsing
|
||||
|
||||
consteval auto make_data_member_options(auto&& name_str) {
|
||||
std::meta::data_member_options options{};
|
||||
options.name = std::forward<decltype(name_str)>(name_str);
|
||||
return options;
|
||||
}
|
||||
|
||||
// JSON string may contain embedded nulls, and C++26 reflection does not yet
|
||||
// support std::string_view as a data member type. As a workaround, we define
|
||||
// a custom type that holds a const char* and a size.
|
||||
@@ -326,8 +320,7 @@ using class_type = type_builder<meta_info...>::constructed_type;
|
||||
/**
|
||||
* @brief Variable template for constructing instances with values
|
||||
*/
|
||||
template <typename T, auto... Vs> constexpr T construct_from = T{Vs...};
|
||||
|
||||
template <typename T, auto... Vs> constexpr auto construct_from = T{Vs...};
|
||||
|
||||
// in JSON, there are only a few whitespace characters that are allowed
|
||||
// outside of objects, arrays, strings, and numbers.
|
||||
@@ -417,7 +410,7 @@ parse_number(std::string_view json,
|
||||
std::from_chars(json.data(), json.data() + json.size(), int_value);
|
||||
if (res.ec == std::errc()) {
|
||||
out = int_value;
|
||||
if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
|
||||
if ((res.ptr - json.data()) != scope) {
|
||||
simdjson_consteval_error(
|
||||
"Internal error: cannot agree on the character range of the float");
|
||||
}
|
||||
@@ -431,7 +424,7 @@ parse_number(std::string_view json,
|
||||
std::from_chars(json.data(), json.data() + json.size(), uint_value);
|
||||
if (res.ec == std::errc()) {
|
||||
out = uint_value;
|
||||
if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
|
||||
if ((res.ptr - json.data()) != scope) {
|
||||
simdjson_consteval_error(
|
||||
"Internal error: cannot agree on the character range of the float");
|
||||
}
|
||||
@@ -540,7 +533,7 @@ parse_string(std::string_view json) {
|
||||
// present, we have an error (isolated high surrogate), which we
|
||||
// tolerate by substituting the substitution_code_point.
|
||||
if (end - cursor < 6 || *cursor != '\\' ||
|
||||
*(cursor + 1) != 'u') {
|
||||
*(cursor + 1) != 'u' > 0xFFFF) {
|
||||
code_point = substitution_code_point;
|
||||
} else { // we have \u following the high surrogate
|
||||
cursor += 2; // skip \u
|
||||
@@ -894,19 +887,10 @@ parse_json_array_impl(const std::string_view json) {
|
||||
std::size_t count = values.size() - 1;
|
||||
// We assume all elements have the same type as the first element.
|
||||
// However, if the array is heterogeneous, we should use std::variant.
|
||||
auto elem_type = std::meta::type_of(values[1]);
|
||||
// String literals reflected via reflect_constant_string have type const
|
||||
// char[N], but when passed as template auto parameters they decay to
|
||||
// const char*. Use const char* as the element type so that
|
||||
// construct_from can aggregate-initialize the array.
|
||||
if (std::meta::is_array_type(elem_type) &&
|
||||
std::meta::remove_all_extents(elem_type) == ^^const char) {
|
||||
elem_type = ^^const char *;
|
||||
}
|
||||
auto array_type = std::meta::substitute(
|
||||
^^std::array,
|
||||
{
|
||||
elem_type, std::meta::reflect_constant(count)});
|
||||
std::meta::type_of(values[1]), std::meta::reflect_constant(count)});
|
||||
|
||||
// Create array instance with values
|
||||
values[0] = array_type;
|
||||
@@ -973,7 +957,8 @@ parse_json_object_impl(std::string_view json) {
|
||||
simdjson_consteval_error("Expected '}'");
|
||||
}
|
||||
cursor += object_size;
|
||||
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
|
||||
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed),
|
||||
{.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(parsed);
|
||||
|
||||
@@ -982,7 +967,8 @@ parse_json_object_impl(std::string_view json) {
|
||||
case '[': {
|
||||
std::string_view value(cursor, end);
|
||||
auto [parsed, array_size] = parse_json_array_impl(value);
|
||||
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
|
||||
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed),
|
||||
{.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(parsed);
|
||||
if (*(cursor + array_size - 1) != ']') {
|
||||
@@ -1003,7 +989,8 @@ parse_json_object_impl(std::string_view json) {
|
||||
}
|
||||
}
|
||||
auto dms =
|
||||
std::meta::data_member_spec(^^const char *, make_data_member_options(field_name));
|
||||
std::meta::data_member_spec(^^const char *, {
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant_string(value));
|
||||
break;
|
||||
@@ -1014,7 +1001,8 @@ parse_json_object_impl(std::string_view json) {
|
||||
}
|
||||
cursor += 4;
|
||||
|
||||
auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
|
||||
auto dms = std::meta::data_member_spec(^^bool, {
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant(true));
|
||||
break;
|
||||
@@ -1025,7 +1013,8 @@ parse_json_object_impl(std::string_view json) {
|
||||
}
|
||||
cursor += 5;
|
||||
|
||||
auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
|
||||
auto dms = std::meta::data_member_spec(^^bool, {
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant(false));
|
||||
break;
|
||||
@@ -1036,7 +1025,9 @@ parse_json_object_impl(std::string_view json) {
|
||||
}
|
||||
cursor += 4;
|
||||
|
||||
auto dms = std::meta::data_member_spec(^^std::nullptr_t, make_data_member_options(field_name));
|
||||
auto dms = std::meta::data_member_spec(^^std::nullptr_t,
|
||||
{
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant(nullptr));
|
||||
break;
|
||||
@@ -1060,19 +1051,22 @@ parse_json_object_impl(std::string_view json) {
|
||||
if (std::holds_alternative<int64_t>(out)) {
|
||||
int64_t int_value = std::get<int64_t>(out);
|
||||
auto dms =
|
||||
std::meta::data_member_spec(^^int64_t, make_data_member_options(field_name));
|
||||
std::meta::data_member_spec(^^int64_t, {
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant(int_value));
|
||||
} else if (std::holds_alternative<uint64_t>(out)) {
|
||||
uint64_t uint_value = std::get<uint64_t>(out);
|
||||
auto dms =
|
||||
std::meta::data_member_spec(^^uint64_t, make_data_member_options(field_name));
|
||||
std::meta::data_member_spec(^^uint64_t, {
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant(uint_value));
|
||||
} else {
|
||||
double float_value = std::get<double>(out);
|
||||
auto dms =
|
||||
std::meta::data_member_spec(^^double, make_data_member_options(field_name));
|
||||
std::meta::data_member_spec(^^double, {
|
||||
.name = field_name});
|
||||
members.push_back(std::meta::reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant(float_value));
|
||||
}
|
||||
@@ -1117,11 +1111,16 @@ template <constevalutil::fixed_string json_str> consteval auto parse_json() {
|
||||
"Only JSON objects and arrays are supported at the top level, this "
|
||||
"limitation will be lifted in the future.");*/
|
||||
|
||||
if constexpr (json.front() == '[') {
|
||||
return [: parse_json_array_impl(json).first :];
|
||||
} else {
|
||||
return [: parse_json_object_impl(json).first :];
|
||||
}
|
||||
constexpr auto result = json.front() == '['
|
||||
? parse_json_array_impl(json)
|
||||
: parse_json_object_impl(json);
|
||||
return [: result.first :];
|
||||
/*
|
||||
if(json.front() == '[') {
|
||||
return [:parse_json_array_impl(json).first:];
|
||||
} else if(json.front() == '{') {
|
||||
// return [:parse_json_object_impl(json).first:];
|
||||
}*/
|
||||
}
|
||||
|
||||
} // namespace compile_time
|
||||
|
||||
@@ -63,16 +63,13 @@ namespace compile_time {
|
||||
template <constevalutil::fixed_string json_str> consteval auto parse_json();
|
||||
|
||||
} // namespace compile_time
|
||||
} // namespace simdjson
|
||||
|
||||
inline namespace literals {
|
||||
|
||||
template <simdjson::constevalutil::fixed_string str>
|
||||
consteval auto operator ""_json() {
|
||||
return simdjson::compile_time::parse_json<str>();
|
||||
}
|
||||
|
||||
} // namespace literals
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
|
||||
|
||||
@@ -119,4 +119,5 @@
|
||||
#define SIMDJSON_CONSTEVAL 0
|
||||
#endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
|
||||
#endif // !defined(SIMDJSON_CONSTEVAL)
|
||||
|
||||
#endif // SIMDJSON_COMPILER_CHECK_H
|
||||
|
||||
@@ -58,11 +58,6 @@ struct fixed_string {
|
||||
data[i] = str[i];
|
||||
}
|
||||
}
|
||||
constexpr fixed_string(const unsigned char (&str)[N]) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
data[i] = static_cast<char>(str[i]);
|
||||
}
|
||||
}
|
||||
char data[N];
|
||||
constexpr std::string_view view() const { return {data, N - 1}; }
|
||||
constexpr size_t size() const { return N ; }
|
||||
|
||||
@@ -37,6 +37,9 @@ 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>) {
|
||||
@@ -115,6 +118,16 @@ 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
@@ -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() = default;
|
||||
// Prevent moving
|
||||
auto_parser(auto_parser&&) = delete;
|
||||
auto_parser &operator=(auto_parser &&) noexcept = delete;
|
||||
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;
|
||||
|
||||
simdjson_warn_unused std::remove_pointer_t<parser_type> &parser() noexcept;
|
||||
|
||||
template <typename T>
|
||||
@@ -75,6 +75,11 @@ 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*>;
|
||||
@@ -85,12 +90,7 @@ 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.
|
||||
*
|
||||
* 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`.
|
||||
* It provides a convenient way to convert JSON data into C++ objects using the `auto_parser`.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
|
||||
@@ -103,9 +103,21 @@ 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;
|
||||
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, 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"
|
||||
|
||||
// Get the child
|
||||
auto child = array(tape).at(array_index);
|
||||
|
||||
@@ -33,25 +33,16 @@ inline error_code document::allocate(size_t capacity) noexcept {
|
||||
allocated_capacity = 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
if (capacity > SIMDJSON_MAXSIZE_BYTES) {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
// a pathological input like "[[[[..." would generate capacity tape elements, so
|
||||
// need a capacity of at least capacity + 1, but it is also possible to do
|
||||
// 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
|
||||
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);
|
||||
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)) {
|
||||
@@ -150,15 +141,6 @@ inline bool document::dump_raw_tape(std::ostream &os) const noexcept {
|
||||
case 'r': // we start and end with the root node
|
||||
// should we be hitting the root node?
|
||||
return false;
|
||||
case 'Z': // we have a big integer
|
||||
os << "bigint ";
|
||||
std::memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t));
|
||||
os << std::string_view(
|
||||
reinterpret_cast<const char *>(string_buf.get() + payload + sizeof(uint32_t)),
|
||||
string_length
|
||||
);
|
||||
os << '\n';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,14 +89,12 @@ simdjson_inline document_stream::document_stream(
|
||||
dom::parser &_parser,
|
||||
const uint8_t *_buf,
|
||||
size_t _len,
|
||||
size_t _batch_size,
|
||||
stream_format _format
|
||||
size_t _batch_size
|
||||
) noexcept
|
||||
: parser{&_parser},
|
||||
buf{_buf},
|
||||
len{_len},
|
||||
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
|
||||
format{_format},
|
||||
error{SUCCESS}
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
|
||||
@@ -114,7 +112,6 @@ simdjson_inline document_stream::document_stream() noexcept
|
||||
buf{nullptr},
|
||||
len{0},
|
||||
batch_size{0},
|
||||
format{stream_format::whitespace_delimited},
|
||||
error{UNINITIALIZED}
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
, use_thread(false)
|
||||
@@ -227,14 +224,7 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
|
||||
} else {
|
||||
size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index];
|
||||
size_t svlen = next_doc_index - current_index();
|
||||
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464 json_sequence
|
||||
// mode the scanner classifies RS as a scalar character, so an RS-prefixed
|
||||
// scalar document (number/true/false/null/string) has no closing structural
|
||||
// index and the slice runs all the way up to the next document's RS. RS
|
||||
// cannot legally appear in a JSON value at the source level (control
|
||||
// characters in strings must be escaped as \u001E), so stripping it is
|
||||
// safe in every stream_format.
|
||||
while(svlen > 1 && (std::isspace(static_cast<unsigned char>(start[svlen-1])) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E || (stream->format == stream_format::comma_delimited && start[svlen-1] == ','))) {
|
||||
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
|
||||
svlen--;
|
||||
}
|
||||
return std::string_view(start, svlen);
|
||||
@@ -284,35 +274,10 @@ inline size_t document_stream::next_batch_start() const noexcept {
|
||||
|
||||
inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
|
||||
size_t remaining = len - _batch_start;
|
||||
stage1_mode mode;
|
||||
if (remaining <= batch_size) {
|
||||
// Final batch
|
||||
switch (format) {
|
||||
case stream_format::json_sequence:
|
||||
mode = stage1_mode::json_sequence_final;
|
||||
break;
|
||||
case stream_format::comma_delimited:
|
||||
mode = stage1_mode::comma_delimited_final;
|
||||
break;
|
||||
default:
|
||||
mode = stage1_mode::streaming_final;
|
||||
break;
|
||||
}
|
||||
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
|
||||
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
|
||||
} else {
|
||||
// Partial batch
|
||||
switch (format) {
|
||||
case stream_format::json_sequence:
|
||||
mode = stage1_mode::json_sequence_partial;
|
||||
break;
|
||||
case stream_format::comma_delimited:
|
||||
mode = stage1_mode::comma_delimited_partial;
|
||||
break;
|
||||
default:
|
||||
mode = stage1_mode::streaming_partial;
|
||||
break;
|
||||
}
|
||||
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
|
||||
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,14 +206,12 @@ private:
|
||||
* @param buf is the raw byte buffer we need to process
|
||||
* @param len is the length of the raw byte buffer in bytes
|
||||
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
|
||||
* @param format is the stream format
|
||||
*/
|
||||
simdjson_inline document_stream(
|
||||
dom::parser &parser,
|
||||
const uint8_t *buf,
|
||||
size_t len,
|
||||
size_t batch_size,
|
||||
stream_format format = stream_format::whitespace_delimited
|
||||
size_t batch_size
|
||||
) noexcept;
|
||||
|
||||
/**
|
||||
@@ -263,8 +261,6 @@ private:
|
||||
const uint8_t *buf;
|
||||
size_t len;
|
||||
size_t batch_size;
|
||||
/** The stream format. */
|
||||
stream_format format;
|
||||
/** The error (or lack thereof) from the current document. */
|
||||
error_code error;
|
||||
size_t batch_start{0};
|
||||
|
||||
@@ -81,10 +81,6 @@ simdjson_inline simdjson_result<bool> simdjson_result<dom::element>::get_bool()
|
||||
if (error()) { return error(); }
|
||||
return first.get_bool();
|
||||
}
|
||||
simdjson_inline simdjson_result<std::string_view> simdjson_result<dom::element>::get_bigint() const noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_bigint();
|
||||
}
|
||||
|
||||
simdjson_inline bool simdjson_result<dom::element>::is_array() const noexcept {
|
||||
return !error() && first.is_array();
|
||||
@@ -114,9 +110,6 @@ simdjson_inline bool simdjson_result<dom::element>::is_bool() const noexcept {
|
||||
simdjson_inline bool simdjson_result<dom::element>::is_null() const noexcept {
|
||||
return !error() && first.is_null();
|
||||
}
|
||||
simdjson_inline bool simdjson_result<dom::element>::is_bigint() const noexcept {
|
||||
return !error() && first.is_bigint();
|
||||
}
|
||||
|
||||
simdjson_inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](std::string_view key) const noexcept {
|
||||
if (error()) { return error(); }
|
||||
@@ -225,15 +218,6 @@ inline simdjson_result<bool> element::get_bool() const noexcept {
|
||||
}
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
inline simdjson_result<std::string_view> element::get_bigint() const noexcept {
|
||||
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable());
|
||||
switch (tape.tape_ref_type()) {
|
||||
case internal::tape_type::BIGINT:
|
||||
return tape.get_string_view();
|
||||
default:
|
||||
return INCORRECT_TYPE;
|
||||
}
|
||||
}
|
||||
inline simdjson_result<const char *> element::get_c_str() const noexcept {
|
||||
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
|
||||
switch (tape.tape_ref_type()) {
|
||||
@@ -376,10 +360,6 @@ inline bool element::is_null() const noexcept {
|
||||
return tape.is_null_on_tape();
|
||||
}
|
||||
|
||||
inline bool element::is_bigint() const noexcept {
|
||||
return tape.tape_ref_type() == internal::tape_type::BIGINT;
|
||||
}
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
|
||||
inline element::operator bool() const noexcept(false) { return get<bool>(); }
|
||||
@@ -512,8 +492,6 @@ inline std::ostream& operator<<(std::ostream& out, element_type type) {
|
||||
return out << "bool";
|
||||
case element_type::NULL_VALUE:
|
||||
return out << "null";
|
||||
case element_type::BIGINT:
|
||||
return out << "bigint";
|
||||
default:
|
||||
return out << "unexpected content!!!"; // abort() usage is forbidden in the library
|
||||
}
|
||||
|
||||
@@ -21,10 +21,7 @@ enum class element_type {
|
||||
DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double.
|
||||
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
|
||||
NULL_VALUE = 'n' ///< null
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -123,14 +120,6 @@ public:
|
||||
*/
|
||||
inline simdjson_result<bool> get_bool() const noexcept;
|
||||
|
||||
/**
|
||||
* Read this element as a big integer (raw digit string).
|
||||
*
|
||||
* @returns A string_view of the raw digits, or:
|
||||
* INCORRECT_TYPE if the JSON element is not a big integer.
|
||||
*/
|
||||
inline simdjson_result<std::string_view> get_bigint() const noexcept;
|
||||
|
||||
/**
|
||||
* Whether this element is a json array.
|
||||
*
|
||||
@@ -186,11 +175,6 @@ public:
|
||||
*/
|
||||
inline bool is_null() const noexcept;
|
||||
|
||||
/**
|
||||
* Whether this element is a big integer (number exceeding 64-bit range).
|
||||
*/
|
||||
inline bool is_bigint() const noexcept;
|
||||
|
||||
/**
|
||||
* Tell whether the value can be cast to provided type (T).
|
||||
*
|
||||
@@ -549,7 +533,6 @@ public:
|
||||
simdjson_inline simdjson_result<uint64_t> get_uint64() const noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double() const noexcept;
|
||||
simdjson_inline simdjson_result<bool> get_bool() const noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_bigint() const noexcept;
|
||||
|
||||
simdjson_inline bool is_array() const noexcept;
|
||||
simdjson_inline bool is_object() const noexcept;
|
||||
@@ -560,7 +543,6 @@ public:
|
||||
simdjson_inline bool is_number() const noexcept;
|
||||
simdjson_inline bool is_bool() const noexcept;
|
||||
simdjson_inline bool is_null() const noexcept;
|
||||
simdjson_inline bool is_bigint() const noexcept;
|
||||
|
||||
simdjson_inline simdjson_result<dom::element> operator[](std::string_view key) const noexcept;
|
||||
simdjson_inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
|
||||
|
||||
@@ -72,7 +72,7 @@ inline element_metrics structure_analyzer::analyze_scalar(const dom::element& el
|
||||
metrics.complexity = 0;
|
||||
metrics.child_count = 0;
|
||||
metrics.can_inline = true;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
|
||||
switch (elem.type()) {
|
||||
case dom::element_type::STRING: {
|
||||
@@ -158,16 +158,16 @@ inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
|
||||
|
||||
// Decide layout
|
||||
if (metrics.child_count == 0) {
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
} else if (metrics.can_inline) {
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
} else if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
|
||||
metrics.recommended_layout = layout_mode::table;
|
||||
metrics.recommended_layout = layout_mode::TABLE;
|
||||
} else if (current_opts_->enable_compact_multiline &&
|
||||
max_child_complexity <= current_opts_->max_compact_array_complexity) {
|
||||
metrics.recommended_layout = layout_mode::compact_multiline;
|
||||
metrics.recommended_layout = layout_mode::COMPACT_MULTILINE;
|
||||
} else {
|
||||
metrics.recommended_layout = layout_mode::expanded;
|
||||
metrics.recommended_layout = layout_mode::EXPANDED;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
@@ -206,9 +206,9 @@ inline element_metrics structure_analyzer::analyze_object(const dom::object& obj
|
||||
|
||||
// Objects use inline or expanded (no table/compact for objects)
|
||||
if (metrics.child_count == 0 || metrics.can_inline) {
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
} else {
|
||||
metrics.recommended_layout = layout_mode::expanded;
|
||||
metrics.recommended_layout = layout_mode::EXPANDED;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
@@ -227,18 +227,8 @@ 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::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
|
||||
if (std::isnan(d) || std::isinf(d)) {
|
||||
return 4; // "null" for invalid numbers
|
||||
#endif
|
||||
}
|
||||
// Rough estimate: up to 17 significant digits + sign + decimal point + exponent
|
||||
char buf[32];
|
||||
@@ -356,28 +346,28 @@ inline layout_mode structure_analyzer::decide_layout(const element_metrics& metr
|
||||
size_t depth,
|
||||
size_t available_width) const {
|
||||
if (metrics.child_count == 0) {
|
||||
return layout_mode::single_line;
|
||||
return layout_mode::INLINE;
|
||||
}
|
||||
|
||||
// Check inline feasibility
|
||||
size_t indent_width = depth * current_opts_->indent_spaces;
|
||||
if (metrics.can_inline &&
|
||||
metrics.estimated_inline_len + indent_width <= available_width) {
|
||||
return layout_mode::single_line;
|
||||
return layout_mode::INLINE;
|
||||
}
|
||||
|
||||
// Check table mode
|
||||
if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
|
||||
return layout_mode::table;
|
||||
return layout_mode::TABLE;
|
||||
}
|
||||
|
||||
// Check compact multiline
|
||||
if (current_opts_->enable_compact_multiline &&
|
||||
metrics.complexity <= current_opts_->max_compact_array_complexity + 1) {
|
||||
return layout_mode::compact_multiline;
|
||||
return layout_mode::COMPACT_MULTILINE;
|
||||
}
|
||||
|
||||
return layout_mode::expanded;
|
||||
return layout_mode::EXPANDED;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -388,7 +378,7 @@ inline fractured_formatter::fractured_formatter(const fractured_json_options& op
|
||||
: options_(opts), column_widths_{} {}
|
||||
|
||||
simdjson_inline void fractured_formatter::print_newline() {
|
||||
if (current_layout_ == layout_mode::single_line) {
|
||||
if (current_layout_ == layout_mode::INLINE) {
|
||||
return; // No newlines in inline mode
|
||||
}
|
||||
one_char('\n');
|
||||
@@ -396,7 +386,7 @@ simdjson_inline void fractured_formatter::print_newline() {
|
||||
}
|
||||
|
||||
simdjson_inline void fractured_formatter::print_indents(size_t depth) {
|
||||
if (current_layout_ == layout_mode::single_line) {
|
||||
if (current_layout_ == layout_mode::INLINE) {
|
||||
return; // No indentation in inline mode
|
||||
}
|
||||
for (size_t i = 0; i < depth * options_.indent_spaces; i++) {
|
||||
@@ -543,16 +533,16 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
|
||||
const element_metrics& metrics,
|
||||
size_t depth) {
|
||||
switch (metrics.recommended_layout) {
|
||||
case layout_mode::single_line:
|
||||
case layout_mode::INLINE:
|
||||
format_array_inline(arr, metrics);
|
||||
break;
|
||||
case layout_mode::compact_multiline:
|
||||
case layout_mode::COMPACT_MULTILINE:
|
||||
format_array_compact_multiline(arr, metrics, depth);
|
||||
break;
|
||||
case layout_mode::table:
|
||||
case layout_mode::TABLE:
|
||||
format_array_as_table(arr, metrics, depth);
|
||||
break;
|
||||
case layout_mode::expanded:
|
||||
case layout_mode::EXPANDED:
|
||||
default:
|
||||
format_array_expanded(arr, metrics, depth);
|
||||
break;
|
||||
@@ -562,7 +552,7 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
|
||||
inline void fractured_string_builder::format_array_inline(const dom::array& arr,
|
||||
const element_metrics& metrics) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
|
||||
format_.start_array();
|
||||
|
||||
@@ -623,7 +613,7 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom::
|
||||
|
||||
// Format element inline
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
const element_metrics& child_metrics = (child_idx < metrics.children.size())
|
||||
? metrics.children[child_idx] : element_metrics{};
|
||||
format_element(elem, child_metrics, depth + 1);
|
||||
@@ -719,7 +709,7 @@ inline void fractured_string_builder::format_array_as_table(const dom::array& ar
|
||||
// Write value
|
||||
if (found) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
const element_metrics& value_metrics = (field_idx < row_metrics.children.size())
|
||||
? row_metrics.children[field_idx] : element_metrics{};
|
||||
format_element(value, value_metrics, depth + 1);
|
||||
@@ -788,7 +778,7 @@ inline void fractured_string_builder::format_array_expanded(const dom::array& ar
|
||||
inline void fractured_string_builder::format_object(const dom::object& obj,
|
||||
const element_metrics& metrics,
|
||||
size_t depth) {
|
||||
if (metrics.recommended_layout == layout_mode::single_line || metrics.can_inline) {
|
||||
if (metrics.recommended_layout == layout_mode::INLINE || metrics.can_inline) {
|
||||
format_object_inline(obj, metrics);
|
||||
} else {
|
||||
format_object_expanded(obj, metrics, depth);
|
||||
@@ -798,7 +788,7 @@ inline void fractured_string_builder::format_object(const dom::object& obj,
|
||||
inline void fractured_string_builder::format_object_inline(const dom::object& obj,
|
||||
const element_metrics& metrics) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
|
||||
format_.start_object();
|
||||
|
||||
@@ -960,14 +950,6 @@ 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;
|
||||
|
||||
@@ -36,11 +36,10 @@ inline bool parser::dump_raw_tape(std::ostream &os) const noexcept {
|
||||
}
|
||||
|
||||
inline simdjson_result<size_t> parser::read_file(std::string_view path) noexcept {
|
||||
const std::string path_copy(path);
|
||||
// Open the file
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe
|
||||
std::FILE *fp = std::fopen(path_copy.c_str(), "rb");
|
||||
std::FILE *fp = std::fopen(path.data(), "rb");
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
|
||||
if (fp == nullptr) {
|
||||
@@ -133,7 +132,6 @@ inline simdjson_result<element> parser::parse_into_document(document& provided_d
|
||||
buf += 3;
|
||||
len -= 3;
|
||||
}
|
||||
implementation->_number_as_string = _number_as_string;
|
||||
_error = implementation->parse(buf, len, provided_doc);
|
||||
|
||||
if (_error) { return _error; }
|
||||
@@ -170,7 +168,12 @@ simdjson_inline simdjson_result<element> parser::parse(const padded_string_view
|
||||
}
|
||||
|
||||
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
|
||||
return parse_many(buf, len, batch_size, stream_format::whitespace_delimited);
|
||||
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||
buf += 3;
|
||||
len -= 3;
|
||||
}
|
||||
return document_stream(*this, buf, len, batch_size);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept {
|
||||
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
|
||||
@@ -181,48 +184,6 @@ 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; }
|
||||
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||
buf += 3;
|
||||
len -= 3;
|
||||
}
|
||||
if (format == stream_format::comma_delimited_array) {
|
||||
// Strip leading JSON whitespace.
|
||||
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
|
||||
buf++; len--;
|
||||
}
|
||||
// Expect the opening '['.
|
||||
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
|
||||
buf++; len--;
|
||||
// Strip trailing JSON whitespace.
|
||||
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
|
||||
len--;
|
||||
}
|
||||
// Expect the closing ']'.
|
||||
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
|
||||
len--;
|
||||
// Fall through to comma_delimited over the array contents.
|
||||
format = stream_format::comma_delimited;
|
||||
}
|
||||
return document_stream(*this, buf, len, batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
|
||||
return parse_many(s.data(), s.length(), batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
|
||||
return parse_many(s.data(), s.length(), batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept {
|
||||
return parse_many(v.data(), v.length(), batch_size, format);
|
||||
}
|
||||
|
||||
simdjson_inline size_t parser::capacity() const noexcept {
|
||||
return implementation ? implementation->capacity() : 0;
|
||||
|
||||
@@ -490,39 +490,10 @@ 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;
|
||||
|
||||
/**
|
||||
* Parse a stream of JSON documents with explicit format specification.
|
||||
*
|
||||
* @param buf The concatenated JSON documents.
|
||||
* @param len The length of the buffer.
|
||||
* @param batch_size The batch size to use.
|
||||
* @param format The stream format.
|
||||
* @return A stream of documents, or an error.
|
||||
*/
|
||||
inline simdjson_result<document_stream> parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept;
|
||||
|
||||
/**
|
||||
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
|
||||
* and `max_depth` depth.
|
||||
@@ -639,13 +610,6 @@ public:
|
||||
inline bool dump_raw_tape(std::ostream &os) const noexcept;
|
||||
|
||||
|
||||
/**
|
||||
* When enabled, big integers (exceeding uint64 range) are stored as strings
|
||||
* in the tape instead of returning BIGINT_ERROR. Default: false.
|
||||
*/
|
||||
inline void number_as_string(bool enabled) noexcept { _number_as_string = enabled; }
|
||||
inline bool number_as_string() const noexcept { return _number_as_string; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* The maximum document length this parser will automatically support.
|
||||
@@ -654,9 +618,6 @@ private:
|
||||
*/
|
||||
size_t _max_capacity;
|
||||
|
||||
/** Whether to store big integers as strings instead of returning BIGINT_ERROR */
|
||||
bool _number_as_string{false};
|
||||
|
||||
/**
|
||||
* The loaded buffer (reused each time load() is called)
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "simdjson/dom/object-inl.h"
|
||||
#include "simdjson/internal/tape_ref-inl.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace simdjson {
|
||||
@@ -182,22 +181,6 @@ 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
|
||||
@@ -477,12 +460,6 @@ inline void string_builder<serializer>::append(simdjson::dom::element value) {
|
||||
case tape_type::STRING:
|
||||
format.string(iter.get_string_view());
|
||||
break;
|
||||
case tape_type::BIGINT: {
|
||||
// Big integer stored as string — output raw digits (no quotes)
|
||||
auto sv = iter.get_string_view();
|
||||
format.chars(sv.data(), sv.data() + sv.size());
|
||||
break;
|
||||
}
|
||||
case tape_type::INT64:
|
||||
format.number(iter.next_tape_value<int64_t>());
|
||||
iter.json_index++; // numbers take up 2 spots, so we need to increment
|
||||
|
||||
@@ -41,19 +41,6 @@ simdjson_inline int leading_zeroes(uint64_t input_num) {
|
||||
#endif// _MSC_VER
|
||||
}
|
||||
|
||||
simdjson_inline int trailing_zeroes(uint64_t input_num) {
|
||||
#ifdef _MSC_VER
|
||||
unsigned long trailing_zero = 0;
|
||||
// Search the mask data from least significant bit (LSB)
|
||||
// to most significant bit (MSB) for a set bit (1).
|
||||
if (_BitScanForward64(&trailing_zero, input_num))
|
||||
return (int)trailing_zero;
|
||||
else return 64;
|
||||
#else
|
||||
return __builtin_ctzll(input_num);
|
||||
#endif// _MSC_VER
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
} // namespace fallback
|
||||
} // namespace simdjson
|
||||
|
||||
@@ -22,9 +22,6 @@ 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.
|
||||
@@ -36,28 +33,6 @@ 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;
|
||||
@@ -94,74 +69,9 @@ simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) {
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
#if SIMDJSON_ENABLE_NAN_INF
|
||||
// "nan" is 3 bytes; we check characters and then verify the next
|
||||
// character is structural or whitespace. We accept both "nan" and "NaN".
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_nan_atom(const uint8_t *src) {
|
||||
return (str3ncmp_case_insensitive(src, "nan")
|
||||
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
|
||||
}
|
||||
|
||||
// checks that the next four characters of a string are 'nan"', where the 'nan'
|
||||
// is checked in a case-insensitive way.
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_nan_in_string(const uint8_t *src) {
|
||||
return (str3ncmp_case_insensitive(src, "nan") | (src[3] ^ '"')) == 0;
|
||||
}
|
||||
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_nan_atom(const uint8_t *src, size_t len) {
|
||||
if (len > 3) { return is_valid_nan_atom(src); }
|
||||
if (len == 3) { return str3ncmp_case_insensitive(src, "nan") == 0; }
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function will accept any case-insensitive 3-character spelling of
|
||||
// infinity: 'inf', 'INF', and 'Inf' are all accepted.
|
||||
//
|
||||
// Any capitalization of 'infinity' is also accepted.
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_inf_atom(const uint8_t *src) {
|
||||
bool is_short_inf = (str3ncmp_case_insensitive(src, "inf")
|
||||
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
|
||||
if(is_short_inf) return true;
|
||||
|
||||
// Check for 'infinity' (any capitalization)
|
||||
return (str8ncmp_case_insensitive(src, "infinity") | jsoncharutils::is_not_structural_or_whitespace(src[8])) == 0;
|
||||
}
|
||||
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_inf_in_string(const uint8_t *src) {
|
||||
bool is_short_inf = (str3ncmp_case_insensitive(src, "inf") | (src[3] ^ '"')) == 0;
|
||||
if(is_short_inf) return true;
|
||||
|
||||
return (str8ncmp_case_insensitive(src, "infinity") | (src[8] ^ '"')) == 0;
|
||||
}
|
||||
|
||||
|
||||
// This function will accept any case-insensitive 3-character spelling of
|
||||
// infinity: 'inf', 'INF', and 'Inf' are all accepted.
|
||||
//
|
||||
// Any capitalization of 'infinity' is also accepted.
|
||||
simdjson_warn_unused
|
||||
simdjson_inline bool is_valid_inf_atom(const uint8_t *src, size_t len) {
|
||||
if (len > 8) { return is_valid_inf_atom(src); }
|
||||
if (len == 8 && str8ncmp_case_insensitive(src, "infinity") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (len > 3) {
|
||||
return (str3ncmp_case_insensitive(src, "inf")
|
||||
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
|
||||
}
|
||||
if (len == 3) { return str3ncmp_case_insensitive(src, "inf") == 0; }
|
||||
return false;
|
||||
}
|
||||
#endif // SIMDJSON_ENABLE_NAN_INF
|
||||
|
||||
} // namespace atomparsing
|
||||
} // unnamed namespace
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ATOMPARSING_H
|
||||
#endif // SIMDJSON_GENERIC_ATOMPARSING_H
|
||||
@@ -10,6 +10,5 @@
|
||||
// Otherwise, amalgamation will fail.
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/dom/fractured_json.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
|
||||
@@ -4,7 +4,6 @@
|
||||
#define SIMDJSON_GENERIC_STRING_BUILDER_H
|
||||
#include "simdjson/generic/builder/json_string_builder.h"
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
@@ -22,113 +21,23 @@ 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>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
requires(concepts::container_but_not_string<T> && !require_custom_serialization<T>)
|
||||
constexpr void atom(string_builder &b, const T &t) {
|
||||
auto it = t.begin();
|
||||
auto end = t.end();
|
||||
if (it == end) {
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "[]", 2);
|
||||
w.pos += 2;
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '[';
|
||||
atom(w, *it);
|
||||
b.append('[');
|
||||
atom(b, *it);
|
||||
++it;
|
||||
for (; it != end; ++it) {
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
atom(w, *it);
|
||||
b.append(',');
|
||||
atom(b, *it);
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ']';
|
||||
b.append(']');
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -136,100 +45,37 @@ template <class T>
|
||||
std::is_same_v<T, std::string_view> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char>)
|
||||
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++] = '"';
|
||||
constexpr void atom(string_builder &b, const T &t) {
|
||||
b.escape_and_append_with_quotes(t);
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &m) {
|
||||
constexpr void atom(string_builder &b, const T &m) {
|
||||
if (m.empty()) {
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "{}", 2);
|
||||
w.pos += 2;
|
||||
b.append_raw("{}");
|
||||
return;
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
b.append('{');
|
||||
bool first = true;
|
||||
for (const auto& [key, value] : m) {
|
||||
if (!first) {
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
b.append(',');
|
||||
}
|
||||
first = false;
|
||||
// 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);
|
||||
// Keys must be convertible to string_view per the concept
|
||||
b.escape_and_append_with_quotes(key);
|
||||
b.append(':');
|
||||
atom(b, value);
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
|
||||
template<typename number_type,
|
||||
typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type>
|
||||
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);
|
||||
}
|
||||
constexpr void atom(string_builder &b, const number_type t) {
|
||||
b.append(t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -242,86 +88,69 @@ 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>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
// Per-field block: ensure key+value worst case, then write key + value
|
||||
// through the writer's local pos. For arithmetic fields, the integer
|
||||
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
|
||||
// never round-trips through memory.
|
||||
bool first = true;
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
constexpr void atom(string_builder &b, const T &t) {
|
||||
int i = 0;
|
||||
b.append('{');
|
||||
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (std::meta::annotations_of_with_type(dm, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr const char* key_name = simdjson::get_json_key_name<dm>();
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) return;
|
||||
if (first) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
}
|
||||
first = false;
|
||||
atom(w, t.[:dm:]);
|
||||
}
|
||||
if (i != 0)
|
||||
b.append(',');
|
||||
#if ABLATION_NO_CONSTEVAL
|
||||
b.escape_and_append_with_quotes(std::meta::identifier_of(dm));
|
||||
#else
|
||||
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
|
||||
b.append_raw(key);
|
||||
#endif
|
||||
b.append(':');
|
||||
atom(b, t.[:dm:]);
|
||||
i++;
|
||||
};
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
// Support for optional types (std::optional, etc.)
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &opt) {
|
||||
constexpr void atom(string_builder &b, const T &opt) {
|
||||
if (opt) {
|
||||
atom(w, opt.value());
|
||||
atom(b, opt.value());
|
||||
} else {
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "null", 4);
|
||||
w.pos += 4;
|
||||
b.append_raw("null");
|
||||
}
|
||||
}
|
||||
|
||||
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &ptr) {
|
||||
constexpr void atom(string_builder &b, const T &ptr) {
|
||||
if (ptr) {
|
||||
atom(w, *ptr);
|
||||
atom(b, *ptr);
|
||||
} else {
|
||||
if (!w.ensure(4)) return;
|
||||
std::memcpy(w.ptr + w.pos, "null", 4);
|
||||
w.pos += 4;
|
||||
b.append_raw("null");
|
||||
}
|
||||
}
|
||||
|
||||
// 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>)
|
||||
simdjson_really_inline void atom(writer &w, const T &e) {
|
||||
void atom(string_builder &b, const T &e) {
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
|
||||
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:]) {
|
||||
if (!w.ensure(enum_str_len)) return;
|
||||
std::memcpy(w.ptr + w.pos, enum_str, enum_str_len);
|
||||
w.pos += enum_str_len;
|
||||
#if ABLATION_NO_CONSTEVAL
|
||||
b.escape_and_append_with_quotes(std::meta::identifier_of(enum_val));
|
||||
#else
|
||||
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
|
||||
b.append_raw(enum_str);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Fallback to integer if enum value not found
|
||||
atom(w, static_cast<std::underlying_type_t<T>>(e));
|
||||
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||
#else
|
||||
// Fallback: serialize as integer if reflection not available
|
||||
atom(w, static_cast<std::underlying_type_t<T>>(e));
|
||||
atom(b, static_cast<std::underlying_type_t<T>>(e));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -331,37 +160,28 @@ 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>)
|
||||
simdjson_really_inline constexpr void atom(writer &w, const T &container) {
|
||||
constexpr void atom(string_builder &b, const T &container) {
|
||||
if (container.empty()) {
|
||||
if (!w.ensure(2)) return;
|
||||
std::memcpy(w.ptr + w.pos, "[]", 2);
|
||||
w.pos += 2;
|
||||
b.append_raw("[]");
|
||||
return;
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '[';
|
||||
b.append('[');
|
||||
bool first = true;
|
||||
for (const auto& item : container) {
|
||||
if (!first) {
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ',';
|
||||
b.append(',');
|
||||
}
|
||||
first = false;
|
||||
atom(w, item);
|
||||
atom(b, item);
|
||||
}
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = ']';
|
||||
b.append(']');
|
||||
}
|
||||
|
||||
// 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.
|
||||
// append functions that delegate to atom functions for primitive types
|
||||
template <class T>
|
||||
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -369,26 +189,20 @@ template <class T>
|
||||
std::is_same_v<T, std::string_view> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::optional_type T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::smart_pointer T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::appendable_containers T>
|
||||
@@ -396,18 +210,14 @@ 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>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
template <concepts::string_view_keyed_map T>
|
||||
requires(!require_custom_serialization<T>)
|
||||
simdjson_inline void append(string_builder &b, const T &t) {
|
||||
writer w(b);
|
||||
atom(w, t);
|
||||
w.sync();
|
||||
void append(string_builder &b, const T &t) {
|
||||
atom(b, t);
|
||||
}
|
||||
|
||||
// works for struct
|
||||
@@ -421,19 +231,43 @@ 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>)
|
||||
simdjson_inline void append(string_builder &b, const Z &z) {
|
||||
writer w(b);
|
||||
atom(w, z);
|
||||
w.sync();
|
||||
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(',');
|
||||
#if ABLATION_NO_CONSTEVAL
|
||||
b.escape_and_append_with_quotes(std::meta::identifier_of(dm));
|
||||
#else
|
||||
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
|
||||
b.append_raw(key);
|
||||
#endif
|
||||
b.append(':');
|
||||
atom(b, z.[:dm:]);
|
||||
i++;
|
||||
};
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
// works for container that have begin() and end() iterators
|
||||
template <class Z>
|
||||
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
|
||||
simdjson_inline void append(string_builder &b, const Z &z) {
|
||||
writer w(b);
|
||||
atom(w, z);
|
||||
w.sync();
|
||||
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(']');
|
||||
}
|
||||
|
||||
template <class Z>
|
||||
@@ -472,41 +306,44 @@ 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) {
|
||||
writer w(b);
|
||||
if (!w.ensure(1)) { w.sync(); return; }
|
||||
w.ptr[w.pos++] = '{';
|
||||
// Helper to check if a field name matches any of the requested fields
|
||||
auto should_extract = [](std::string_view field_name) constexpr -> bool {
|
||||
return ((FieldNames.view() == field_name) || ...);
|
||||
};
|
||||
|
||||
b.append('{');
|
||||
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()));
|
||||
template for (constexpr auto mem : members) {
|
||||
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_public(mem)) {
|
||||
static constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
||||
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
||||
|
||||
// Only serialize this field if it's in our list of requested fields
|
||||
if constexpr (((FieldNames.view() == key) || ...)) {
|
||||
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;
|
||||
if constexpr (should_extract(key)) {
|
||||
if (!first) {
|
||||
b.append(',');
|
||||
}
|
||||
first = false;
|
||||
atom(w, obj.[:mem:]);
|
||||
|
||||
// Serialize the key
|
||||
#if ABLATION_NO_CONSTEVAL
|
||||
b.escape_and_append_with_quotes(std::meta::identifier_of(mem));
|
||||
#else
|
||||
constexpr auto quoted_key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)));
|
||||
b.append_raw(quoted_key);
|
||||
#endif
|
||||
b.append(':');
|
||||
|
||||
// Serialize the value
|
||||
atom(b, obj.[:mem:]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!w.ensure(1)) { w.sync(); return; }
|
||||
w.ptr[w.pos++] = '}';
|
||||
w.sync();
|
||||
b.append('}');
|
||||
}
|
||||
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
||||
|
||||
@@ -29,22 +27,6 @@
|
||||
#define SIMDJSON_EXPERIMENTAL_HAS_NEON 1
|
||||
#endif
|
||||
#endif
|
||||
#if defined(__loongarch_sx)
|
||||
#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX
|
||||
#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1
|
||||
#endif
|
||||
#endif
|
||||
#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \
|
||||
defined(__riscv_vector)
|
||||
#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV
|
||||
#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1
|
||||
#endif
|
||||
#endif
|
||||
#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__)
|
||||
#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64
|
||||
#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1
|
||||
#endif
|
||||
#endif
|
||||
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
|
||||
#include <arm_neon.h>
|
||||
#ifdef _MSC_VER
|
||||
@@ -57,22 +39,6 @@
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
#if SIMDJSON_EXPERIMENTAL_HAS_LSX
|
||||
#include <lsxintrin.h>
|
||||
#endif
|
||||
#if SIMDJSON_EXPERIMENTAL_HAS_RVV
|
||||
#include <riscv_vector.h>
|
||||
#endif
|
||||
#if SIMDJSON_EXPERIMENTAL_HAS_PPC64
|
||||
#include <altivec.h>
|
||||
#ifdef bool
|
||||
#undef bool
|
||||
#endif
|
||||
#ifdef vector
|
||||
#undef vector
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
@@ -173,64 +139,6 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
}
|
||||
return _mm_movemask_epi8(running) != 0;
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
if (view.size() < 16) {
|
||||
return simple_needs_escaping(view);
|
||||
}
|
||||
size_t i = 0;
|
||||
__m128i running = __lsx_vreplgr2vr_b(0);
|
||||
__m128i v34 = __lsx_vreplgr2vr_b(34);
|
||||
__m128i v92 = __lsx_vreplgr2vr_b(92);
|
||||
__m128i v32 = __lsx_vreplgr2vr_b(32);
|
||||
|
||||
for (; i + 15 < view.size(); i += 16) {
|
||||
__m128i word =
|
||||
__lsx_vld(reinterpret_cast<const char *>(view.data() + i), 0);
|
||||
__m128i chunk = __lsx_vseq_b(word, v34);
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
|
||||
running = __lsx_vor_v(running, chunk);
|
||||
}
|
||||
if (i < view.size()) {
|
||||
__m128i word = __lsx_vld(
|
||||
reinterpret_cast<const char *>(view.data() + view.length() - 16), 0);
|
||||
__m128i chunk = __lsx_vseq_b(word, v34);
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
|
||||
running = __lsx_vor_v(running, chunk);
|
||||
}
|
||||
return !__lsx_bz_v(running);
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
if (view.size() < 16) {
|
||||
return simple_needs_escaping(view);
|
||||
}
|
||||
size_t i = 0;
|
||||
__vector unsigned char running = vec_splats((unsigned char)0);
|
||||
__vector unsigned char v34 = vec_splats((unsigned char)34);
|
||||
__vector unsigned char v92 = vec_splats((unsigned char)92);
|
||||
__vector unsigned char v32 = vec_splats((unsigned char)32);
|
||||
|
||||
for (; i + 15 < view.size(); i += 16) {
|
||||
__vector unsigned char word =
|
||||
vec_vsx_ld(0, reinterpret_cast<const unsigned char *>(view.data() + i));
|
||||
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34));
|
||||
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92));
|
||||
running = vec_or(running,
|
||||
(__vector unsigned char)vec_cmplt(word, v32));
|
||||
}
|
||||
if (i < view.size()) {
|
||||
__vector unsigned char word = vec_vsx_ld(
|
||||
0, reinterpret_cast<const unsigned char *>(view.data() + view.length() - 16));
|
||||
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34));
|
||||
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92));
|
||||
running = vec_or(running,
|
||||
(__vector unsigned char)vec_cmplt(word, v32));
|
||||
}
|
||||
return !vec_all_eq(running, vec_splats((unsigned char)0));
|
||||
}
|
||||
#else
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
return simple_needs_escaping(view);
|
||||
@@ -274,12 +182,28 @@ find_next_json_quotable_character(const std::string_view view,
|
||||
needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92));
|
||||
needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32));
|
||||
|
||||
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4);
|
||||
const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
|
||||
if(mask != 0) {
|
||||
if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) {
|
||||
// Found quotable character - extract exact byte position using ctz
|
||||
uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape);
|
||||
uint64_t lo = vgetq_lane_u64(as64, 0);
|
||||
uint64_t hi = vgetq_lane_u64(as64, 1);
|
||||
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
||||
auto trailing_zero = trailing_zeroes(mask);
|
||||
return offset + (trailing_zero >> 2);
|
||||
#ifdef _MSC_VER
|
||||
unsigned long trailing_zero = 0;
|
||||
if (lo != 0) {
|
||||
_BitScanForward64(&trailing_zero, lo);
|
||||
return offset + trailing_zero / 8;
|
||||
} else {
|
||||
_BitScanForward64(&trailing_zero, hi);
|
||||
return offset + 8 + trailing_zero / 8;
|
||||
}
|
||||
#else
|
||||
if (lo != 0) {
|
||||
return offset + __builtin_ctzll(lo) / 8;
|
||||
} else {
|
||||
return offset + 8 + __builtin_ctzll(hi) / 8;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ptr += 16;
|
||||
remaining -= 16;
|
||||
@@ -317,128 +241,13 @@ find_next_json_quotable_character(const std::string_view view,
|
||||
if (mask != 0) {
|
||||
// Found quotable character - use trailing zero count to find position
|
||||
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
||||
return offset + trailing_zeroes(mask);
|
||||
}
|
||||
ptr += 16;
|
||||
remaining -= 16;
|
||||
}
|
||||
|
||||
// Scalar fallback for remaining bytes
|
||||
size_t current = len - remaining;
|
||||
return find_next_json_quotable_character_scalar(view, current);
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
|
||||
simdjson_inline size_t
|
||||
find_next_json_quotable_character(const std::string_view view,
|
||||
size_t location) noexcept {
|
||||
const size_t len = view.size();
|
||||
const uint8_t *ptr =
|
||||
reinterpret_cast<const uint8_t *>(view.data()) + location;
|
||||
size_t remaining = len - location;
|
||||
|
||||
//SIMD constants for characters requiring escape
|
||||
__m128i v34 = __lsx_vreplgr2vr_b(34); // '"'
|
||||
__m128i v92 = __lsx_vreplgr2vr_b(92); // '\\'
|
||||
__m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold
|
||||
|
||||
while (remaining >= 16){
|
||||
__m128i word = __lsx_vld(ptr, 0);
|
||||
|
||||
//Check for the quotable characters: '"', '\\', or control char (<32)
|
||||
__m128i needs_escape = __lsx_vseq_b(word, v34);
|
||||
needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92));
|
||||
needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32));
|
||||
|
||||
if (!__lsx_bz_v(needs_escape)){
|
||||
|
||||
//Found quotable character - extract exact byte position
|
||||
uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0);
|
||||
uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1);
|
||||
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
||||
if ( lo != 0) {
|
||||
return offset + trailing_zeroes(lo) / 8;
|
||||
} else {
|
||||
return offset + 8 + trailing_zeroes(hi) / 8;
|
||||
}
|
||||
}
|
||||
ptr += 16;
|
||||
remaining -= 16;
|
||||
}
|
||||
size_t current = len - remaining;
|
||||
return find_next_json_quotable_character_scalar(view, current);
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_RVV
|
||||
simdjson_inline size_t
|
||||
find_next_json_quotable_character(const std::string_view view,
|
||||
size_t location) noexcept {
|
||||
const size_t len = view.size();
|
||||
const uint8_t *ptr =
|
||||
reinterpret_cast<const uint8_t *>(view.data()) + location;
|
||||
size_t remaining = len - location;
|
||||
|
||||
while (remaining > 0) {
|
||||
size_t vl = __riscv_vsetvl_e8m1(remaining);
|
||||
vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl);
|
||||
|
||||
// Check for quotable characters: '"', '\\', or control chars (< 32)
|
||||
vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl);
|
||||
needs_escape = __riscv_vmor(needs_escape,
|
||||
__riscv_vmseq(word, (uint8_t)92, vl), vl);
|
||||
needs_escape = __riscv_vmor(needs_escape,
|
||||
__riscv_vmsltu(word, (uint8_t)32, vl), vl);
|
||||
|
||||
long first = __riscv_vfirst(needs_escape, vl);
|
||||
if (first >= 0) {
|
||||
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
||||
return offset + first;
|
||||
}
|
||||
ptr += vl;
|
||||
remaining -= vl;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
|
||||
simdjson_inline size_t
|
||||
find_next_json_quotable_character(const std::string_view view,
|
||||
size_t location) noexcept {
|
||||
const size_t len = view.size();
|
||||
const uint8_t *ptr =
|
||||
reinterpret_cast<const uint8_t *>(view.data()) + location;
|
||||
size_t remaining = len - location;
|
||||
|
||||
// SIMD constants for characters requiring escape
|
||||
__vector unsigned char v34 = vec_splats((unsigned char)34); // '"'
|
||||
__vector unsigned char v92 = vec_splats((unsigned char)92); // '\\'
|
||||
__vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold
|
||||
|
||||
// Bitmask for vec_vbpermq to extract one bit per byte
|
||||
const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50,
|
||||
0x48, 0x40, 0x38, 0x30, 0x28, 0x20,
|
||||
0x18, 0x10, 0x08, 0x00};
|
||||
|
||||
while (remaining >= 16) {
|
||||
__vector unsigned char word =
|
||||
vec_vsx_ld(0, reinterpret_cast<const unsigned char *>(ptr));
|
||||
|
||||
// Check for quotable characters: '"', '\\', or control chars (< 32)
|
||||
__vector unsigned char needs_escape =
|
||||
(__vector unsigned char)vec_cmpeq(word, v34);
|
||||
needs_escape = vec_or(needs_escape,
|
||||
(__vector unsigned char)vec_cmpeq(word, v92));
|
||||
needs_escape = vec_or(needs_escape,
|
||||
(__vector unsigned char)vec_cmplt(word, v32));
|
||||
|
||||
__vector unsigned long long result =
|
||||
(__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask);
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
unsigned int mask = static_cast<unsigned int>(result[1]);
|
||||
#ifdef _MSC_VER
|
||||
unsigned long trailing_zero = 0;
|
||||
_BitScanForward(&trailing_zero, mask);
|
||||
return offset + trailing_zero;
|
||||
#else
|
||||
unsigned int mask = static_cast<unsigned int>(result[0]);
|
||||
#endif
|
||||
if (mask != 0) {
|
||||
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
||||
return offset + __builtin_ctz(mask);
|
||||
#endif
|
||||
}
|
||||
ptr += 16;
|
||||
remaining -= 16;
|
||||
@@ -486,9 +295,22 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o
|
||||
// written. Uses SIMD position finding to locate quotable characters efficiently.
|
||||
inline size_t write_string_escaped(const std::string_view input, char *out) {
|
||||
size_t mysize = input.size();
|
||||
#if ABLATION_NO_SIMD_ESCAPING
|
||||
auto naive_find_next_json_quotable_character = [](std::string_view input, size_t start) -> size_t {
|
||||
for (size_t i = start; i < input.size(); ++i) {
|
||||
auto c = static_cast<unsigned char>(input[i]);
|
||||
if (c == '"' || c == '\\' || c < 32) return i;
|
||||
}
|
||||
return input.size();
|
||||
};
|
||||
#endif
|
||||
|
||||
// Use SIMD position finder directly - it returns mysize if no escape needed
|
||||
#if ABLATION_NO_SIMD_ESCAPING
|
||||
size_t location = naive_find_next_json_quotable_character(input, 0);
|
||||
#else
|
||||
size_t location = find_next_json_quotable_character(input, 0);
|
||||
#endif
|
||||
if (location == mysize) {
|
||||
// Fast path: no escaping needed
|
||||
memcpy(out, input.data(), input.size());
|
||||
@@ -501,7 +323,11 @@ inline size_t write_string_escaped(const std::string_view input, char *out) {
|
||||
escape_json_char(input[location], out);
|
||||
location += 1;
|
||||
while (location < mysize) {
|
||||
#if ABLATION_NO_SIMD_ESCAPING
|
||||
size_t newlocation = naive_find_next_json_quotable_character(input, location);
|
||||
#else
|
||||
size_t newlocation = find_next_json_quotable_character(input, location);
|
||||
#endif
|
||||
memcpy(out, input.data() + location, newlocation - location);
|
||||
out += newlocation - location;
|
||||
location = newlocation;
|
||||
@@ -523,6 +349,15 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
|
||||
// We use the convention that when is_valid is false, then the capacity and
|
||||
// the position are 0.
|
||||
// Most of the time, this function will return true.
|
||||
#if ABLATION_NO_BRANCH_HINTS
|
||||
if (upcoming_bytes <= capacity - position) {
|
||||
return true;
|
||||
}
|
||||
// check for overflow, most of the time there is no overflow
|
||||
if (position + upcoming_bytes < position) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (simdjson_likely(upcoming_bytes <= capacity - position)) {
|
||||
return true;
|
||||
}
|
||||
@@ -530,13 +365,14 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
|
||||
if (simdjson_unlikely(position + upcoming_bytes < position)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// We will rarely get here.
|
||||
grow_buffer((std::max)(capacity * 2, position + upcoming_bytes));
|
||||
// If the buffer allocation failed, we set is_valid to false.
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
inline void string_builder::grow_buffer(size_t desired_capacity) {
|
||||
simdjson_inline void string_builder::grow_buffer(size_t desired_capacity) {
|
||||
if (!is_valid) {
|
||||
return;
|
||||
}
|
||||
@@ -592,6 +428,62 @@ 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,
|
||||
@@ -611,78 +503,6 @@ 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>
|
||||
@@ -710,59 +530,87 @@ 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;
|
||||
char* end = internal::write_uint_jeaiii(
|
||||
buffer.get() + position,
|
||||
static_cast<uint64_t>(static_cast<unsigned_type>(v)));
|
||||
position = end - buffer.get();
|
||||
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;
|
||||
}
|
||||
}
|
||||
else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) {
|
||||
// 19 digits (max abs value of int64_t) + optional minus sign.
|
||||
// Same 4-digit batching as unsigned path for signed integers
|
||||
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;
|
||||
// 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.
|
||||
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.
|
||||
buffer.get()[position] = '-';
|
||||
position += negative;
|
||||
char* end = internal::write_uint_jeaiii(
|
||||
buffer.get() + position, static_cast<uint64_t>(pv));
|
||||
position = end - buffer.get();
|
||||
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;
|
||||
}
|
||||
}
|
||||
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));
|
||||
@@ -774,11 +622,6 @@ 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);
|
||||
}
|
||||
@@ -787,11 +630,6 @@ 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);
|
||||
@@ -823,9 +661,7 @@ simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
|
||||
#endif
|
||||
|
||||
simdjson_inline void string_builder::append_raw(const char *c) noexcept {
|
||||
// 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);
|
||||
size_t len = std::strlen(c);
|
||||
append_raw(c, len);
|
||||
}
|
||||
|
||||
@@ -844,14 +680,6 @@ 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>
|
||||
@@ -881,7 +709,7 @@ simdjson_inline void string_builder::append(const T &value) {
|
||||
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// Support for range-based appending (std::ranges::view, etc.)
|
||||
template <std::ranges::range R>
|
||||
requires(!std::is_convertible<R, std::string_view>::value && !concepts::optional_type<R> && !require_custom_serialization<R>)
|
||||
requires(!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>)
|
||||
simdjson_inline void string_builder::append(const R &range) noexcept {
|
||||
auto it = std::ranges::begin(range);
|
||||
auto end = std::ranges::end(range);
|
||||
|
||||
@@ -171,7 +171,7 @@ public:
|
||||
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// Support for range-based appending (std::ranges::view, etc.)
|
||||
template <std::ranges::range R>
|
||||
requires (!std::is_convertible<R, std::string_view>::value && !concepts::optional_type<R> && !require_custom_serialization<R>)
|
||||
requires (!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>)
|
||||
simdjson_inline void append(const R &range) noexcept;
|
||||
#endif
|
||||
/**
|
||||
@@ -185,15 +185,6 @@ 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.
|
||||
@@ -245,26 +236,6 @@ 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.
|
||||
@@ -278,7 +249,7 @@ private:
|
||||
* If the allocation fails, is_valid is set to false. We expect
|
||||
* that this function would not be repeatedly called.
|
||||
*/
|
||||
inline void grow_buffer(size_t desired_capacity);
|
||||
simdjson_inline void grow_buffer(size_t desired_capacity);
|
||||
|
||||
/**
|
||||
* We use this helper function to make sure that is_valid is kept consistent.
|
||||
|
||||
@@ -63,11 +63,7 @@ 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 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;
|
||||
size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7;
|
||||
structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] );
|
||||
if (!structural_indexes) { _capacity = 0; return MEMALLOC; }
|
||||
structural_indexes[0] = 0;
|
||||
@@ -78,7 +74,6 @@ inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(s
|
||||
}
|
||||
|
||||
inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept {
|
||||
if(max_depth > SIMDJSON_MAX_DEPTH) { return CAPACITY; }
|
||||
// Stage 2 stacks
|
||||
open_containers.reset(new (std::nothrow) open_container[max_depth]);
|
||||
is_array.reset(new (std::nothrow) bool[max_depth]);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#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
|
||||
|
||||
@@ -300,24 +299,6 @@ 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
|
||||
@@ -497,7 +478,7 @@ simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t d
|
||||
} // unnamed namespace
|
||||
|
||||
/** @private */
|
||||
inline error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
|
||||
static error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) {
|
||||
if (parse_float_fallback(src, answer)) {
|
||||
return SUCCESS;
|
||||
}
|
||||
@@ -619,21 +600,7 @@ 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)) {
|
||||
|
||||
#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);
|
||||
}
|
||||
if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); }
|
||||
|
||||
//
|
||||
// Handle floats if there is a . or e (or both)
|
||||
@@ -1064,17 +1031,7 @@ 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 ) {
|
||||
|
||||
#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 ( p == src ) { return INCORRECT_TYPE; }
|
||||
if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; }
|
||||
|
||||
//
|
||||
@@ -1292,22 +1249,7 @@ 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 ) {
|
||||
#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 ( p == src ) { return INCORRECT_TYPE; }
|
||||
if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; }
|
||||
|
||||
//
|
||||
|
||||
@@ -20,10 +20,8 @@
|
||||
#include "simdjson/generic/ondemand/document.h"
|
||||
#include "simdjson/generic/ondemand/document_stream.h"
|
||||
#include "simdjson/generic/ondemand/field.h"
|
||||
#include "simdjson/generic/ondemand/key_selector.h"
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||
#include "simdjson/generic/ondemand/ranges.h"
|
||||
#include "simdjson/generic/ondemand/serialization.h"
|
||||
|
||||
// Deserialization for standard types
|
||||
@@ -41,7 +39,6 @@
|
||||
#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"
|
||||
|
||||
@@ -135,9 +135,21 @@ 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;
|
||||
SIMDJSON_TRY(internal::parse_json_pointer_array_index(json_pointer, array_index, 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"
|
||||
// Get the child
|
||||
auto child = at(array_index);
|
||||
// If there is an error, it ends here
|
||||
@@ -158,34 +170,46 @@ inline simdjson_result<value> array::at_path(std::string_view json_path) noexcep
|
||||
return at_pointer(json_pointer);
|
||||
}
|
||||
|
||||
#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 {
|
||||
inline simdjson_result<std::vector<value>> array::at_path_with_wildcard(std::string_view json_path) noexcept {
|
||||
std::vector<value> result;
|
||||
|
||||
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) {
|
||||
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; }
|
||||
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()));
|
||||
}
|
||||
}
|
||||
return SUCCESS;
|
||||
} else {
|
||||
return result;
|
||||
}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;
|
||||
}
|
||||
@@ -193,13 +217,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa
|
||||
}
|
||||
|
||||
auto element = at(idx);
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,15 +289,9 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdj
|
||||
if (error()) { return error(); }
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
#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 {
|
||||
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 (error()) { return error(); }
|
||||
return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return first.at_path_with_wildcard(json_path);
|
||||
}
|
||||
simdjson_inline simdjson_result<std::string_view> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::array>::raw_json() noexcept {
|
||||
if (error()) { return error(); }
|
||||
|
||||
@@ -119,21 +119,13 @@ public:
|
||||
inline simdjson_result<value> at_path(std::string_view json_path) noexcept;
|
||||
|
||||
/**
|
||||
* Call the provided callback for each value matching the given JSONPath
|
||||
* expression with wildcard support.
|
||||
* Get all values matching the given JSONPath expression with wildcard support.
|
||||
* Supports wildcard patterns like "[*]" to match all array elements.
|
||||
*
|
||||
* @param json_path JSONPath expression with wildcards
|
||||
* @param callback Function called for each matching value
|
||||
* @return error_code indicating success or failure
|
||||
* @return Vector of values matching the wildcard pattern
|
||||
*/
|
||||
#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;
|
||||
inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
|
||||
/**
|
||||
* Consumes the array and returns a string_view instance corresponding to the
|
||||
@@ -257,13 +249,7 @@ 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;
|
||||
#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::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> raw_json() noexcept;
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
// TODO: move this code into object-inl.h
|
||||
|
||||
@@ -40,13 +40,6 @@ 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
|
||||
|
||||
@@ -10,13 +10,9 @@
|
||||
// Otherwise, amalgamation will fail.
|
||||
#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY
|
||||
#include "simdjson/implementation.h"
|
||||
#include "simdjson/base.h"
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/constevalutil.h"
|
||||
#include "simdjson/padded_string.h"
|
||||
#include "simdjson/padded_string_view.h"
|
||||
#include "simdjson/internal/dom_parser_implementation.h"
|
||||
#include "simdjson/jsonpathutil.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H
|
||||
@@ -16,8 +16,6 @@
|
||||
#include "simdjson/generic/ondemand/deserialize.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
@@ -133,18 +131,6 @@ simdjson_inline simdjson_result<int64_t> document::get_int64() noexcept {
|
||||
simdjson_inline simdjson_result<int64_t> document::get_int64_in_string() noexcept {
|
||||
return get_root_value_iterator().get_root_int64_in_string(true);
|
||||
}
|
||||
simdjson_inline simdjson_result<uint32_t> document::get_uint32() noexcept {
|
||||
uint64_t result;
|
||||
SIMDJSON_TRY(get_uint64().get(result));
|
||||
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
|
||||
return static_cast<uint32_t>(result);
|
||||
}
|
||||
simdjson_inline simdjson_result<int32_t> document::get_int32() noexcept {
|
||||
int64_t result;
|
||||
SIMDJSON_TRY(get_int64().get(result));
|
||||
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
|
||||
return static_cast<int32_t>(result);
|
||||
}
|
||||
simdjson_inline simdjson_result<double> document::get_double() noexcept {
|
||||
return get_root_value_iterator().get_root_double(true);
|
||||
}
|
||||
@@ -178,8 +164,6 @@ template<> simdjson_inline simdjson_result<std::string_view> document::get() & n
|
||||
template<> simdjson_inline simdjson_result<double> document::get() & noexcept { return get_double(); }
|
||||
template<> simdjson_inline simdjson_result<uint64_t> document::get() & noexcept { return get_uint64(); }
|
||||
template<> simdjson_inline simdjson_result<int64_t> document::get() & noexcept { return get_int64(); }
|
||||
template<> simdjson_inline simdjson_result<uint32_t> document::get() & noexcept { return get_uint32(); }
|
||||
template<> simdjson_inline simdjson_result<int32_t> document::get() & noexcept { return get_int32(); }
|
||||
template<> simdjson_inline simdjson_result<bool> document::get() & noexcept { return get_bool(); }
|
||||
template<> simdjson_inline simdjson_result<value> document::get() & noexcept { return get_value(); }
|
||||
|
||||
@@ -190,8 +174,6 @@ template<> simdjson_warn_unused simdjson_inline error_code document::get(std::st
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint32_t& out) & noexcept { return get_uint32().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(int32_t& out) & noexcept { return get_int32().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); }
|
||||
|
||||
@@ -365,14 +347,8 @@ simdjson_inline simdjson_result<value> document::at_path(std::string_view json_p
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
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 (json_path.empty()) {
|
||||
return INVALID_JSON_POINTER;
|
||||
}
|
||||
@@ -380,9 +356,9 @@ simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_
|
||||
SIMDJSON_TRY(type().get(t));
|
||||
switch (t) {
|
||||
case json_type::array:
|
||||
return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return (*this).get_array().at_path_with_wildcard(json_path);
|
||||
case json_type::object:
|
||||
return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return (*this).get_object().at_path_with_wildcard(json_path);
|
||||
default:
|
||||
return INVALID_JSON_POINTER;
|
||||
}
|
||||
@@ -528,14 +504,6 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
|
||||
if (error()) { return error(); }
|
||||
return first.get_int64_in_string();
|
||||
}
|
||||
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_uint32() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_uint32();
|
||||
}
|
||||
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_int32() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_int32();
|
||||
}
|
||||
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_double() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_double();
|
||||
@@ -659,27 +627,27 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::op
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator uint64_t() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_uint64();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator int64_t() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_int64();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator double() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_double();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator std::string_view() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_string();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_raw_json_string();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator bool() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_bool();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
@@ -719,15 +687,9 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
|
||||
#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 {
|
||||
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 (error()) { return error(); }
|
||||
return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return first.at_path_with_wildcard(json_path);
|
||||
}
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
@@ -769,18 +731,6 @@ simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64() noexc
|
||||
simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64_in_string() noexcept { return doc->get_root_value_iterator().get_root_uint64_in_string(false); }
|
||||
simdjson_inline simdjson_result<int64_t> document_reference::get_int64() noexcept { return doc->get_root_value_iterator().get_root_int64(false); }
|
||||
simdjson_inline simdjson_result<int64_t> document_reference::get_int64_in_string() noexcept { return doc->get_root_value_iterator().get_root_int64_in_string(false); }
|
||||
simdjson_inline simdjson_result<uint32_t> document_reference::get_uint32() noexcept {
|
||||
uint64_t result;
|
||||
SIMDJSON_TRY(doc->get_root_value_iterator().get_root_uint64(false).get(result));
|
||||
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
|
||||
return static_cast<uint32_t>(result);
|
||||
}
|
||||
simdjson_inline simdjson_result<int32_t> document_reference::get_int32() noexcept {
|
||||
int64_t result;
|
||||
SIMDJSON_TRY(doc->get_root_value_iterator().get_root_int64(false).get(result));
|
||||
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
|
||||
return static_cast<int32_t>(result);
|
||||
}
|
||||
simdjson_inline simdjson_result<double> document_reference::get_double() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
|
||||
simdjson_inline simdjson_result<double> document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
|
||||
simdjson_inline simdjson_result<std::string_view> document_reference::get_string(bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(false, allow_replacement); }
|
||||
@@ -798,8 +748,6 @@ template<> simdjson_inline simdjson_result<std::string_view> document_reference:
|
||||
template<> simdjson_inline simdjson_result<double> document_reference::get() & noexcept { return get_double(); }
|
||||
template<> simdjson_inline simdjson_result<uint64_t> document_reference::get() & noexcept { return get_uint64(); }
|
||||
template<> simdjson_inline simdjson_result<int64_t> document_reference::get() & noexcept { return get_int64(); }
|
||||
template<> simdjson_inline simdjson_result<uint32_t> document_reference::get() & noexcept { return get_uint32(); }
|
||||
template<> simdjson_inline simdjson_result<int32_t> document_reference::get() & noexcept { return get_int32(); }
|
||||
template<> simdjson_inline simdjson_result<bool> document_reference::get() & noexcept { return get_bool(); }
|
||||
template<> simdjson_inline simdjson_result<value> document_reference::get() & noexcept { return get_value(); }
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
@@ -838,13 +786,7 @@ 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); }
|
||||
#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::vector<value>> document_reference::at_path_with_wildcard(std::string_view json_path) noexcept { return doc->at_path_with_wildcard(json_path); }
|
||||
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
|
||||
@@ -937,14 +879,6 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
|
||||
if (error()) { return error(); }
|
||||
return first.get_int64_in_string();
|
||||
}
|
||||
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_uint32() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_uint32();
|
||||
}
|
||||
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_int32() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_int32();
|
||||
}
|
||||
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_double() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_double();
|
||||
@@ -1060,27 +994,27 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_refe
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator uint64_t() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_uint64();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator int64_t() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_int64();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator double() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_double();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator std::string_view() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_string();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_raw_json_string();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator bool() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
return first.get_bool();
|
||||
return first;
|
||||
}
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) {
|
||||
if (error()) { throw simdjson_error(error()); }
|
||||
@@ -1109,17 +1043,11 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
}
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
#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 {
|
||||
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 (error()) {
|
||||
return error();
|
||||
}
|
||||
return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return first.at_path_with_wildcard(json_path);
|
||||
}
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
|
||||
@@ -76,26 +76,6 @@ public:
|
||||
* @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer.
|
||||
*/
|
||||
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
|
||||
/**
|
||||
* Cast this JSON value to a 32-bit unsigned integer.
|
||||
*
|
||||
* Calls get_uint64() and checks that the result fits in a uint32_t.
|
||||
*
|
||||
* @returns A 32-bit unsigned integer.
|
||||
* @returns INCORRECT_TYPE If the JSON value is not an unsigned integer.
|
||||
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in a uint32_t.
|
||||
*/
|
||||
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
|
||||
/**
|
||||
* Cast this JSON value to a 32-bit signed integer.
|
||||
*
|
||||
* Calls get_int64() and checks that the result fits in an int32_t.
|
||||
*
|
||||
* @returns A 32-bit signed integer.
|
||||
* @returns INCORRECT_TYPE If the JSON value is not an integer.
|
||||
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in an int32_t.
|
||||
*/
|
||||
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
|
||||
/**
|
||||
* Cast this JSON value to a double.
|
||||
*
|
||||
@@ -319,21 +299,21 @@ public:
|
||||
* @returns A signed 64-bit integer.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer.
|
||||
*/
|
||||
explicit simdjson_inline operator uint64_t() noexcept(false);
|
||||
simdjson_inline operator uint64_t() noexcept(false);
|
||||
/**
|
||||
* Cast this JSON value to a signed integer.
|
||||
*
|
||||
* @returns A signed 64-bit integer.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer.
|
||||
*/
|
||||
explicit simdjson_inline operator int64_t() noexcept(false);
|
||||
simdjson_inline operator int64_t() noexcept(false);
|
||||
/**
|
||||
* Cast this JSON value to a double.
|
||||
*
|
||||
* @returns A double.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number.
|
||||
*/
|
||||
explicit simdjson_inline operator double() noexcept(false);
|
||||
simdjson_inline operator double() noexcept(false);
|
||||
/**
|
||||
* Cast this JSON value to a string.
|
||||
*
|
||||
@@ -343,7 +323,7 @@ public:
|
||||
* time it parses a document or when it is destroyed.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
|
||||
*/
|
||||
explicit simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound;
|
||||
simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound;
|
||||
/**
|
||||
* Cast this JSON value to a raw_json_string.
|
||||
*
|
||||
@@ -352,14 +332,14 @@ public:
|
||||
* @returns A pointer to the raw JSON for the given string.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
|
||||
*/
|
||||
explicit simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound;
|
||||
simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound;
|
||||
/**
|
||||
* Cast this JSON value to a bool.
|
||||
*
|
||||
* @returns A bool value.
|
||||
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false.
|
||||
*/
|
||||
explicit simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator bool() noexcept(false);
|
||||
/**
|
||||
* Cast this JSON value to a value when the document is an object or an array.
|
||||
*
|
||||
@@ -744,24 +724,21 @@ public:
|
||||
simdjson_inline simdjson_result<value> at_path(std::string_view json_path) noexcept;
|
||||
|
||||
/**
|
||||
* Call the provided callback for each value matching the given JSONPath
|
||||
* expression with wildcard support.
|
||||
* Get all values 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
|
||||
* @param callback Function called for each matching value
|
||||
* @return error_code indicating success or failure
|
||||
* @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
|
||||
*/
|
||||
#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;
|
||||
simdjson_inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
|
||||
/**
|
||||
* Consumes the document and returns a string_view instance corresponding to the
|
||||
@@ -852,8 +829,6 @@ public:
|
||||
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
|
||||
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
@@ -948,12 +923,12 @@ public:
|
||||
explicit simdjson_inline operator T() noexcept(false);
|
||||
simdjson_inline operator array() & noexcept(false);
|
||||
simdjson_inline operator object() & noexcept(false);
|
||||
explicit simdjson_inline operator uint64_t() noexcept(false);
|
||||
explicit simdjson_inline operator int64_t() noexcept(false);
|
||||
explicit simdjson_inline operator double() noexcept(false);
|
||||
explicit simdjson_inline operator std::string_view() noexcept(false);
|
||||
explicit simdjson_inline operator raw_json_string() noexcept(false);
|
||||
explicit simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator uint64_t() noexcept(false);
|
||||
simdjson_inline operator int64_t() noexcept(false);
|
||||
simdjson_inline operator double() noexcept(false);
|
||||
simdjson_inline operator std::string_view() noexcept(false);
|
||||
simdjson_inline operator raw_json_string() noexcept(false);
|
||||
simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator value() noexcept(false);
|
||||
#endif
|
||||
simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
|
||||
@@ -982,13 +957,7 @@ 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;
|
||||
#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;
|
||||
simdjson_inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
|
||||
private:
|
||||
document *doc{nullptr};
|
||||
@@ -1013,8 +982,6 @@ public:
|
||||
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
|
||||
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
@@ -1039,12 +1006,12 @@ public:
|
||||
explicit simdjson_inline operator T() noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false);
|
||||
explicit simdjson_inline operator uint64_t() noexcept(false);
|
||||
explicit simdjson_inline operator int64_t() noexcept(false);
|
||||
explicit simdjson_inline operator double() noexcept(false);
|
||||
explicit simdjson_inline operator std::string_view() noexcept(false);
|
||||
explicit simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
|
||||
explicit simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator uint64_t() noexcept(false);
|
||||
simdjson_inline operator int64_t() noexcept(false);
|
||||
simdjson_inline operator double() noexcept(false);
|
||||
simdjson_inline operator std::string_view() noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
|
||||
simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false);
|
||||
#endif
|
||||
simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
|
||||
@@ -1074,13 +1041,7 @@ 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;
|
||||
#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::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
@@ -1108,8 +1069,6 @@ public:
|
||||
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
|
||||
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
@@ -1131,12 +1090,12 @@ public:
|
||||
explicit simdjson_inline operator T() noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false);
|
||||
explicit simdjson_inline operator uint64_t() noexcept(false);
|
||||
explicit simdjson_inline operator int64_t() noexcept(false);
|
||||
explicit simdjson_inline operator double() noexcept(false);
|
||||
explicit simdjson_inline operator std::string_view() noexcept(false);
|
||||
explicit simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
|
||||
explicit simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator uint64_t() noexcept(false);
|
||||
simdjson_inline operator int64_t() noexcept(false);
|
||||
simdjson_inline operator double() noexcept(false);
|
||||
simdjson_inline operator std::string_view() noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
|
||||
simdjson_inline operator bool() noexcept(false);
|
||||
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false);
|
||||
#endif
|
||||
simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
|
||||
@@ -1165,13 +1124,7 @@ 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;
|
||||
#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::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
template<constevalutil::fixed_string... FieldNames, typename T>
|
||||
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
|
||||
|
||||
@@ -95,15 +95,13 @@ simdjson_inline document_stream::document_stream(
|
||||
const uint8_t *_buf,
|
||||
size_t _len,
|
||||
size_t _batch_size,
|
||||
bool _allow_comma_separated,
|
||||
stream_format _format
|
||||
bool _allow_comma_separated
|
||||
) noexcept
|
||||
: parser{&_parser},
|
||||
buf{_buf},
|
||||
len{_len},
|
||||
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
|
||||
allow_comma_separated{_allow_comma_separated},
|
||||
format{_format},
|
||||
error{SUCCESS}
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
|
||||
@@ -122,7 +120,6 @@ simdjson_inline document_stream::document_stream() noexcept
|
||||
len{0},
|
||||
batch_size{0},
|
||||
allow_comma_separated{false},
|
||||
format{stream_format::whitespace_delimited},
|
||||
error{UNINITIALIZED}
|
||||
#ifdef SIMDJSON_THREADS_ENABLED
|
||||
, use_thread(false)
|
||||
@@ -222,10 +219,7 @@ inline void document_stream::start() noexcept {
|
||||
error = run_stage1(*parser, batch_start);
|
||||
}
|
||||
if (error) { return; }
|
||||
// For json_sequence mode, structural_indexes[0] points to the actual JSON value
|
||||
// after the RS delimiter and any following whitespace. For regular mode, it is
|
||||
// the offset from batch_start to the first document in the batch.
|
||||
doc_index = batch_start + parser->implementation->structural_indexes[0];
|
||||
doc_index = batch_start;
|
||||
doc = document(json_iterator(&buf[batch_start], parser));
|
||||
doc.iter._streaming = true;
|
||||
|
||||
@@ -306,7 +300,7 @@ inline void document_stream::next() noexcept {
|
||||
*/
|
||||
|
||||
if (error) { continue; } // If the error was EMPTY, we may want to load another batch.
|
||||
doc_index = batch_start + parser->implementation->structural_indexes[0];
|
||||
doc_index = batch_start;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,35 +329,10 @@ inline error_code document_stream::run_stage1(ondemand::parser &p, size_t _batch
|
||||
// This code only updates the structural index in the parser, it does not update any json_iterator
|
||||
// instance.
|
||||
size_t remaining = len - _batch_start;
|
||||
stage1_mode mode;
|
||||
if (remaining <= batch_size) {
|
||||
// Final batch
|
||||
switch (format) {
|
||||
case stream_format::json_sequence:
|
||||
mode = stage1_mode::json_sequence_final;
|
||||
break;
|
||||
case stream_format::comma_delimited:
|
||||
mode = stage1_mode::comma_delimited_final;
|
||||
break;
|
||||
default:
|
||||
mode = stage1_mode::streaming_final;
|
||||
break;
|
||||
}
|
||||
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
|
||||
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
|
||||
} else {
|
||||
// Partial batch
|
||||
switch (format) {
|
||||
case stream_format::json_sequence:
|
||||
mode = stage1_mode::json_sequence_partial;
|
||||
break;
|
||||
case stream_format::comma_delimited:
|
||||
mode = stage1_mode::comma_delimited_partial;
|
||||
break;
|
||||
default:
|
||||
mode = stage1_mode::streaming_partial;
|
||||
break;
|
||||
}
|
||||
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
|
||||
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,21 +353,14 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
|
||||
depth--;
|
||||
break;
|
||||
default: // Scalar value document
|
||||
// TODO: We could remove trailing whitespaces
|
||||
// This returns a string spanning from start of value to the beginning of the next document (excluded)
|
||||
{
|
||||
auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index];
|
||||
// normally the length would be next_index - current_index() - 1, except for the last document
|
||||
size_t svlen = next_index - current_index();
|
||||
const char *start = reinterpret_cast<const char*>(stream->buf) + current_index();
|
||||
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464
|
||||
// json_sequence mode the scanner classifies RS as a scalar
|
||||
// character, so an RS-prefixed scalar document (number / true /
|
||||
// false / null / string) has no closing structural index and the
|
||||
// slice runs all the way up to the next document's RS. RS cannot
|
||||
// legally appear in a JSON value at the source level (control
|
||||
// characters in strings must be escaped as \u001E), so stripping
|
||||
// it is safe in every stream_format.
|
||||
while(svlen > 1 && (std::isspace(static_cast<unsigned char>(start[svlen-1])) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E || (stream->format == stream_format::comma_delimited && start[svlen-1] == ','))) {
|
||||
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
|
||||
svlen--;
|
||||
}
|
||||
return std::string_view(start, svlen);
|
||||
@@ -479,4 +441,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_stre
|
||||
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H
|
||||
@@ -229,16 +229,13 @@ private:
|
||||
* @param buf is the raw byte buffer we need to process
|
||||
* @param len is the length of the raw byte buffer in bytes
|
||||
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
|
||||
* @param allow_comma_separated whether to allow comma-separated documents
|
||||
* @param format the stream format
|
||||
*/
|
||||
simdjson_inline document_stream(
|
||||
ondemand::parser &parser,
|
||||
const uint8_t *buf,
|
||||
size_t len,
|
||||
size_t batch_size,
|
||||
bool allow_comma_separated,
|
||||
stream_format format = stream_format::whitespace_delimited
|
||||
bool allow_comma_separated
|
||||
) noexcept;
|
||||
|
||||
/**
|
||||
@@ -287,7 +284,6 @@ private:
|
||||
size_t len;
|
||||
size_t batch_size;
|
||||
bool allow_comma_separated;
|
||||
stream_format format;
|
||||
/**
|
||||
* We are going to use just one document instance. The document owns
|
||||
* the json_iterator. It implies that we only ever pass a reference
|
||||
|
||||
@@ -24,9 +24,6 @@ simdjson_inline json_iterator::json_iterator(json_iterator &&other) noexcept
|
||||
_depth{other._depth},
|
||||
_root{other._root},
|
||||
_streaming{other._streaming}
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
, _allow_incomplete_json{other._allow_incomplete_json}
|
||||
#endif
|
||||
{
|
||||
other.parser = nullptr;
|
||||
}
|
||||
@@ -38,9 +35,6 @@ simdjson_inline json_iterator &json_iterator::operator=(json_iterator &&other) n
|
||||
_depth = other._depth;
|
||||
_root = other._root;
|
||||
_streaming = other._streaming;
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
_allow_incomplete_json = other._allow_incomplete_json;
|
||||
#endif
|
||||
other.parser = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@@ -67,8 +61,7 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse
|
||||
_string_buf_loc{parser->string_buf.get()},
|
||||
_depth{1},
|
||||
_root{parser->implementation->structural_indexes.get()},
|
||||
_streaming{streaming},
|
||||
_allow_incomplete_json{true}
|
||||
_streaming{streaming}
|
||||
|
||||
{
|
||||
logger::log_headers();
|
||||
@@ -200,17 +193,6 @@ simdjson_inline bool json_iterator::streaming() const noexcept {
|
||||
return _streaming;
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool json_iterator::allow_incomplete_json() const noexcept {
|
||||
return _allow_incomplete_json;
|
||||
}
|
||||
|
||||
simdjson_inline size_t json_iterator::remaining_input_length(const uint8_t *json) const noexcept {
|
||||
const uint8_t *end = token.buf + parser->_document_len;
|
||||
return json < end ? size_t(end - json) : 0;
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
simdjson_inline token_position json_iterator::root_position() const noexcept {
|
||||
return _root;
|
||||
}
|
||||
@@ -468,4 +450,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::json_iterator
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
|
||||
@@ -60,9 +60,6 @@ protected:
|
||||
* value of this attribute.
|
||||
*/
|
||||
bool _streaming{false};
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
bool _allow_incomplete_json{false};
|
||||
#endif
|
||||
|
||||
public:
|
||||
simdjson_inline json_iterator() noexcept = default;
|
||||
@@ -87,10 +84,6 @@ public:
|
||||
* start_root_array() and start_root_object().
|
||||
*/
|
||||
simdjson_inline bool streaming() const noexcept;
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool allow_incomplete_json() const noexcept;
|
||||
simdjson_inline size_t remaining_input_length(const uint8_t *json) const noexcept;
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
/**
|
||||
* Get the root value iterator
|
||||
@@ -342,4 +335,4 @@ public:
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
|
||||
@@ -1,979 +0,0 @@
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#include "simdjson/base.h"
|
||||
#include "simdjson/common_defs.h"
|
||||
#include "simdjson/constevalutil.h"
|
||||
#include "simdjson/generic/ondemand/raw_json_string.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#if defined(__aarch64__) || defined(__ARM_NEON)
|
||||
#include <arm_neon.h>
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_NEON 1
|
||||
#else
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_NEON 0
|
||||
#endif
|
||||
#if defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_SSE2 1
|
||||
#else
|
||||
#define SIMDJSON_KEY_SELECTOR_HAS_SSE2 0
|
||||
#endif
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
namespace key_selector_detail {
|
||||
|
||||
// ============================================================================
|
||||
// Compile-time perfect-hash generator.
|
||||
// It scales to ~100 keys at compile time by determining association values one (position, character)
|
||||
// symbol at a time (gperf-style) instead of an exhaustive offset search, and
|
||||
// falls back to a Hash-and-Displace construction for large/awkward key sets.
|
||||
//
|
||||
// Only flat tables survive to runtime; the lookup is a few additions plus a
|
||||
// single SIMD key comparison (see match_raw below).
|
||||
// ============================================================================
|
||||
|
||||
// Maximum number of character positions the gperf hash may combine.
|
||||
static constexpr std::size_t MAX_POSITIONS = 16;
|
||||
// Sentinel "position" meaning "the last character of the key".
|
||||
static constexpr std::size_t LAST_CHAR = std::size_t(-1);
|
||||
// Runtime-encoded sentinels (stored in uint8 tables).
|
||||
static constexpr std::uint8_t POS_LAST_CHAR = 0xFF; // positions_[i] == last char
|
||||
static constexpr std::uint8_t HD_MODE = 0xFF; // num_positions == H&D mode
|
||||
// Flags stored in positions[2] in H&D mode to select the key-hash variant.
|
||||
static constexpr std::size_t HD_HASH_2BYTE_FLAG = 2;
|
||||
static constexpr std::size_t HD_HASH_4BYTE_FLAG = 4;
|
||||
|
||||
constexpr std::size_t next_power_of_2(std::size_t n) noexcept {
|
||||
if (n == 0) { return 1; }
|
||||
std::size_t p = 1;
|
||||
while (p < n) { p <<= 1; }
|
||||
return p;
|
||||
}
|
||||
|
||||
// Character at a given position (LAST_CHAR means last character), or 256 if out
|
||||
// of bounds.
|
||||
constexpr std::size_t char_at(std::string_view key, std::size_t pos) noexcept {
|
||||
if (pos == LAST_CHAR) {
|
||||
if (key.empty()) { return 256; }
|
||||
return static_cast<unsigned char>(key[key.size() - 1]);
|
||||
}
|
||||
if (pos >= key.size()) { return 256; }
|
||||
return static_cast<unsigned char>(key[pos]);
|
||||
}
|
||||
|
||||
// Count key pairs that a set of positions fails to distinguish. Keys whose
|
||||
// lengths differ modulo the table size are separated by the length term in the
|
||||
// hash, so they need no position coverage.
|
||||
template <std::size_t N>
|
||||
consteval std::size_t count_undistinguished_pairs(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
const std::size_t* positions,
|
||||
std::size_t num_positions,
|
||||
std::size_t modulus) {
|
||||
std::size_t count = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
for (std::size_t j = i + 1; j < N; ++j) {
|
||||
if (keys[i].size() % modulus != keys[j].size() % modulus) { continue; }
|
||||
bool distinguished = false;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
if (char_at(keys[i], positions[p]) != char_at(keys[j], positions[p])) {
|
||||
distinguished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!distinguished) { ++count; }
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
consteval bool positions_distinguish(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
const std::size_t* positions,
|
||||
std::size_t num_positions,
|
||||
std::size_t modulus) {
|
||||
return count_undistinguished_pairs<N>(keys, positions, num_positions, modulus) == 0;
|
||||
}
|
||||
|
||||
// Number of distinct (length % modulus, char_at(key, pos)) pairs at a position.
|
||||
template <std::size_t N>
|
||||
consteval std::size_t discriminating_power(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::size_t pos,
|
||||
std::size_t modulus) {
|
||||
struct pair { std::size_t len_mod; std::size_t ch; };
|
||||
std::array<pair, N> pairs{};
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
pairs[i] = {keys[i].size() % modulus, char_at(keys[i], pos)};
|
||||
}
|
||||
std::size_t count = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
bool dup = false;
|
||||
for (std::size_t j = 0; j < i; ++j) {
|
||||
if (pairs[i].len_mod == pairs[j].len_mod && pairs[i].ch == pairs[j].ch) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) { ++count; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
consteval std::size_t max_key_length(const std::array<std::string_view, N>& keys) {
|
||||
std::size_t m = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (keys[i].size() > m) { m = keys[i].size(); }
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Bounded backtracking DFS for a minimal set of distinguishing positions.
|
||||
template <std::size_t N>
|
||||
consteval bool backtracking_search(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
const std::size_t* candidates,
|
||||
std::size_t num_candidates,
|
||||
std::size_t* positions,
|
||||
std::size_t& num_positions_out,
|
||||
std::size_t& budget,
|
||||
std::size_t modulus) {
|
||||
constexpr std::size_t MAX_DEPTH = 8;
|
||||
std::size_t breadth = num_candidates < 20 ? num_candidates : 20;
|
||||
|
||||
struct frame { std::size_t depth; std::size_t next_ci; std::size_t parent_count; };
|
||||
std::array<frame, MAX_DEPTH + 1> stack{};
|
||||
std::size_t sp = 0;
|
||||
|
||||
std::size_t initial_count = count_undistinguished_pairs<N>(keys, positions, 0, modulus);
|
||||
if (budget > 0) { --budget; }
|
||||
if (initial_count == 0) { num_positions_out = 0; return true; }
|
||||
|
||||
stack[0] = {0, 0, initial_count};
|
||||
|
||||
while (budget > 0) {
|
||||
if (sp > MAX_DEPTH) {
|
||||
if (sp == 0) { break; }
|
||||
--sp;
|
||||
++stack[sp].next_ci;
|
||||
continue;
|
||||
}
|
||||
auto& f = stack[sp];
|
||||
if (f.next_ci >= breadth) {
|
||||
if (sp == 0) { break; }
|
||||
--sp;
|
||||
++stack[sp].next_ci;
|
||||
continue;
|
||||
}
|
||||
positions[sp] = candidates[f.next_ci];
|
||||
--budget;
|
||||
std::size_t new_count = count_undistinguished_pairs<N>(keys, positions, sp + 1, modulus);
|
||||
if (new_count == 0) { num_positions_out = sp + 1; return true; }
|
||||
if (new_count < f.parent_count && sp + 1 < MAX_DEPTH) {
|
||||
stack[sp + 1] = {sp + 1, f.next_ci + 1, new_count};
|
||||
++sp;
|
||||
} else {
|
||||
++f.next_ci;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 1: select character positions that distinguish all colliding pairs.
|
||||
template <std::size_t N>
|
||||
consteval std::size_t select_positions(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::array<std::size_t, MAX_POSITIONS>& positions,
|
||||
std::size_t modulus) {
|
||||
if (positions_distinguish<N>(keys, positions.data(), 0, modulus)) { return 0; }
|
||||
|
||||
std::size_t max_len = max_key_length(keys);
|
||||
constexpr std::size_t MAX_CANDIDATES = 256;
|
||||
std::array<std::size_t, MAX_CANDIDATES> candidates{};
|
||||
std::array<std::size_t, MAX_CANDIDATES> powers{};
|
||||
std::size_t num_candidates = 0;
|
||||
for (std::size_t p = 0; p < max_len && num_candidates < MAX_CANDIDATES - 1; ++p) {
|
||||
candidates[num_candidates] = p;
|
||||
powers[num_candidates] = discriminating_power(keys, p, modulus);
|
||||
++num_candidates;
|
||||
}
|
||||
if (num_candidates < MAX_CANDIDATES) {
|
||||
candidates[num_candidates] = LAST_CHAR;
|
||||
powers[num_candidates] = discriminating_power(keys, LAST_CHAR, modulus);
|
||||
++num_candidates;
|
||||
}
|
||||
for (std::size_t i = 0; i < num_candidates; ++i) {
|
||||
for (std::size_t j = i + 1; j < num_candidates; ++j) {
|
||||
if (powers[j] > powers[i]) {
|
||||
auto tc = candidates[i]; candidates[i] = candidates[j]; candidates[j] = tc;
|
||||
auto tp = powers[i]; powers[i] = powers[j]; powers[j] = tp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
positions[0] = candidates[0];
|
||||
if (positions_distinguish<N>(keys, positions.data(), 1, modulus)) { return 1; }
|
||||
|
||||
positions[0] = 0;
|
||||
positions[1] = LAST_CHAR;
|
||||
if (positions_distinguish<N>(keys, positions.data(), 2, modulus)) { return 2; }
|
||||
|
||||
{
|
||||
std::size_t budget = 5000;
|
||||
std::size_t num_found = 0;
|
||||
if (backtracking_search<N>(keys, candidates.data(), num_candidates,
|
||||
positions.data(), num_found, budget, modulus)) {
|
||||
return num_found;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t num_pos = 0;
|
||||
for (std::size_t ci = 0; ci < num_candidates && num_pos < MAX_POSITIONS; ++ci) {
|
||||
bool already = false;
|
||||
for (std::size_t p = 0; p < num_pos; ++p) {
|
||||
if (positions[p] == candidates[ci]) { already = true; break; }
|
||||
}
|
||||
if (already) { continue; }
|
||||
positions[num_pos] = candidates[ci];
|
||||
++num_pos;
|
||||
if (positions_distinguish<N>(keys, positions.data(), num_pos, modulus)) { return num_pos; }
|
||||
}
|
||||
|
||||
throw "Failed to find distinguishing positions for perfect hash";
|
||||
}
|
||||
|
||||
// Result of PHF computation. A max-sized slot_to_key array lets the same struct
|
||||
// type carry any chosen table size.
|
||||
template <std::size_t N>
|
||||
struct phf_result {
|
||||
// Allow up to 8x the minimum table size. Sparser tables solve faster.
|
||||
static constexpr std::size_t MAX_TABLE_SIZE = next_power_of_2(N) * 8;
|
||||
std::size_t table_size{};
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso_values{};
|
||||
std::size_t num_positions{};
|
||||
std::array<std::size_t, MAX_POSITIONS> positions{};
|
||||
std::array<std::size_t, MAX_TABLE_SIZE> slot_to_key{};
|
||||
};
|
||||
|
||||
// Partition-based asso_values search (gperf-style). Determines asso_values one
|
||||
// (position, character) symbol at a time; never revisits a value. Equivalence
|
||||
// classes (keys sharing the same undetermined symbols) keep the search cheap.
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_generate_gperf(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS>& asso_values,
|
||||
std::size_t& num_positions,
|
||||
std::array<std::size_t, MAX_POSITIONS>& positions,
|
||||
std::array<std::size_t, M>& slot_to_key) {
|
||||
num_positions = select_positions<N>(keys, positions, M);
|
||||
|
||||
for (std::size_t p = 0; p < MAX_POSITIONS; ++p) {
|
||||
for (std::size_t c = 0; c < 256; ++c) { asso_values[p][c] = 0; }
|
||||
}
|
||||
|
||||
if (num_positions == 0) {
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t slot = keys[i].size() % M;
|
||||
if (slot_to_key[slot] != N) { return false; }
|
||||
slot_to_key[slot] = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<std::array<std::size_t, MAX_POSITIONS>, N> kchars{};
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
kchars[k][p] = char_at(keys[k], positions[p]);
|
||||
}
|
||||
}
|
||||
|
||||
struct sym_t { std::size_t pos; std::size_t ch; std::size_t freq; };
|
||||
constexpr std::size_t MAX_SYMS = MAX_POSITIONS * 256;
|
||||
std::array<sym_t, MAX_SYMS> syms{};
|
||||
std::size_t nsyms = 0;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
std::array<std::size_t, 256> freq{};
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
std::size_t c = kchars[k][p];
|
||||
if (c < 256) { freq[c]++; }
|
||||
}
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
if (freq[c] > 0) { syms[nsyms++] = {p, c, freq[c]}; }
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < nsyms; ++i) {
|
||||
for (std::size_t j = i + 1; j < nsyms; ++j) {
|
||||
if (syms[j].freq > syms[i].freq) {
|
||||
auto tmp = syms[i]; syms[i] = syms[j]; syms[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::size_t, N> phash{};
|
||||
for (std::size_t k = 0; k < N; ++k) { phash[k] = keys[k].size(); }
|
||||
|
||||
// Equivalence-class signatures: sig[k] = XOR of a per-symbol salt over the
|
||||
// key's undetermined symbols. Updated incrementally as symbols are fixed.
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> salt{};
|
||||
{
|
||||
std::size_t s = 0x9e3779b97f4a7c15ULL;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
s = s * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
salt[p][c] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::array<std::size_t, N> sig{};
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
std::size_t s = 0;
|
||||
for (std::size_t p = 0; p < num_positions; ++p) {
|
||||
std::size_t c = kchars[k][p];
|
||||
if (c < 256) { s ^= salt[p][c]; }
|
||||
}
|
||||
sig[k] = s;
|
||||
}
|
||||
std::array<std::size_t, N> order{};
|
||||
for (std::size_t k = 0; k < N; ++k) { order[k] = k; }
|
||||
|
||||
std::array<std::size_t, M> slot_gen{};
|
||||
std::size_t gen = 0;
|
||||
|
||||
std::size_t search_limit = next_power_of_2(M);
|
||||
if (search_limit < 32) { search_limit = 32; }
|
||||
|
||||
for (std::size_t si = 0; si < nsyms; ++si) {
|
||||
std::size_t sp = syms[si].pos;
|
||||
std::size_t sc = syms[si].ch;
|
||||
|
||||
std::size_t sp_salt = salt[sp][sc];
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
if (kchars[k][sp] == sc) { sig[k] ^= sp_salt; }
|
||||
}
|
||||
|
||||
for (std::size_t i = 1; i < N; ++i) {
|
||||
std::size_t x = order[i];
|
||||
std::size_t xs = sig[x];
|
||||
std::size_t j = i;
|
||||
while (j > 0 && sig[order[j - 1]] > xs) {
|
||||
order[j] = order[j - 1];
|
||||
--j;
|
||||
}
|
||||
order[j] = x;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (std::size_t v = 0; v < search_limit && !found; ++v) {
|
||||
bool collision = false;
|
||||
std::size_t ci = 0;
|
||||
while (ci < N && !collision) {
|
||||
std::size_t class_sig = sig[order[ci]];
|
||||
std::size_t cj = ci;
|
||||
while (cj < N && sig[order[cj]] == class_sig) { ++cj; }
|
||||
if (cj - ci > 1) {
|
||||
++gen;
|
||||
for (std::size_t x = ci; x < cj; ++x) {
|
||||
std::size_t k = order[x];
|
||||
std::size_t h = phash[k];
|
||||
if (kchars[k][sp] == sc) { h += v; }
|
||||
h %= M;
|
||||
if (slot_gen[h] == gen) { collision = true; break; }
|
||||
slot_gen[h] = gen;
|
||||
}
|
||||
}
|
||||
ci = cj;
|
||||
}
|
||||
if (!collision) {
|
||||
asso_values[sp][sc] = v;
|
||||
for (std::size_t k = 0; k < N; ++k) {
|
||||
if (kchars[k][sp] == sc) { phash[k] += v; }
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) { return false; }
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t slot = phash[i] % M;
|
||||
if (slot_to_key[slot] != N) { return false; }
|
||||
slot_to_key[slot] = i;
|
||||
}
|
||||
std::size_t filled = 0;
|
||||
for (std::size_t i = 0; i < M; ++i) {
|
||||
if (slot_to_key[i] != N) { ++filled; }
|
||||
}
|
||||
return filled == N;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_compute_phf(const std::array<std::string_view, N>& keys, phf_result<N>& result) {
|
||||
static_assert(M <= phf_result<N>::MAX_TABLE_SIZE, "Table size M exceeds maximum");
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso{};
|
||||
std::size_t npos{};
|
||||
std::array<std::size_t, MAX_POSITIONS> pos{};
|
||||
std::array<std::size_t, M> s2k{};
|
||||
if (try_generate_gperf<N, M>(keys, asso, npos, pos, s2k)) {
|
||||
result.table_size = M;
|
||||
result.asso_values = asso;
|
||||
result.num_positions = npos;
|
||||
result.positions = pos;
|
||||
for (std::size_t i = 0; i < M; ++i) { result.slot_to_key[i] = s2k[i]; }
|
||||
for (std::size_t i = M; i < phf_result<N>::MAX_TABLE_SIZE; ++i) { result.slot_to_key[i] = N; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M, std::size_t MaxM>
|
||||
consteval bool try_gperf_po2(const std::array<std::string_view, N>& keys, phf_result<N>& result) {
|
||||
if (try_compute_phf<N, M>(keys, result)) { return true; }
|
||||
constexpr std::size_t NextM = M * 2;
|
||||
if constexpr (NextM <= MaxM) { return try_gperf_po2<N, NextM, MaxM>(keys, result); }
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Hash-and-Displace fallback --------------------------------------------
|
||||
|
||||
constexpr std::size_t hd_bucket_hash(std::string_view key) noexcept {
|
||||
std::size_t c0 = key.empty() ? 0 : static_cast<unsigned char>(key[0]);
|
||||
std::size_t c1 = key.empty() ? 0 : static_cast<unsigned char>(key[key.size() - 1]);
|
||||
return (c0 + c1 * 3 + key.size() * 17) & 0xFF;
|
||||
}
|
||||
constexpr std::size_t hd_safe_char(const char* p, std::size_t len, std::size_t idx) noexcept {
|
||||
std::size_t has = static_cast<std::size_t>(idx < len);
|
||||
std::size_t si = idx & (std::size_t{0} - has);
|
||||
return static_cast<unsigned char>(p[si]) & (std::size_t{0} - has);
|
||||
}
|
||||
constexpr std::size_t hd_key_hash_2(std::string_view key) noexcept {
|
||||
std::size_t kc = key.size();
|
||||
kc = kc * 31 + static_cast<unsigned char>(key[0]);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 1);
|
||||
return kc;
|
||||
}
|
||||
constexpr std::size_t hd_key_hash_4(std::string_view key) noexcept {
|
||||
std::size_t kc = key.size();
|
||||
kc = kc * 31 + static_cast<unsigned char>(key[0]);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 1);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 2);
|
||||
kc = kc * 31 + hd_safe_char(key.data(), key.size(), 3);
|
||||
return kc;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_hash_and_displace(
|
||||
const std::array<std::string_view, N>& keys,
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS>& asso_values,
|
||||
std::size_t& num_positions,
|
||||
std::array<std::size_t, MAX_POSITIONS>& positions,
|
||||
std::array<std::size_t, M>& slot_to_key) {
|
||||
num_positions = select_positions<N>(keys, positions, M);
|
||||
|
||||
if (num_positions == 0) {
|
||||
for (std::size_t i = 0; i < 256; ++i) { asso_values[0][i] = 0; }
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t slot = keys[i].size() % M;
|
||||
if (slot_to_key[slot] != N) { return false; }
|
||||
slot_to_key[slot] = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < 256; ++i) { asso_values[0][i] = 0; }
|
||||
num_positions = HD_MODE; // sentinel for H&D mode
|
||||
positions[0] = 0;
|
||||
positions[1] = LAST_CHAR;
|
||||
|
||||
std::array<std::size_t, N> key_bucket{};
|
||||
for (std::size_t i = 0; i < N; ++i) { key_bucket[i] = hd_bucket_hash(keys[i]); }
|
||||
|
||||
struct bucket_info { std::size_t ch; std::size_t count; };
|
||||
std::array<bucket_info, N> buckets{};
|
||||
std::size_t num_buckets = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
std::size_t bk = key_bucket[i];
|
||||
bool found = false;
|
||||
for (std::size_t b = 0; b < num_buckets; ++b) {
|
||||
if (buckets[b].ch == bk) { ++buckets[b].count; found = true; break; }
|
||||
}
|
||||
if (!found) { buckets[num_buckets++] = {bk, 1}; }
|
||||
}
|
||||
for (std::size_t i = 0; i < num_buckets; ++i) {
|
||||
for (std::size_t j = i + 1; j < num_buckets; ++j) {
|
||||
if (buckets[j].count > buckets[i].count) {
|
||||
auto tmp = buckets[i]; buckets[i] = buckets[j]; buckets[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto try_placement = [&](auto key_hash_fn) -> bool {
|
||||
for (std::size_t i = 0; i < M; ++i) { slot_to_key[i] = N; }
|
||||
for (std::size_t i = 0; i < 256; ++i) { asso_values[0][i] = 0; }
|
||||
for (std::size_t b = 0; b < num_buckets; ++b) {
|
||||
std::size_t ch = buckets[b].ch;
|
||||
std::array<std::size_t, N> bucket_keys{};
|
||||
std::size_t bk_count = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (key_bucket[i] == ch) { bucket_keys[bk_count++] = i; }
|
||||
}
|
||||
bool placed = false;
|
||||
std::size_t max_d = M < 255 ? M : 255;
|
||||
for (std::size_t d = 0; d < max_d; ++d) {
|
||||
bool ok = true;
|
||||
std::array<std::size_t, N> bucket_slots{};
|
||||
for (std::size_t k = 0; k < bk_count; ++k) {
|
||||
std::size_t slot = (d + key_hash_fn(keys[bucket_keys[k]])) % M;
|
||||
if (slot_to_key[slot] != N) { ok = false; break; }
|
||||
for (std::size_t k2 = 0; k2 < k; ++k2) {
|
||||
if (bucket_slots[k2] == slot) { ok = false; break; }
|
||||
}
|
||||
if (!ok) { break; }
|
||||
bucket_slots[k] = slot;
|
||||
}
|
||||
if (ok) {
|
||||
asso_values[0][ch] = d;
|
||||
for (std::size_t k = 0; k < bk_count; ++k) {
|
||||
slot_to_key[bucket_slots[k]] = bucket_keys[k];
|
||||
}
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) { return false; }
|
||||
}
|
||||
std::size_t filled = 0;
|
||||
for (std::size_t i = 0; i < M; ++i) {
|
||||
if (slot_to_key[i] != N) { ++filled; }
|
||||
}
|
||||
return filled == N;
|
||||
};
|
||||
|
||||
if (try_placement([](std::string_view k) { return hd_key_hash_2(k); })) {
|
||||
positions[2] = HD_HASH_2BYTE_FLAG;
|
||||
return true;
|
||||
}
|
||||
if (try_placement([](std::string_view k) { return hd_key_hash_4(k); })) {
|
||||
positions[2] = HD_HASH_4BYTE_FLAG;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval bool try_compute_phf_hd(const std::array<std::string_view, N>& keys, phf_result<N>& result) {
|
||||
static_assert(M <= phf_result<N>::MAX_TABLE_SIZE, "Table size M exceeds maximum");
|
||||
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso{};
|
||||
std::size_t npos{};
|
||||
std::array<std::size_t, MAX_POSITIONS> pos{};
|
||||
std::array<std::size_t, M> s2k{};
|
||||
if (try_hash_and_displace<N, M>(keys, asso, npos, pos, s2k)) {
|
||||
result.table_size = M;
|
||||
result.asso_values = asso;
|
||||
result.num_positions = npos;
|
||||
result.positions = pos;
|
||||
for (std::size_t i = 0; i < M; ++i) { result.slot_to_key[i] = s2k[i]; }
|
||||
for (std::size_t i = M; i < phf_result<N>::MAX_TABLE_SIZE; ++i) { result.slot_to_key[i] = N; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
consteval phf_result<N> compute_phf_hd_po2(const std::array<std::string_view, N>& keys) {
|
||||
static_assert(M <= phf_result<N>::MAX_TABLE_SIZE, "Table size M exceeds maximum");
|
||||
phf_result<N> result{};
|
||||
if (try_compute_phf_hd<N, M>(keys, result)) { return result; }
|
||||
constexpr std::size_t NextM = M * 2;
|
||||
if constexpr (NextM <= phf_result<N>::MAX_TABLE_SIZE) {
|
||||
return compute_phf_hd_po2<N, NextM>(keys);
|
||||
} else {
|
||||
throw "Hash-and-Displace: failed to find valid table size";
|
||||
}
|
||||
}
|
||||
|
||||
// Compute a perfect hash for `keys`: try gperf at power-of-two sizes (capped so
|
||||
// the runtime tables stay within uint8 indices), then fall back to H&D.
|
||||
template <std::size_t N>
|
||||
consteval phf_result<N> compute_phf(const std::array<std::string_view, N>& keys) {
|
||||
constexpr std::size_t StartM = next_power_of_2(N);
|
||||
constexpr std::size_t GPERF_MAX_TABLE =
|
||||
phf_result<N>::MAX_TABLE_SIZE < 256 ? phf_result<N>::MAX_TABLE_SIZE : 256;
|
||||
if constexpr (StartM <= GPERF_MAX_TABLE) {
|
||||
phf_result<N> result{};
|
||||
if (try_gperf_po2<N, StartM, GPERF_MAX_TABLE>(keys, result)) { return result; }
|
||||
}
|
||||
return compute_phf_hd_po2<N, StartM>(keys);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Runtime tables (flat, uint8) derived from a phf_result.
|
||||
// ============================================================================
|
||||
|
||||
template <std::size_t N, std::size_t TableSize, std::size_t MaxKeyLen>
|
||||
struct phf_data {
|
||||
std::array<std::array<std::uint8_t, 256>, MAX_POSITIONS> asso_values{};
|
||||
std::array<std::uint8_t, MAX_POSITIONS> positions{};
|
||||
std::uint8_t num_positions{};
|
||||
std::uint8_t hd_hash_variant{}; // 2 or 4 (H&D only)
|
||||
std::array<std::uint8_t, TableSize> slot_to_key{};
|
||||
// slot_key_bytes[s] holds the key stored at slot s, zero-padded to a 16-byte
|
||||
// multiple so the SIMD comparison can read a whole register.
|
||||
std::array<std::array<char, ((MaxKeyLen + 15) / 16) * 16>, TableSize> slot_key_bytes{};
|
||||
std::array<std::uint8_t, TableSize> slot_key_len{};
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
constexpr std::size_t compute_max_key_len(const std::array<std::string_view, N>& keys) noexcept {
|
||||
std::size_t m = 0;
|
||||
for (std::size_t i = 0; i < N; ++i) { if (keys[i].size() > m) { m = keys[i].size(); } }
|
||||
return m;
|
||||
}
|
||||
|
||||
// Validate keys and build the runtime tables from the computed perfect hash.
|
||||
template <std::size_t N, std::size_t TableSize, std::size_t MaxKeyLen>
|
||||
consteval phf_data<N, TableSize, MaxKeyLen>
|
||||
build_phf_data(const std::array<std::string_view, N>& keys, const phf_result<N>& result) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
if (keys[i].empty()) { throw "empty keys are not allowed in key_selector"; }
|
||||
if (keys[i].size() > MaxKeyLen) { throw "key length exceeds MaxKeyLen"; }
|
||||
for (char c : keys[i]) {
|
||||
if (c == '\\') { throw "backslash not allowed in key_selector keys"; }
|
||||
if (c == '"') { throw "quote not allowed in key_selector keys"; }
|
||||
if (c == '\0') { throw "null byte not allowed in key_selector keys"; }
|
||||
}
|
||||
for (std::size_t j = i + 1; j < N; ++j) {
|
||||
if (keys[i] == keys[j]) { throw "duplicate keys in key_selector"; }
|
||||
}
|
||||
}
|
||||
|
||||
phf_data<N, TableSize, MaxKeyLen> out{};
|
||||
|
||||
if (result.num_positions == HD_MODE) {
|
||||
// H&D mode: single displacement table in asso_values[0].
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
out.asso_values[0][c] = static_cast<std::uint8_t>(result.asso_values[0][c]);
|
||||
}
|
||||
out.num_positions = static_cast<std::uint8_t>(HD_MODE);
|
||||
out.hd_hash_variant = static_cast<std::uint8_t>(result.positions[2]);
|
||||
} else {
|
||||
for (std::size_t pi = 0; pi < result.num_positions; ++pi) {
|
||||
for (std::size_t c = 0; c < 256; ++c) {
|
||||
out.asso_values[pi][c] = static_cast<std::uint8_t>(result.asso_values[pi][c] % TableSize);
|
||||
}
|
||||
}
|
||||
out.num_positions = static_cast<std::uint8_t>(result.num_positions);
|
||||
for (std::size_t i = 0; i < result.num_positions; ++i) {
|
||||
out.positions[i] = (result.positions[i] == LAST_CHAR)
|
||||
? POS_LAST_CHAR
|
||||
: static_cast<std::uint8_t>(result.positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t s = 0; s < TableSize; ++s) {
|
||||
out.slot_to_key[s] = static_cast<std::uint8_t>(result.slot_to_key[s]);
|
||||
}
|
||||
|
||||
for (std::size_t s = 0; s < TableSize; ++s) {
|
||||
std::size_t ki = result.slot_to_key[s];
|
||||
if (ki < N) {
|
||||
auto k = keys[ki];
|
||||
out.slot_key_len[s] = static_cast<std::uint8_t>(k.size());
|
||||
for (std::size_t c = 0; c < k.size(); ++c) { out.slot_key_bytes[s][c] = k[c]; }
|
||||
} else {
|
||||
out.slot_key_len[s] = 0; // empty slot: no length can match
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// --- SIMD runtime primitives ------------------------------------------------
|
||||
|
||||
// Scan for the terminating '"' starting at p. Returns its byte offset (= key
|
||||
// length). Caller guarantees SIMDJSON_PADDING bytes past the JSON buffer, so the
|
||||
// load is safe.
|
||||
template <std::size_t MaxKeyLen>
|
||||
simdjson_really_inline std::size_t scan_key_length(const char* p) noexcept {
|
||||
// The SIMD paths scan only two 16-byte blocks (offsets 0..31), so a key
|
||||
// whose closing quote sits at offset 32 would be missed. Cap at 31 to keep
|
||||
// SIMD and scalar builds in agreement.
|
||||
static_assert(MaxKeyLen <= 31, "MaxKeyLen must be <= 31 for current SIMD implementations");
|
||||
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
|
||||
uint8x16_t v0 = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
|
||||
uint8x16_t cmp0 = vceqq_u8(v0, vdupq_n_u8('"'));
|
||||
uint64_t m0 = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp0), 4)), 0);
|
||||
if constexpr (MaxKeyLen < 16) {
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctzll(m0)) >> 2;
|
||||
return MaxKeyLen + 1;
|
||||
} else {
|
||||
uint8x16_t v1 = vld1q_u8(reinterpret_cast<const uint8_t*>(p) + 16);
|
||||
uint8x16_t cmp1 = vceqq_u8(v1, vdupq_n_u8('"'));
|
||||
uint64_t m1 = vget_lane_u64(
|
||||
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp1), 4)), 0);
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctzll(m0)) >> 2;
|
||||
if (m1 != 0) return 16 + (std::size_t(__builtin_ctzll(m1)) >> 2);
|
||||
return MaxKeyLen + 1;
|
||||
}
|
||||
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
|
||||
__m128i v0 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
|
||||
__m128i cmp0 = _mm_cmpeq_epi8(v0, _mm_set1_epi8('"'));
|
||||
unsigned m0 = static_cast<unsigned>(_mm_movemask_epi8(cmp0));
|
||||
if constexpr (MaxKeyLen < 16) {
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctz(m0));
|
||||
return MaxKeyLen + 1;
|
||||
} else {
|
||||
__m128i v1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16));
|
||||
__m128i cmp1 = _mm_cmpeq_epi8(v1, _mm_set1_epi8('"'));
|
||||
unsigned m1 = static_cast<unsigned>(_mm_movemask_epi8(cmp1));
|
||||
if (simdjson_likely(m0 != 0)) return std::size_t(__builtin_ctz(m0));
|
||||
if (m1 != 0) return 16 + std::size_t(__builtin_ctz(m1));
|
||||
return MaxKeyLen + 1;
|
||||
}
|
||||
#else
|
||||
for (std::size_t i = 0; i <= MaxKeyLen; ++i)
|
||||
if (p[i] == '"') return i;
|
||||
return MaxKeyLen + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Byte-equal of p[0..len) against stored[0..len). stored is zero-padded past
|
||||
// `len`. Input is read over 16 or 32 bytes (padded JSON buffer guaranteed).
|
||||
template <std::size_t MaxKeyLen>
|
||||
simdjson_really_inline bool compare_key_bytes(
|
||||
const char* p, const char* stored, std::size_t len) noexcept {
|
||||
alignas(16) static constexpr uint8_t idx16[16] =
|
||||
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
|
||||
if constexpr (MaxKeyLen <= 16) {
|
||||
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
|
||||
uint8x16_t vp = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
|
||||
uint8x16_t vs = vld1q_u8(reinterpret_cast<const uint8_t*>(stored));
|
||||
uint8x16_t mask = vcltq_u8(vld1q_u8(idx16), vdupq_n_u8(static_cast<uint8_t>(len)));
|
||||
uint8x16_t diff = veorq_u8(vandq_u8(vp, mask), vs);
|
||||
// A 32-bit-lane horizontal max is enough to decide "all bytes equal"
|
||||
// (diff is zero iff every 32-bit word is zero) and is cheaper than a
|
||||
// byte-wide reduction.
|
||||
return vmaxvq_u32(vreinterpretq_u32_u8(diff)) == 0;
|
||||
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
|
||||
__m128i vp = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
|
||||
__m128i vs = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored));
|
||||
__m128i idx = _mm_load_si128(reinterpret_cast<const __m128i*>(idx16));
|
||||
__m128i mask = _mm_cmplt_epi8(idx, _mm_set1_epi8(static_cast<char>(len)));
|
||||
__m128i eq = _mm_cmpeq_epi8(_mm_and_si128(vp, mask), vs);
|
||||
return _mm_movemask_epi8(eq) == 0xFFFF;
|
||||
#else
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
if (p[i] != stored[i]) return false;
|
||||
return true;
|
||||
#endif
|
||||
} else if constexpr (MaxKeyLen <= 32) {
|
||||
alignas(16) static constexpr uint8_t idx32_hi[16] =
|
||||
{16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31};
|
||||
#if SIMDJSON_KEY_SELECTOR_HAS_NEON
|
||||
uint8x16_t vp_lo = vld1q_u8(reinterpret_cast<const uint8_t*>(p));
|
||||
uint8x16_t vp_hi = vld1q_u8(reinterpret_cast<const uint8_t*>(p) + 16);
|
||||
uint8x16_t vs_lo = vld1q_u8(reinterpret_cast<const uint8_t*>(stored));
|
||||
uint8x16_t vs_hi = vld1q_u8(reinterpret_cast<const uint8_t*>(stored) + 16);
|
||||
uint8x16_t lenv = vdupq_n_u8(static_cast<uint8_t>(len));
|
||||
uint8x16_t m_lo = vcltq_u8(vld1q_u8(idx16), lenv);
|
||||
uint8x16_t m_hi = vcltq_u8(vld1q_u8(idx32_hi), lenv);
|
||||
uint8x16_t d_lo = veorq_u8(vandq_u8(vp_lo, m_lo), vs_lo);
|
||||
uint8x16_t d_hi = veorq_u8(vandq_u8(vp_hi, m_hi), vs_hi);
|
||||
return vmaxvq_u32(vreinterpretq_u32_u8(vorrq_u8(d_lo, d_hi))) == 0;
|
||||
#elif SIMDJSON_KEY_SELECTOR_HAS_SSE2
|
||||
__m128i vp_lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
|
||||
__m128i vp_hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16));
|
||||
__m128i vs_lo = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored));
|
||||
__m128i vs_hi = _mm_loadu_si128(reinterpret_cast<const __m128i*>(stored + 16));
|
||||
__m128i lenv = _mm_set1_epi8(static_cast<char>(len));
|
||||
__m128i m_lo = _mm_cmplt_epi8(_mm_load_si128(reinterpret_cast<const __m128i*>(idx16)), lenv);
|
||||
__m128i m_hi = _mm_cmplt_epi8(_mm_load_si128(reinterpret_cast<const __m128i*>(idx32_hi)), lenv);
|
||||
__m128i eq_lo = _mm_cmpeq_epi8(_mm_and_si128(vp_lo, m_lo), vs_lo);
|
||||
__m128i eq_hi = _mm_cmpeq_epi8(_mm_and_si128(vp_hi, m_hi), vs_hi);
|
||||
return (_mm_movemask_epi8(eq_lo) & _mm_movemask_epi8(eq_hi)) == 0xFFFF;
|
||||
#else
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
if (p[i] != stored[i]) return false;
|
||||
return true;
|
||||
#endif
|
||||
} else {
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
if (p[i] != stored[i]) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace key_selector_detail
|
||||
|
||||
/**
|
||||
* Stateless, compile-time key selector.
|
||||
*
|
||||
* Usage:
|
||||
* using sel_t = key_selector<"id", "text", "user">;
|
||||
* std::size_t i = sel_t::match_raw(raw_key); // returns sel_t::size() on miss
|
||||
*
|
||||
* The perfect hash is built at compile time (gperf-style, with a
|
||||
* Hash-and-Displace fallback) and only flat tables survive to runtime. All
|
||||
* tables are static constexpr, so the lookup fully inlines.
|
||||
*
|
||||
* Limitations:
|
||||
* - Each key must be at most 31 characters long (and no longer than
|
||||
* SIMDJSON_PADDING). Longer keys trigger a compile-time error.
|
||||
* - The number of keys should be moderate. The hard limit is 255 keys;
|
||||
* compilation time grows with the number of keys, so prefer a few dozen at
|
||||
* most per selector.
|
||||
* - Keys must be distinct, non-empty, and free of backslash, double-quote and
|
||||
* null bytes (matching is done against the raw, unescaped JSON key bytes).
|
||||
*/
|
||||
template <constevalutil::fixed_string... Keys>
|
||||
struct key_selector {
|
||||
static constexpr std::size_t N = sizeof...(Keys);
|
||||
static_assert(N > 0, "key_selector requires at least one key");
|
||||
static_assert(N <= 255,"key_selector supports at most 255 keys");
|
||||
|
||||
static constexpr std::array<std::string_view, N> keys{ Keys.view()... };
|
||||
static constexpr std::size_t max_key_len = key_selector_detail::compute_max_key_len<N>(keys);
|
||||
static_assert(max_key_len <= SIMDJSON_PADDING,
|
||||
"key longer than SIMDJSON_PADDING is not supported");
|
||||
// The SIMD key-length scan covers offsets 0..31 only; a 32-character key's
|
||||
// closing quote lands at offset 32 and would be silently missed on
|
||||
// NEON/SSE2 while still matching in scalar builds. Cap at 31 so the result
|
||||
// is identical across implementations.
|
||||
static_assert(max_key_len <= 31,
|
||||
"key_selector keys must be at most 31 characters long");
|
||||
|
||||
static constexpr auto result = key_selector_detail::compute_phf<N>(keys);
|
||||
static constexpr std::size_t table_size = result.table_size;
|
||||
|
||||
static constexpr auto phf =
|
||||
key_selector_detail::build_phf_data<N, table_size, max_key_len>(keys, result);
|
||||
|
||||
static constexpr std::size_t size() noexcept { return N; }
|
||||
|
||||
/**
|
||||
* Look up a JSON key. rjs must point just after an opening quote in a padded
|
||||
* simdjson buffer. Returns the selector index in [0, N) on match, or N on miss.
|
||||
*/
|
||||
static simdjson_really_inline std::size_t match_raw(raw_json_string rjs) noexcept {
|
||||
const char* p = rjs.raw();
|
||||
std::size_t len = key_selector_detail::scan_key_length<max_key_len>(p);
|
||||
if (len == 0 || len > max_key_len) { return N; }
|
||||
|
||||
std::size_t slot;
|
||||
if (phf.num_positions == key_selector_detail::HD_MODE) {
|
||||
// Hash-and-Displace: bucket displacement + per-key hash.
|
||||
std::string_view key(p, len);
|
||||
std::size_t bucket = key_selector_detail::hd_bucket_hash(key);
|
||||
std::size_t kh = (phf.hd_hash_variant == key_selector_detail::HD_HASH_2BYTE_FLAG)
|
||||
? key_selector_detail::hd_key_hash_2(key)
|
||||
: key_selector_detail::hd_key_hash_4(key);
|
||||
slot = (phf.asso_values[0][bucket] + kh) & (table_size - 1);
|
||||
} else {
|
||||
// gperf: h = len + sum of asso_values over the selected positions.
|
||||
// positions / num_positions / asso_values are compile-time constants,
|
||||
// so this loop fully unrolls. The idx < len guard mirrors the
|
||||
// generator's char_at()-> 256 -> skip behavior for out-of-range
|
||||
// positions (required: arbitrary positions may exceed a key's length).
|
||||
std::size_t h = len;
|
||||
for (std::uint8_t i = 0; i < phf.num_positions; ++i) {
|
||||
std::uint8_t pos = phf.positions[i];
|
||||
std::size_t idx = (pos == key_selector_detail::POS_LAST_CHAR)
|
||||
? (len - std::size_t{1})
|
||||
: static_cast<std::size_t>(pos);
|
||||
if (idx < len) {
|
||||
h += phf.asso_values[i][static_cast<unsigned char>(p[idx])];
|
||||
}
|
||||
}
|
||||
slot = h & (table_size - 1);
|
||||
}
|
||||
|
||||
std::uint8_t ki = phf.slot_to_key[slot];
|
||||
if (ki >= N) { return N; }
|
||||
if (phf.slot_key_len[slot] != len) { return N; }
|
||||
if (!key_selector_detail::compare_key_bytes<max_key_len>(
|
||||
p, phf.slot_key_bytes[slot].data(), len)) { return N; }
|
||||
return ki;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the JSON key at `p` (just past the opening quote, in a padded
|
||||
* buffer) against the compile-time key at selector index I. Because keys[I]
|
||||
* has a compile-time-constant length and bytes, the length check and memcmp
|
||||
* fully inline to a handful of fixed-width loads/compares -- the same cheap
|
||||
* comparison the ordered obj[key] path performs. The trailing-quote check
|
||||
* disambiguates keys that are prefixes of one another and of longer JSON keys.
|
||||
*/
|
||||
template <std::size_t I>
|
||||
static simdjson_really_inline bool matches_at(const char* p) noexcept {
|
||||
constexpr std::string_view k = keys[I];
|
||||
return p[k.size()] == '"' && std::memcmp(p, k.data(), k.size()) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order-independent linear match: try each key in turn with a fully inlined,
|
||||
* compile-time-sized comparison, stopping at the first hit. For a small number
|
||||
* of keys this is cheaper than the perfect hash (no length scan, no table
|
||||
* loads), which matters because deserialization is dominated by many small
|
||||
* structs. Returns the selector index in [0, N) on match, or N on miss.
|
||||
*/
|
||||
static simdjson_really_inline std::size_t match_linear(raw_json_string rjs) noexcept {
|
||||
const char* p = rjs.raw();
|
||||
std::size_t idx = N;
|
||||
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
||||
(void)((matches_at<Is>(p) ? (idx = Is, true) : false) || ...);
|
||||
}(std::make_index_sequence<N>{});
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Selectors no larger than this use the unrolled linear matcher; larger ones
|
||||
// use the perfect hash. The crossover comes from a microbenchmark of both
|
||||
// matchers: for in-order hits (the deserialization case) linear wins up to
|
||||
// ~N=10, and for a pure miss the crossover is ~N=8, so 8 captures the small-
|
||||
// struct win without regressing larger or miss-heavy selectors. match_raw and
|
||||
// match_linear remain available if a caller wants to force one.
|
||||
static constexpr std::size_t linear_match_max = 8;
|
||||
|
||||
/**
|
||||
* Look up a JSON key, choosing the cheaper matcher for this selector's size:
|
||||
* the unrolled linear comparison for small selectors, the perfect hash for
|
||||
* large ones. Returns the selector index in [0, N) on match, or N on miss.
|
||||
*/
|
||||
static simdjson_really_inline std::size_t match(raw_json_string rjs) noexcept {
|
||||
if constexpr (N <= linear_match_max) {
|
||||
return match_linear(rjs);
|
||||
} else {
|
||||
return match_raw(rjs);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the key text at selector index i (i in [0, N)). */
|
||||
static constexpr std::string_view key_at(std::size_t i) noexcept {
|
||||
return keys[i];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_CONCEPTS
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
|
||||
@@ -63,42 +63,6 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
|
||||
return value(iter.child());
|
||||
}
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_flatten simdjson_inline for_each_result object::for_each(Func&& on_match) noexcept {
|
||||
auto first = this->begin();
|
||||
if (first.error()) { return {first.error(), 0}; }
|
||||
object_iterator it = first.value_unsafe();
|
||||
object_iterator last{};
|
||||
std::array<bool, Selector::size()> seen{};
|
||||
std::size_t matched = 0;
|
||||
while (it != last) {
|
||||
auto field_res = *it;
|
||||
if (field_res.error()) { return {field_res.error(), matched}; }
|
||||
field f = field_res.value_unsafe();
|
||||
std::size_t idx = Selector::match(f.key());
|
||||
if (idx < Selector::size() && !seen[idx]) {
|
||||
seen[idx] = true;
|
||||
value matched_value = f.value();
|
||||
// The callback may return either void or an error_code. When it returns an
|
||||
// error_code, we stop at the first non-SUCCESS result and propagate it so
|
||||
// the caller can surface value-parse errors (for example, a type mismatch
|
||||
// on a matched field). A void-returning callback is responsible for
|
||||
// handling its own errors.
|
||||
if constexpr (std::is_same_v<decltype(on_match(idx, matched_value)), error_code>) {
|
||||
error_code e = on_match(idx, matched_value);
|
||||
if (e) { return {e, matched}; }
|
||||
} else {
|
||||
on_match(idx, matched_value);
|
||||
}
|
||||
if (++matched >= Selector::size()) { break; }
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return {SUCCESS, matched};
|
||||
}
|
||||
#endif
|
||||
|
||||
simdjson_inline simdjson_result<object> object::start(value_iterator &iter) noexcept {
|
||||
SIMDJSON_TRY( iter.start_object().error() );
|
||||
return object(iter);
|
||||
@@ -213,13 +177,9 @@ inline simdjson_result<value> object::at_path(std::string_view json_path) noexce
|
||||
return at_pointer(json_pointer);
|
||||
}
|
||||
|
||||
#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 {
|
||||
inline simdjson_result<std::vector<value>> object::at_path_with_wildcard(std::string_view json_path) noexcept {
|
||||
std::vector<value> result;
|
||||
|
||||
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;
|
||||
@@ -229,22 +189,34 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p
|
||||
for (auto field : *this) {
|
||||
value val;
|
||||
SIMDJSON_TRY(field.value().get(val));
|
||||
|
||||
if (remaining_path.empty()) {
|
||||
callback(val);
|
||||
result.push_back(std::move(val));
|
||||
} else {
|
||||
SIMDJSON_TRY(val.for_each_at_path_with_wildcard(remaining_path, callback));
|
||||
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()));
|
||||
}
|
||||
}
|
||||
return SUCCESS;
|
||||
return result;
|
||||
} else {
|
||||
value val;
|
||||
SIMDJSON_TRY(find_field(key).get(val));
|
||||
|
||||
if (remaining_path.empty()) {
|
||||
callback(val);
|
||||
return SUCCESS;
|
||||
result.push_back(std::move(val));
|
||||
return result;
|
||||
} else {
|
||||
return val.for_each_at_path_with_wildcard(remaining_path, callback);
|
||||
return val.at_path_with_wildcard(remaining_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,7 +334,6 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(key);
|
||||
}
|
||||
|
||||
|
||||
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::at_pointer(std::string_view json_pointer) noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.at_pointer(json_pointer);
|
||||
@@ -376,15 +347,9 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
|
||||
#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 {
|
||||
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 (error()) { return error(); }
|
||||
return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return first.at_path_with_wildcard(json_path);
|
||||
}
|
||||
|
||||
inline simdjson_result<bool> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::reset() noexcept {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/generic/implementation_simdjson_result_base.h"
|
||||
#include "simdjson/generic/ondemand/value_iterator.h"
|
||||
#include "simdjson/generic/ondemand/key_selector.h"
|
||||
#include <vector>
|
||||
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
|
||||
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
|
||||
@@ -16,22 +15,6 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
/**
|
||||
* Result of object::for_each: the first error encountered (SUCCESS if none) and
|
||||
* the number of distinct selector keys that matched during the walk. A
|
||||
* matched_count equal to Selector::size() means every selected key was present
|
||||
* in the object. Implicitly converts to error_code so existing callers that only
|
||||
* care about the error (including SIMDJSON_TRY and the test ASSERT_* macros) keep
|
||||
* working unchanged.
|
||||
*/
|
||||
struct for_each_result {
|
||||
error_code error{SUCCESS};
|
||||
std::size_t matched_count{0};
|
||||
constexpr operator error_code() const noexcept { return error; }
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A forward-only JSON object field iterator.
|
||||
*/
|
||||
@@ -139,41 +122,6 @@ public:
|
||||
/** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) & noexcept; */
|
||||
simdjson_inline simdjson_result<value> operator[](std::string_view key) && noexcept;
|
||||
|
||||
#if SIMDJSON_SUPPORTS_CONCEPTS
|
||||
/**
|
||||
* Walk this object once and invoke on_match(selector_index, value) for each
|
||||
* field whose key is in the compile-time key_selector Selector, in JSON order
|
||||
* (first occurrence of a duplicate key wins). Iteration stops once all
|
||||
* Selector::size() keys have matched or the object ends. The value is consumed
|
||||
* in place, so this is a low-overhead way to extract a known set of fields
|
||||
* regardless of their order in the JSON.
|
||||
*
|
||||
* Usage:
|
||||
* using sel_t = ondemand::key_selector<"id", "text", "user">;
|
||||
* obj.for_each<sel_t>([&](std::size_t i, ondemand::value v) {
|
||||
* switch (i) { case 0: ...; case 1: ...; }
|
||||
* });
|
||||
*
|
||||
* Limitations (see key_selector): each key must be at most 31 characters long,
|
||||
* and the number of keys should be moderate (hard limit 255; a handful is
|
||||
* best, as the compile-time perfect hash may fail or slow compilation for
|
||||
* large key sets). They keys must be distinct, non-empty, and free of backslash, double-quote and
|
||||
* null bytes.
|
||||
*
|
||||
* The callback may return either void or an error_code. When it returns an
|
||||
* error_code, the walk stops at the first non-SUCCESS result and that error is
|
||||
* returned, which lets the callback surface value-parse errors.
|
||||
*
|
||||
* @returns a for_each_result holding the first error encountered while walking
|
||||
* the object (including any error returned by the callback, SUCCESS if
|
||||
* none) and the number of distinct selector keys that matched. The
|
||||
* result converts implicitly to error_code, so callers that only need
|
||||
* the error can ignore the count.
|
||||
*/
|
||||
template <typename Selector, typename Func>
|
||||
simdjson_inline for_each_result for_each(Func&& on_match) noexcept;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the value associated with the given JSON pointer. We use the RFC 6901
|
||||
* https://tools.ietf.org/html/rfc6901 standard, interpreting the current node
|
||||
@@ -224,21 +172,13 @@ public:
|
||||
inline simdjson_result<value> at_path(std::string_view json_path) noexcept;
|
||||
|
||||
/**
|
||||
* Call the provided callback for each value matching the given JSONPath
|
||||
* expression with wildcard support.
|
||||
* Get all values matching the given JSONPath expression with wildcard support.
|
||||
* Supports wildcard patterns like ".*" to match all object fields.
|
||||
*
|
||||
* @param json_path JSONPath expression with wildcards
|
||||
* @param callback Function called for each matching value
|
||||
* @return error_code indicating success or failure
|
||||
* @return Vector of values matching the wildcard pattern
|
||||
*/
|
||||
#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;
|
||||
inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
|
||||
/**
|
||||
* Reset the iterator so that we are pointing back at the
|
||||
@@ -388,13 +328,7 @@ 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;
|
||||
#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::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
inline simdjson_result<bool> reset() noexcept;
|
||||
inline simdjson_result<bool> is_empty() noexcept;
|
||||
inline simdjson_result<size_t> count_fields() & noexcept;
|
||||
|
||||
@@ -27,10 +27,7 @@ simdjson_warn_unused simdjson_inline error_code parser::allocate(size_t new_capa
|
||||
|
||||
// string_capacity copied from document::allocate
|
||||
_capacity = 0;
|
||||
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);
|
||||
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]);
|
||||
@@ -55,7 +52,6 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(p
|
||||
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
|
||||
json.remove_utf8_bom();
|
||||
_document_len = json.length();
|
||||
|
||||
// Allocate if needed
|
||||
if (capacity() < json.length() || !string_buf) {
|
||||
@@ -72,7 +68,6 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate_a
|
||||
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
|
||||
json.remove_utf8_bom();
|
||||
_document_len = json.length();
|
||||
|
||||
// Allocate if needed
|
||||
if (capacity() < json.length() || !string_buf) {
|
||||
@@ -138,34 +133,6 @@ simdjson_warn_unused simdjson_inline simdjson_result<json_iterator> parser::iter
|
||||
return json_iterator(reinterpret_cast<const uint8_t *>(json.data()), this);
|
||||
}
|
||||
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
|
||||
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||
buf += 3;
|
||||
len -= 3;
|
||||
}
|
||||
return document_stream(*this, buf, len, batch_size, false, stream_format::whitespace_delimited);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size) noexcept {
|
||||
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size) noexcept {
|
||||
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
return iterate_many(s.data(), s.length(), batch_size);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size) noexcept {
|
||||
return iterate_many(padded_string_view(s), batch_size);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size) noexcept {
|
||||
return iterate_many(padded_string_view(s), batch_size);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size) noexcept {
|
||||
return iterate_many(pad(s), batch_size);
|
||||
}
|
||||
|
||||
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
|
||||
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
|
||||
// Warning: no check is done on the buffer padding. We trust the user.
|
||||
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||
@@ -173,11 +140,8 @@ inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf,
|
||||
buf += 3;
|
||||
len -= 3;
|
||||
}
|
||||
// Map allow_comma_separated to stream_format::comma_delimited
|
||||
if (allow_comma_separated) {
|
||||
return document_stream(*this, buf, len, batch_size, false, stream_format::comma_delimited);
|
||||
}
|
||||
return document_stream(*this, buf, len, batch_size, false, stream_format::whitespace_delimited);
|
||||
if(allow_comma_separated && batch_size < len) { batch_size = len; }
|
||||
return document_stream(*this, buf, len, batch_size, allow_comma_separated);
|
||||
}
|
||||
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
|
||||
@@ -197,48 +161,6 @@ inline simdjson_result<document_stream> parser::iterate_many(const std::string &
|
||||
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
|
||||
return iterate_many(pad(s), batch_size, allow_comma_separated);
|
||||
}
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
#endif // SIMDJSON_DISABLE_DEPRECATED_API
|
||||
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||
buf += 3;
|
||||
len -= 3;
|
||||
}
|
||||
if (format == stream_format::comma_delimited_array) {
|
||||
// Strip leading JSON whitespace.
|
||||
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
|
||||
buf++; len--;
|
||||
}
|
||||
// Expect the opening '['.
|
||||
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
|
||||
buf++; len--;
|
||||
// Strip trailing JSON whitespace.
|
||||
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
|
||||
len--;
|
||||
}
|
||||
// Expect the closing ']'.
|
||||
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
|
||||
len--;
|
||||
// Fall through to comma_delimited over the array contents.
|
||||
format = stream_format::comma_delimited;
|
||||
}
|
||||
return document_stream(*this, buf, len, batch_size, false, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept {
|
||||
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
return iterate_many(s.data(), s.length(), batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
|
||||
return iterate_many(padded_string_view(s), batch_size, format);
|
||||
}
|
||||
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
|
||||
return iterate_many(padded_string_view(s), batch_size, format);
|
||||
}
|
||||
simdjson_pure simdjson_inline size_t parser::capacity() const noexcept {
|
||||
return _capacity;
|
||||
}
|
||||
@@ -250,7 +172,7 @@ simdjson_pure simdjson_inline size_t parser::max_depth() const noexcept {
|
||||
}
|
||||
|
||||
simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept {
|
||||
if(max_capacity > dom::MINIMAL_DOCUMENT_CAPACITY) {
|
||||
if(max_capacity < dom::MINIMAL_DOCUMENT_CAPACITY) {
|
||||
_max_capacity = max_capacity;
|
||||
} else {
|
||||
_max_capacity = dom::MINIMAL_DOCUMENT_CAPACITY;
|
||||
@@ -277,7 +199,7 @@ simdjson_inline simdjson_warn_unused ondemand::parser& parser::get_parser() {
|
||||
return *parser::get_parser_instance();
|
||||
}
|
||||
|
||||
simdjson_inline bool parser::release_parser() {
|
||||
simdjson_inline bool release_parser() {
|
||||
auto &parser_instance = parser::get_threadlocal_parser_if_exists();
|
||||
if (parser_instance) {
|
||||
parser_instance.reset();
|
||||
@@ -314,4 +236,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::parser>::simd
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
|
||||
@@ -244,66 +244,32 @@ public:
|
||||
* spot is cache-related: small enough to fit in cache, yet big enough to
|
||||
* parse as many documents as possible in one tight loop.
|
||||
* Defaults to 10MB, which has been a reasonable sweet spot in our tests.
|
||||
* @param allow_comma_separated @deprecated Use stream_format::comma_delimited instead.
|
||||
* When true, maps internally to stream_format::comma_delimited.
|
||||
* Defaults to false.
|
||||
* @param allow_comma_separated (defaults on false) This allows a mode where the documents are
|
||||
* separated by commas instead of whitespace. It comes with a performance
|
||||
* penalty because the entire document is indexed at once (and the document must be
|
||||
* less than 4 GB), and there is no multithreading. In this mode, the batch_size parameter
|
||||
* is effectively ignored, as it is set to at least the document size.
|
||||
* @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors:
|
||||
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails
|
||||
* - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity.
|
||||
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
|
||||
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
|
||||
*/
|
||||
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size)
|
||||
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
|
||||
the string might be automatically padded with up to SIMDJSON_PADDING whitespace characters */
|
||||
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||
simdjson_result<document_stream> iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete;
|
||||
|
||||
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
|
||||
/**
|
||||
* @deprecated Use iterate_many with stream_format::comma_delimited instead.
|
||||
*/
|
||||
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||
#endif // SIMDJSON_DISABLE_DEPRECATED_API
|
||||
|
||||
/**
|
||||
* Parse a stream of JSON documents with explicit format specification.
|
||||
*
|
||||
* @param buf The concatenated JSON documents.
|
||||
* @param len The length of the buffer.
|
||||
* @param batch_size The batch size to use.
|
||||
* @param format The stream format (whitespace_delimited, json_sequence, or comma_delimited).
|
||||
* @return A stream of documents, or an error.
|
||||
*/
|
||||
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
|
||||
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
|
||||
|
||||
/** The capacity of this parser (the largest document it can process). */
|
||||
simdjson_pure simdjson_inline size_t capacity() const noexcept;
|
||||
/** The maximum capacity of this parser (the largest document it is allowed to process). */
|
||||
@@ -422,6 +388,8 @@ 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 */
|
||||
@@ -431,7 +399,6 @@ private:
|
||||
size_t _capacity{0};
|
||||
size_t _max_capacity;
|
||||
size_t _max_depth{DEFAULT_MAX_DEPTH};
|
||||
size_t _document_len{0};
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
|
||||
#if SIMDJSON_DEVELOPMENT_CHECKS
|
||||
@@ -458,4 +425,4 @@ public:
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_H
|
||||
@@ -1,138 +0,0 @@
|
||||
#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
|
||||
@@ -1,182 +0,0 @@
|
||||
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||
|
||||
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/array_iterator.h"
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||
#include "simdjson/generic/ondemand/field.h"
|
||||
#include "simdjson/generic/ondemand/value.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#if SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
/**
|
||||
* A ranges-compatible iterator adapter for JSON arrays.
|
||||
*
|
||||
* Wraps array_iterator to satisfy std::input_iterator by providing:
|
||||
* - const operator* (via mutable internal state)
|
||||
* - post-increment operator
|
||||
* - iterator_concept tag
|
||||
*
|
||||
* The mutable approach is standard for single-pass input iterators that
|
||||
* read from external sources (similar to std::istream_iterator).
|
||||
*/
|
||||
class array_range_iterator {
|
||||
public:
|
||||
using iterator_concept = std::input_iterator_tag;
|
||||
using value_type = simdjson_result<value>;
|
||||
using reference = simdjson_result<value>;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
simdjson_inline array_range_iterator() noexcept = default;
|
||||
simdjson_inline explicit array_range_iterator(array_iterator iter) noexcept;
|
||||
|
||||
/**
|
||||
* Get the current element. Const-qualified for std::indirectly_readable;
|
||||
* internally delegates to the mutable wrapped iterator.
|
||||
*/
|
||||
simdjson_inline simdjson_result<value> operator*() const noexcept;
|
||||
simdjson_inline array_range_iterator& operator++() noexcept;
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
simdjson_inline void operator++(int) noexcept;
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
/**
|
||||
* Comparison delegates to array_iterator::operator==, which checks
|
||||
* whether the underlying parser has finished the array (depth-based).
|
||||
*/
|
||||
simdjson_inline friend bool operator==(const array_range_iterator& a,
|
||||
const array_range_iterator& b) noexcept {
|
||||
return a.iter_ == b.iter_;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable array_iterator iter_{};
|
||||
};
|
||||
|
||||
/**
|
||||
* A std::ranges::view over a JSON array.
|
||||
*
|
||||
* Wraps an ondemand::array and exposes begin()/end() that return
|
||||
* array_range_iterator (satisfying std::input_iterator), enabling
|
||||
* use with std::views::transform and other range adaptors.
|
||||
*
|
||||
* If the array's begin() returns an error (only possible under
|
||||
* SIMDJSON_DEVELOPMENT_CHECKS), the range will be empty and error()
|
||||
* will return the error code.
|
||||
*
|
||||
* Usage:
|
||||
* ondemand::parser parser;
|
||||
* auto doc = parser.iterate(json);
|
||||
* auto arr = doc.get_array().value();
|
||||
* for (auto elem : ondemand::get_range(arr)) { ... }
|
||||
*/
|
||||
class array_range {
|
||||
public:
|
||||
simdjson_inline array_range() noexcept = default;
|
||||
simdjson_inline explicit array_range(array& arr) noexcept;
|
||||
|
||||
simdjson_inline array_range_iterator begin() noexcept;
|
||||
simdjson_inline array_range_iterator end() noexcept;
|
||||
|
||||
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
|
||||
simdjson_inline error_code error() const noexcept { return error_; }
|
||||
|
||||
private:
|
||||
array_iterator begin_{};
|
||||
array_iterator end_{};
|
||||
error_code error_{SUCCESS};
|
||||
};
|
||||
|
||||
/**
|
||||
* A ranges-compatible iterator adapter for JSON objects.
|
||||
*
|
||||
* Wraps object_iterator to satisfy std::input_iterator, yielding
|
||||
* simdjson_result<field> elements (key-value pairs).
|
||||
*/
|
||||
class object_range_iterator {
|
||||
public:
|
||||
using iterator_concept = std::input_iterator_tag;
|
||||
using value_type = simdjson_result<field>;
|
||||
using reference = simdjson_result<field>;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
simdjson_inline object_range_iterator() noexcept = default;
|
||||
simdjson_inline explicit object_range_iterator(object_iterator iter) noexcept;
|
||||
|
||||
simdjson_inline simdjson_result<field> operator*() const noexcept;
|
||||
simdjson_inline object_range_iterator& operator++() noexcept;
|
||||
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||
simdjson_inline void operator++(int) noexcept;
|
||||
SIMDJSON_POP_DISABLE_WARNINGS
|
||||
simdjson_inline friend bool operator==(const object_range_iterator& a,
|
||||
const object_range_iterator& b) noexcept {
|
||||
return a.iter_ == b.iter_;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable object_iterator iter_{};
|
||||
};
|
||||
|
||||
/**
|
||||
* A std::ranges::view over a JSON object.
|
||||
*
|
||||
* Wraps an ondemand::object and exposes begin()/end() that return
|
||||
* object_range_iterator, enabling use with range adaptors.
|
||||
*
|
||||
* If the object's begin() returns an error, the range will be empty
|
||||
* and error() will return the error code.
|
||||
*/
|
||||
class object_range {
|
||||
public:
|
||||
simdjson_inline object_range() noexcept = default;
|
||||
simdjson_inline explicit object_range(object& obj) noexcept;
|
||||
|
||||
simdjson_inline object_range_iterator begin() noexcept;
|
||||
simdjson_inline object_range_iterator end() noexcept;
|
||||
|
||||
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
|
||||
simdjson_inline error_code error() const noexcept { return error_; }
|
||||
|
||||
private:
|
||||
object_iterator begin_{};
|
||||
object_iterator end_{};
|
||||
error_code error_{SUCCESS};
|
||||
};
|
||||
|
||||
/** Get a std::ranges compatible view over a JSON array. */
|
||||
simdjson_inline array_range get_range(array& arr) noexcept;
|
||||
|
||||
/** Get a std::ranges compatible view over a JSON object (key-value pairs). */
|
||||
simdjson_inline object_range get_key_value_range(object& obj) noexcept;
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
|
||||
simdjson_inline array_range get_range(simdjson_result<array> result);
|
||||
|
||||
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
|
||||
simdjson_inline object_range get_key_value_range(simdjson_result<object> result);
|
||||
#endif // SIMDJSON_EXCEPTIONS
|
||||
|
||||
} // namespace ondemand
|
||||
} // namespace SIMDJSON_IMPLEMENTATION
|
||||
} // namespace simdjson
|
||||
|
||||
namespace std {
|
||||
namespace ranges {
|
||||
template<>
|
||||
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range> = true;
|
||||
template<>
|
||||
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range> = true;
|
||||
} // namespace ranges
|
||||
} // namespace std
|
||||
|
||||
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||
@@ -6,14 +6,12 @@
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
#include <meta>
|
||||
#include <vector>
|
||||
// #include <static_reflection> // for std::define_static_string - header not available yet
|
||||
#endif
|
||||
|
||||
@@ -269,135 +267,6 @@ constexpr bool user_defined_type = (std::is_class_v<T>
|
||||
!concepts::appendable_containers<T>);
|
||||
|
||||
|
||||
#if defined(SIMDJSON_USE_KEY_SELECTOR_REFLECTION) && SIMDJSON_USE_KEY_SELECTOR_REFLECTION
|
||||
|
||||
// Experimental: deserialize a reflected struct using a compile-time key_selector
|
||||
// and a single object::for_each pass (perfect-hash key matching) instead of one
|
||||
// obj[key] lookup per member. Enable with -DSIMDJSON_USE_KEY_SELECTOR_REFLECTION=1.
|
||||
namespace key_selector_reflection_detail {
|
||||
|
||||
// A member participates if it is public, non-const, and not annotated to skip.
|
||||
consteval bool is_eligible_member(std::meta::info mem) {
|
||||
return !std::meta::is_const(mem) && std::meta::is_public(mem)
|
||||
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty();
|
||||
}
|
||||
|
||||
// JSON key name for `mem`, as a constevalutil::fixed_string usable as an NTTP.
|
||||
template <auto mem>
|
||||
consteval auto member_key_fixed_string() {
|
||||
constexpr std::string_view key{ simdjson::get_json_key_name<mem>() };
|
||||
char buffer[key.size() + 1] = {};
|
||||
for (std::size_t i = 0; i < key.size(); ++i) { buffer[i] = key[i]; }
|
||||
return constevalutil::fixed_string<key.size() + 1>(buffer);
|
||||
}
|
||||
|
||||
// key_selector template arguments (one fixed_string per eligible member), in
|
||||
// declaration order.
|
||||
template <typename T>
|
||||
consteval std::vector<std::meta::info> selector_key_args() {
|
||||
std::vector<std::meta::info> args;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (is_eligible_member(mem)) {
|
||||
args.push_back(std::meta::reflect_constant(member_key_fixed_string<mem>()));
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// key_selector whose keys are exactly T's eligible members (index i <-> i-th).
|
||||
template <typename T>
|
||||
using selector_for = typename [: std::meta::substitute(
|
||||
^^SIMDJSON_IMPLEMENTATION::ondemand::key_selector, selector_key_args<T>()) :];
|
||||
|
||||
// True when none of T's eligible members is an optional type, i.e. every member
|
||||
// is required. In that case presence can be checked with a single match count
|
||||
// instead of a per-member "seen" array.
|
||||
template <typename T>
|
||||
consteval bool all_eligible_members_required() {
|
||||
bool all_required = true;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (is_eligible_member(mem)) {
|
||||
if constexpr (concepts::optional_type<typename [: std::meta::type_of(mem) :]>) {
|
||||
all_required = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_required;
|
||||
}
|
||||
|
||||
} // namespace key_selector_reflection_detail
|
||||
|
||||
template <typename T, typename ValT>
|
||||
requires(user_defined_type<T> && std::is_class_v<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
SIMDJSON_IMPLEMENTATION::ondemand::object obj;
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<ValT>, SIMDJSON_IMPLEMENTATION::ondemand::object>) {
|
||||
obj = val;
|
||||
} else {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
using selector = key_selector_reflection_detail::selector_for<T>;
|
||||
if constexpr (key_selector_reflection_detail::all_eligible_members_required<T>()) {
|
||||
// Fast path: every member is required. A single for_each pass parses each
|
||||
// matched field; the returned match count then tells us whether every member
|
||||
// was present (matched_count == selector::size()) without a per-member "seen"
|
||||
// array. A value-parse error (e.g. a type mismatch) is propagated by for_each.
|
||||
auto walk = obj.template for_each<selector>(
|
||||
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
|
||||
std::size_t counter = 0;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if (matched_index == counter) { return field_value.get(out.[:mem:]); }
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
return SUCCESS;
|
||||
});
|
||||
if (walk.error) { return walk.error; }
|
||||
// A missing required member shows up as a short match count and is reported as
|
||||
// NO_SUCH_FIELD, mirroring the ordered obj[key] path.
|
||||
if (walk.matched_count != selector::size()) { return NO_SUCH_FIELD; }
|
||||
return SUCCESS;
|
||||
} else {
|
||||
std::array<bool, selector::size()> seen_member{};
|
||||
// Single pass over the object: each field whose key matches a member yields its
|
||||
// selector index, which we map back to the corresponding member. The callback
|
||||
// returns an error_code so that a value-parse error (e.g. a type mismatch on a
|
||||
// matched field) is propagated by for_each instead of being silently dropped.
|
||||
error_code walk_error = obj.template for_each<selector>(
|
||||
[&](std::size_t matched_index, SIMDJSON_IMPLEMENTATION::ondemand::value field_value) -> error_code {
|
||||
std::size_t counter = 0;
|
||||
error_code field_error = SUCCESS;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if (matched_index == counter) {
|
||||
seen_member[counter] = true;
|
||||
field_error = field_value.get(out.[:mem:]);
|
||||
}
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
return field_error;
|
||||
});
|
||||
if (walk_error) { return walk_error; }
|
||||
// Required (non-optional) members must be present: a missing one is reported as
|
||||
// NO_SUCH_FIELD, mirroring the ordered obj[key] path. Optional members may be
|
||||
// absent.
|
||||
std::size_t check_counter = 0;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (key_selector_reflection_detail::is_eligible_member(mem)) {
|
||||
if constexpr (!concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
if (!seen_member[check_counter]) { return NO_SUCH_FIELD; }
|
||||
}
|
||||
++check_counter;
|
||||
}
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template <typename T, typename ValT>
|
||||
requires(user_defined_type<T> && std::is_class_v<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
@@ -408,9 +277,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)
|
||||
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr std::string_view key = simdjson::get_json_key_name<mem>();
|
||||
if constexpr (!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:]);
|
||||
@@ -430,8 +298,6 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
return simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
#endif // SIMDJSON_USE_KEY_SELECTOR_REFLECTION
|
||||
|
||||
// Support for enum deserialization - deserialize from string representation using expand approach from P2996R12
|
||||
template <typename T, typename ValT>
|
||||
requires(std::is_enum_v<T>)
|
||||
@@ -439,7 +305,7 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string_view str;
|
||||
SIMDJSON_TRY(val.get_string().get(str));
|
||||
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
|
||||
constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
|
||||
template for (constexpr auto enum_val : enumerators) {
|
||||
if (str == std::meta::identifier_of(enum_val)) {
|
||||
out = [:enum_val:];
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include "simdjson/generic/ondemand/value.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
@@ -74,18 +72,6 @@ simdjson_inline simdjson_result<int64_t> value::get_int64() noexcept {
|
||||
simdjson_inline simdjson_result<int64_t> value::get_int64_in_string() noexcept {
|
||||
return iter.get_int64_in_string();
|
||||
}
|
||||
simdjson_inline simdjson_result<uint32_t> value::get_uint32() noexcept {
|
||||
uint64_t result;
|
||||
SIMDJSON_TRY(get_uint64().get(result));
|
||||
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
|
||||
return static_cast<uint32_t>(result);
|
||||
}
|
||||
simdjson_inline simdjson_result<int32_t> value::get_int32() noexcept {
|
||||
int64_t result;
|
||||
SIMDJSON_TRY(get_int64().get(result));
|
||||
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
|
||||
return static_cast<int32_t>(result);
|
||||
}
|
||||
simdjson_inline simdjson_result<bool> value::get_bool() noexcept {
|
||||
return iter.get_bool();
|
||||
}
|
||||
@@ -101,8 +87,6 @@ template<> simdjson_inline simdjson_result<number> value::get() noexcept { retur
|
||||
template<> simdjson_inline simdjson_result<double> value::get() noexcept { return get_double(); }
|
||||
template<> simdjson_inline simdjson_result<uint64_t> value::get() noexcept { return get_uint64(); }
|
||||
template<> simdjson_inline simdjson_result<int64_t> value::get() noexcept { return get_int64(); }
|
||||
template<> simdjson_inline simdjson_result<uint32_t> value::get() noexcept { return get_uint32(); }
|
||||
template<> simdjson_inline simdjson_result<int32_t> value::get() noexcept { return get_int32(); }
|
||||
template<> simdjson_inline simdjson_result<bool> value::get() noexcept { return get_bool(); }
|
||||
|
||||
|
||||
@@ -114,8 +98,6 @@ template<> simdjson_warn_unused simdjson_inline error_code value::get(number& ou
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(double& out) noexcept { return get_double().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(uint64_t& out) noexcept { return get_uint64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(int64_t& out) noexcept { return get_int64().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(uint32_t& out) noexcept { return get_uint32().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(int32_t& out) noexcept { return get_int32().get(out); }
|
||||
template<> simdjson_warn_unused simdjson_inline error_code value::get(bool& out) noexcept { return get_bool().get(out); }
|
||||
|
||||
#if SIMDJSON_EXCEPTIONS
|
||||
@@ -314,20 +296,14 @@ simdjson_inline simdjson_result<value> value::at_path(std::string_view json_path
|
||||
}
|
||||
}
|
||||
|
||||
#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 {
|
||||
inline simdjson_result<std::vector<value>> value::at_path_with_wildcard(std::string_view json_path) noexcept {
|
||||
json_type t;
|
||||
SIMDJSON_TRY(type().get(t));
|
||||
switch (t) {
|
||||
case json_type::array:
|
||||
return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return (*this).get_array().at_path_with_wildcard(json_path);
|
||||
case json_type::object:
|
||||
return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return (*this).get_object().at_path_with_wildcard(json_path);
|
||||
default:
|
||||
return INVALID_JSON_POINTER;
|
||||
}
|
||||
@@ -425,14 +401,6 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
|
||||
if (error()) { return error(); }
|
||||
return first.get_int64_in_string();
|
||||
}
|
||||
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get_uint32() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_uint32();
|
||||
}
|
||||
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get_int32() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_int32();
|
||||
}
|
||||
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get_double() noexcept {
|
||||
if (error()) { return error(); }
|
||||
return first.get_double();
|
||||
@@ -591,18 +559,12 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
|
||||
return first.at_path(json_path);
|
||||
}
|
||||
|
||||
#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 {
|
||||
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 (error()) {
|
||||
return error();
|
||||
}
|
||||
return first.for_each_at_path_with_wildcard(json_path, std::forward<Func>(callback));
|
||||
return first.at_path_with_wildcard(json_path);
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
@@ -161,28 +161,6 @@ public:
|
||||
*/
|
||||
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
|
||||
|
||||
/**
|
||||
* Cast this JSON value to a 32-bit unsigned integer.
|
||||
*
|
||||
* Calls get_uint64() and checks that the result fits in a uint32_t.
|
||||
*
|
||||
* @returns A 32-bit unsigned integer.
|
||||
* @returns INCORRECT_TYPE If the JSON value is not an unsigned integer.
|
||||
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in a uint32_t.
|
||||
*/
|
||||
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
|
||||
|
||||
/**
|
||||
* Cast this JSON value to a 32-bit signed integer.
|
||||
*
|
||||
* Calls get_int64() and checks that the result fits in an int32_t.
|
||||
*
|
||||
* @returns A 32-bit signed integer.
|
||||
* @returns INCORRECT_TYPE If the JSON value is not an integer.
|
||||
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in an int32_t.
|
||||
*/
|
||||
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
|
||||
|
||||
/**
|
||||
* Cast this JSON value to a double.
|
||||
*
|
||||
@@ -698,21 +676,13 @@ public:
|
||||
simdjson_inline simdjson_result<value> at_path(std::string_view at_path) noexcept;
|
||||
|
||||
/**
|
||||
* Call the provided callback for each value matching the given JSONPath
|
||||
* expression with wildcard support.
|
||||
* Get all values matching the given JSONPath expression with wildcard support.
|
||||
* Supports wildcard character (*) for arrays or ".*" for objects.
|
||||
*
|
||||
* @param json_path JSONPath expression with wildcards
|
||||
* @param callback Function called for each matching value
|
||||
* @return error_code indicating success or failure
|
||||
* @return Vector of values matching the wildcard pattern
|
||||
*/
|
||||
#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;
|
||||
simdjson_inline simdjson_result<std::vector<value>> at_path_with_wildcard(std::string_view json_path) noexcept;
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -776,8 +746,6 @@ public:
|
||||
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
|
||||
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
|
||||
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double() noexcept;
|
||||
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
|
||||
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
|
||||
@@ -905,23 +873,9 @@ 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;
|
||||
#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::vector<SIMDJSON_IMPLEMENTATION::ondemand::value>> at_path_with_wildcard(std::string_view json_path) 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user