Compare commits

..

8 Commits

Author SHA1 Message Date
Daniel Lemire 65e59ccf9b patch 4.6.3 2026-04-20 12:07:05 -04:00
Daniel Lemire 61641f9a7a version bump. 2026-04-17 17:22:06 -04:00
Daniel Lemire 996dc25180 adding forward declaration 2026-04-17 11:03:09 -04:00
Daniel Lemire 77cf997595 moving example 2026-04-16 22:07:59 -04:00
Daniel Lemire 453ae4e955 more fixes 2026-04-16 18:48:37 -04:00
Daniel Lemire f5602fa3c7 update 2026-04-16 18:39:43 -04:00
Daniel Lemire 942f7f298b simplifying. 2026-04-16 18:39:43 -04:00
Daniel Lemire 6a4fc260a1 fixing issue 2684 2026-04-16 18:39:30 -04:00
177 changed files with 5062 additions and 11021 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux syste
* We do not support unreleased or experimental compilers. If you encounter an issue with a * We do not support unreleased or experimental compilers. If you encounter an issue with a
pre-release version of a compiler, do not report it as a bug to simdjson. However, we always pre-release version of a compiler, do not report it as a bug to simdjson. However, we always
invite contributions either in the form an analysis or of a code contribution. invite contributions either in the form an analysis or of a code contribution.
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support. As of 2026, Windows 10 is no longer supported. * Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support.
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412). Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: uraimo/run-on-arch-action@v3 - uses: uraimo/run-on-arch-action@v3
name: Test name: Test
id: runcmd id: runcmd
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
image: debian:testing image: debian:testing
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: | run: |
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install Doxygen - name: Install Doxygen
run: sudo apt-get install doxygen graphviz -y run: sudo apt-get install doxygen graphviz -y
- run: mkdir docs - run: mkdir docs
+2 -2
View File
@@ -4,13 +4,13 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 - uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14
- name: Verify - name: Verify
run: emcc -v run: emcc -v
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.6.0
- name: Configure - name: Configure
run: emcmake cmake -B build run: emcmake cmake -B build
- name: Build # We build but do not test - name: Build # We build but do not test
@@ -6,7 +6,7 @@ jobs:
whitespace: whitespace:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Remove whitespace and check the diff - name: Remove whitespace and check the diff
run: | run: |
set -eu set -eu
+3 -3
View File
@@ -38,14 +38,14 @@ jobs:
chmod +x llvm.sh chmod +x llvm.sh
sudo ./llvm.sh $CLANGVERSION sudo ./llvm.sh $CLANGVERSION
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
- uses: actions/cache@v5 - uses: actions/cache@v4
id: cache-corpus id: cache-corpus
with: with:
path: out/ path: out/
-26
View File
@@ -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)
+3 -3
View File
@@ -11,13 +11,13 @@ jobs:
platform: platform:
- { toolchain-version: 2023.08.08 } - { toolchain-version: 2023.08.08 }
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install build requirements - name: Install build requirements
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y --no-install-recommends cmake sudo apt-get install -y --no-install-recommends cmake
- uses: actions/cache/restore@v5 - uses: actions/cache/restore@v4
id: restore-cache id: restore-cache
with: with:
path: /opt/cross-tools path: /opt/cross-tools
@@ -33,7 +33,7 @@ jobs:
mkdir -p /opt mkdir -p /opt
tar -C /opt -x -f /tmp/toolchain.tar.xz tar -C /opt -x -f /tmp/toolchain.tar.xz
- uses: actions/cache/save@v5 - uses: actions/cache/save@v3
if: ${{ !steps.restore-cache.outputs.cache-hit }} if: ${{ !steps.restore-cache.outputs.cache-hit }}
with: with:
path: /opt/cross-tools path: /opt/cross-tools
+2 -13
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
@@ -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 && echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
cd ../tests/installation_tests/find && cd ../tests/installation_tests/find &&
mkdir buildshared && cd buildshared && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../buildshared/destination .. && cmake --build . mkdir buildshared && cd buildshared && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../buildshared/destination .. && cmake --build .
- name: Use cmake (parsing for NaN/Infinity enabled)
run: |
mkdir build_nan_inf &&
cd build_nan_inf &&
cmake -DSIMDJSON_ENABLE_NAN_INF=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j &&
cmake --install . &&
echo -e '#include <simdjson.h>\nint main(int argc,char**argv) {simdjson::dom::parser parser;simdjson::dom::element tweets = parser.load(argv[1]); }' > tmp.cpp && c++ -Idestination/include -Ldestination/lib -std=c++17 -Wl,-rpath,destination/lib -o linkandrun tmp.cpp -lsimdjson && ./linkandrun jsonexamples/twitter.json &&
cd ../tests/installation_tests/find &&
mkdir build_nan_inf && cd build_nan_inf && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build_nan_inf/destination .. && cmake --build .
+2 -2
View File
@@ -27,8 +27,8 @@ jobs:
CMAKE_GENERATOR: Ninja CMAKE_GENERATOR: Ninja
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -29,8 +29,8 @@ jobs:
CMAKE_GENERATOR: Ninja CMAKE_GENERATOR: Ninja
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: uraimo/run-on-arch-action@v3 - uses: uraimo/run-on-arch-action@v3
name: Test name: Test
id: runcmd id: runcmd
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: uraimo/run-on-arch-action@v3 - uses: uraimo/run-on-arch-action@v3
name: Test name: Test
id: runcmd id: runcmd
@@ -26,4 +26,4 @@ jobs:
run: | run: |
cmake -DCMAKE_BUILD_TYPE=Release -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build cmake -DCMAKE_BUILD_TYPE=Release -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
cmake --build build -j=2 cmake --build build -j=2
ctest --output-on-failure --test-dir build -E ondemand_cacheline ctest --output-on-failure --test-dir build
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install packages - name: Install packages
run: | run: |
sudo apt-get update -q -y sudo apt-get update -q -y
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install packages - name: Install packages
run: | run: |
sudo apt-get update -q -y sudo apt-get update -q -y
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install packages - name: Install packages
run: | run: |
sudo apt-get update -q -y sudo apt-get update -q -y
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install packages - name: Install packages
run: | run: |
sudo apt-get update -q -y sudo apt-get update -q -y
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Install packages - name: Install packages
run: | run: |
sudo apt-get update -q -y sudo apt-get update -q -y
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: uraimo/run-on-arch-action@v3 - uses: uraimo/run-on-arch-action@v3
name: Test name: Test
id: runcmd id: runcmd
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
@@ -8,8 +8,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -15,8 +15,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
-27
View File
@@ -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@v6
- uses: actions/cache@v5
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
@@ -11,7 +11,7 @@ jobs:
matrix: matrix:
cxx: [g++-13, clang++-16] cxx: [g++-13, clang++-16]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Prepare - name: Prepare
run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_EXCEPTIONS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -B build run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_EXCEPTIONS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -B build
env: env:
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
matrix: matrix:
cxx: [g++-13, clang++-16] cxx: [g++-13, clang++-16]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Prepare - name: Prepare
run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_DEVELOPER_MODE=ON -B build run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_DEVELOPER_MODE=ON -B build
env: env:
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+4 -18
View File
@@ -9,8 +9,8 @@ jobs:
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
@@ -21,21 +21,14 @@ jobs:
cmake -DSIMDJSON_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. && cmake -DSIMDJSON_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. &&
cmake --build . && cmake --build . &&
ctest --output-on-failure -LE explicitonly -j ctest --output-on-failure -LE explicitonly -j
- name: Use cmake with address sanitizer (Parsing of NaN/Infinity enabled)
run: |
mkdir builddebug_nan_inf &&
cd builddebug_nan_inf &&
cmake -DSIMDJSON_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -DSIMDJSON_ENABLE_NAN_INF=ON .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j
ubuntu-build-undefined-sanitizer: ubuntu-build-undefined-sanitizer:
if: >- if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') && ! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]') ! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
@@ -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 -DSIMDJSON_SANITIZE_UNDEFINED=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF .. &&
cmake --build . && cmake --build . &&
ctest --output-on-failure -LE explicitonly -j ctest --output-on-failure -LE explicitonly -j
- name: Use cmake with undefined sanitizer (Parsing of NaN/Infinity enabled)
run: |
mkdir builddebugundefsani_nan_inf &&
cd builddebugundefsani_nan_inf &&
cmake -DSIMDJSON_SANITIZE_UNDEFINED=ON -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -DBUILD_SHARED_LIBS=OFF -DSIMDJSON_ENABLE_NAN_INF=ON .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j
+2 -3
View File
@@ -12,12 +12,11 @@ jobs:
shared: [ON, OFF] shared: [ON, OFF]
cxx: [g++-13, clang++-16] cxx: [g++-13, clang++-16]
sanitizer: [ON, OFF] sanitizer: [ON, OFF]
nan_inf: [ON, OFF]
build_type: [RelWithDebInfo, Debug, Release] build_type: [RelWithDebInfo, Debug, Release]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Prepare - name: Prepare
run: cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_SANITIZE=${{matrix.sanitizer}} -DBUILD_SHARED_LIBS=${{matrix.shared}} -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: env:
CXX: ${{matrix.cxx}} CXX: ${{matrix.cxx}}
- name: Build - name: Build
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- {arch: ARM64EC} - {arch: ARM64EC}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Use cmake - name: Use cmake
run: | run: |
cmake -A ${{ matrix.arch }} -DCMAKE_SYSTEM_VERSION="10.0.22621.0" -DCMAKE_CROSSCOMPILING=1 -DSIMDJSON_DEVELOPER_MODE=ON -D SIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_EXCEPTIONS=OFF -B build && cmake -A ${{ matrix.arch }} -DCMAKE_SYSTEM_VERSION="10.0.22621.0" -DCMAKE_CROSSCOMPILING=1 -DSIMDJSON_DEVELOPER_MODE=ON -D SIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_EXCEPTIONS=OFF -B build &&
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF} - {gen: Visual Studio 17 2022, arch: x64, shared: OFF}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Configure - name: Configure
run: | run: |
cmake -DSIMDJSON_CXX_STANDARD=20 -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build cmake -DSIMDJSON_CXX_STANDARD=20 -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo} - {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Configure - name: Configure
run: | run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSANITIZE=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSANITIZE=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
+8 -10
View File
@@ -13,20 +13,18 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: 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: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF} - {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release}
- {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: ON, build_type: Release}
- {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: Debug}
- {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: Release}
- {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: RelWithDebInfo}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: ON}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: ON}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Configure - name: Configure
run: | run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -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 - name: Build Debug
run: cmake --build build --config ${{matrix.build_type}} --verbose run: cmake --build build --config ${{matrix.build_type}} --verbose
- name: Run tests - name: Run tests
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- {gen: Visual Studio 17 2022, arch: x64, build_type: RelWithDebInfo} - {gen: Visual Studio 17 2022, arch: x64, build_type: RelWithDebInfo}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Configure - name: Configure
run: | run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- {gen: Visual Studio 17 2022, arch: x64, build_type: RelWithDebInfo} - {gen: Visual Studio 17 2022, arch: x64, build_type: RelWithDebInfo}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Configure - name: Configure
run: | run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
+2 -2
View File
@@ -7,8 +7,8 @@ jobs:
name: windows-vs17 name: windows-vs17
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: actions/cache@v5 - uses: actions/cache@v4
with: with:
path: dependencies/.cache path: dependencies/.cache
key: ${{ hashFiles('dependencies/CMakeLists.txt') }} key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
+8 -80
View File
@@ -12,7 +12,7 @@ endif()
project( project(
simdjson simdjson
# The version number is modified by tools/release.py # The version number is modified by tools/release.py
VERSION 4.6.1 VERSION 4.6.3
DESCRIPTION "Parsing gigabytes of JSON per second" DESCRIPTION "Parsing gigabytes of JSON per second"
HOMEPAGE_URL "https://simdjson.org/" HOMEPAGE_URL "https://simdjson.org/"
LANGUAGES CXX C LANGUAGES CXX C
@@ -75,41 +75,6 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
) )
endif() endif()
# padded_memory_map is always available on POSIX. On Windows it is disabled
# by default because it depends on the `CreateFileMapping2` / `MapViewOfFile3`
# APIs, which require Windows 10 version 1803 or later and are exported via
# onecore.lib rather than the default kernel32.lib. Turn this option ON to
# opt into the feature on Windows; simdjson will then set the appropriate
# Windows version macros and link onecore, so everything that links
# simdjson picks up both the compile-time declarations and the import
# library automatically. The option is a no-op on POSIX (where the feature
# is unconditionally enabled).
option(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
"Enable simdjson::padded_memory_map on Windows (requires Windows 10 \
version 1803 or later). Always enabled on POSIX." OFF)
if(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS)
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1
)
if(WIN32)
# Raise the Windows version floor so that <windows.h> declares the
# modern memory-mapping APIs, and link the import library that
# actually exports them. _WIN32_WINNT / WINVER / NTDDI_VERSION together
# tell <sdkddkver.h> which APIs to light up.
simdjson_add_props(
target_compile_definitions PUBLIC
_WIN32_WINNT=0x0A00
WINVER=0x0A00
NTDDI_VERSION=0x0A000006 # NTDDI_WIN10_RS5, Windows 10 version 1809
)
simdjson_add_props(
target_link_libraries PUBLIC
onecore
)
endif()
endif()
if(is_top_project) if(is_top_project)
option(SIMDJSON_INSTALL "Enable target install" ON) option(SIMDJSON_INSTALL "Enable target install" ON)
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF) option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
@@ -202,37 +167,13 @@ if(MSVC)
endif() endif()
endif() endif()
if(SIMDJSON_STATIC_REFLECTION) if(SIMDJSON_STATIC_REFLECTION)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION MATCHES "^21")
execute_process(
COMMAND ${CMAKE_CXX_COMPILER} --version
OUTPUT_VARIABLE CLANG_VERSION_OUTPUT
ERROR_VARIABLE CLANG_VERSION_ERROR
RESULT_VARIABLE CLANG_VERSION_RESULT
)
if(CLANG_VERSION_RESULT EQUAL 0 AND CLANG_VERSION_OUTPUT MATCHES "https://github.com/bloomberg/clang-p2996.git")
set(IS_BLOOMBERG_P2996_CLANG ON)
message(STATUS "Using Bloomberg P2996 Clang fork")
endif()
endif()
# We would like to require C++26, but no compiler supports that! # We would like to require C++26, but no compiler supports that!
# This is a hack: # This is a hack:
if(IS_BLOOMBERG_P2996_CLANG)
simdjson_add_props( simdjson_add_props(
target_compile_options PUBLIC target_compile_options PUBLIC
-freflection -fexpansion-statements -stdlib=libc++ -std=c++26 -freflection -fexpansion-statements -stdlib=libc++ -std=c++26
) )
else()
simdjson_add_props(
target_compile_options PUBLIC
-freflection -std=c++26
)
endif()
else() else()
simdjson_add_props(target_compile_features PUBLIC cxx_std_11) simdjson_add_props(target_compile_features PUBLIC cxx_std_11)
endif() endif()
@@ -254,12 +195,6 @@ if(SIMDJSON_MINUS_ZERO_AS_FLOAT)
simdjson_add_props(target_compile_definitions PRIVATE SIMDJSON_MINUS_ZERO_AS_FLOAT=1) simdjson_add_props(target_compile_definitions PRIVATE SIMDJSON_MINUS_ZERO_AS_FLOAT=1)
endif(SIMDJSON_MINUS_ZERO_AS_FLOAT) endif(SIMDJSON_MINUS_ZERO_AS_FLOAT)
option(SIMDJSON_ENABLE_NAN_INF "Allow parsing of NaN and Infinity JSON values" OFF)
if(SIMDJSON_ENABLE_NAN_INF)
message(STATUS "simdjson NaN and Infinity parsing is enabled.")
simdjson_add_props(target_compile_definitions PUBLIC SIMDJSON_ENABLE_NAN_INF=1)
endif()
# GCC and Clang have horrendous Debug builds when using SIMD. # GCC and Clang have horrendous Debug builds when using SIMD.
# A common fix is to use '-Og' instead. # A common fix is to use '-Og' instead.
# bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412 # bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412
@@ -452,25 +387,18 @@ add_subdirectory(fuzz)
# #
# Source files should be just ASCII # Source files should be just ASCII
# #
find_program(FIND_CMD find) find_program(FIND find)
find_program(FILE_CMD file) find_program(FILE file)
find_program(GREP_CMD grep) find_program(GREP grep)
if(FIND_CMD AND FILE_CMD AND GREP_CMD) if(FIND AND FILE AND GREP)
add_test( add_test(
NAME just_ascii NAME just_ascii
COMMAND sh -c "\ COMMAND sh -c "\
non_ascii=$(${FIND_CMD} include src windows tools singleheader tests examples benchmark \ ${FIND} include src windows tools singleheader tests examples benchmark \
-path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \ -path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \
-type f -exec ${FILE_CMD} '{}' \; | ${GREP_CMD} -v ASCII); \ -type f -exec ${FILE} '{}' \; | ${GREP} -qv ASCII || exit 0 && exit 1"
if [ -n \"$non_ascii\" ]; then \
echo 'The following files contain non-ASCII characters:'; \
echo \"$non_ascii\"; \
exit 1; \
fi"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
) )
else()
message(WARNING "just_ascii test disabled because required tools were not found: find='${FIND_CMD}', file='${FILE_CMD}', grep='${GREP_CMD}'")
endif() endif()
## ##
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = "4.6.1" PROJECT_NUMBER = "4.6.3"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
+4 -5
View File
@@ -190,7 +190,6 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings. - [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port. - [simdjson-java](https://github.com/simdjson/simdjson-java): Java port.
- [mruby-fast-json](https://github.com/Asmod4n/mruby-fast-json): mruby binding with high API coverage. - [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 About simdjson
-------------- --------------
@@ -201,7 +200,7 @@ CPU's multiple execution cores.
Our default front-end is called On-Demand, and we wrote a paper about it: 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 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: and implementation of simdjson is in our research article:
@@ -214,12 +213,12 @@ We have an in-depth paper focused on the UTF-8 validation:
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/). 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 /> For the video inclined, we had a talk at QCon San Francisco 2019<br />
[![simdjson at QCon San Francisco 2019](https://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](https://www.youtube.com/watch?v=wlvKAT7SZIQ)<br /> [![simdjson at QCon San Francisco 2019](http://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
(It was the best voted talk, we're kinda proud of it.) (It was the best voted talk, we're kinda proud of it.)
We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether youre a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk! We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether youre a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk!
[![simdjson at CppCon 2025](https://img.youtube.com/vi/Mcgk3CxHYMs/0.jpg)](https://www.youtube.com/watch?v=Mcgk3CxHYMs)<br /> [![simdjson at CppCon 2025](http://img.youtube.com/vi/Mcgk3CxHYMs/0.jpg)](http://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
@@ -272,7 +271,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. 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. For efficient number serialization, we bundle Florian Loitsch's implementation of the Grisu2 algorithm for binary to decimal floating-point numbers. The implementation was slightly modified by JSON for Modern C++ library. Both Florian Loitsch's implementation and JSON for Modern C++ are provided under the MIT license.
+1 -3
View File
@@ -1,10 +1,9 @@
add_subdirectory(dom) add_subdirectory(dom)
include_directories( . ) include_directories( . linux )
link_libraries(simdjson-windows-headers test-data) link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson) link_libraries(simdjson)
link_libraries(counters)
if(SIMDJSON_STATIC_REFLECTION) if(SIMDJSON_STATIC_REFLECTION)
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1) add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
endif(SIMDJSON_STATIC_REFLECTION) endif(SIMDJSON_STATIC_REFLECTION)
@@ -16,7 +15,6 @@ if (TARGET benchmark::benchmark)
link_libraries(benchmark::benchmark) link_libraries(benchmark::benchmark)
add_executable(bench_parse_call bench_parse_call.cpp) add_executable(bench_parse_call bench_parse_call.cpp)
add_executable(bench_dom_api bench_dom_api.cpp) add_executable(bench_dom_api bench_dom_api.cpp)
add_executable(bench_stream_formats bench_stream_formats.cpp)
if(SIMDJSON_EXCEPTIONS) if(SIMDJSON_EXCEPTIONS)
add_executable(bench_ondemand bench_ondemand.cpp) add_executable(bench_ondemand bench_ondemand.cpp)
if(TARGET yyjson) if(TARGET yyjson)
File diff suppressed because it is too large Load Diff
-1
View File
@@ -124,7 +124,6 @@ SIMDJSON_POP_DISABLE_WARNINGS
#include "kostya/boostjson.h" #include "kostya/boostjson.h"
#include "large_random/simdjson_ondemand.h" #include "large_random/simdjson_ondemand.h"
#include "large_random/simdjson_ondemand_ranges.h"
#if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED #if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
#include "large_random/simdjson_ondemand_unordered.h" #include "large_random/simdjson_ondemand_unordered.h"
#endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED #endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
-216
View File
@@ -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();
+39 -2
View File
@@ -1,5 +1,4 @@
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
@@ -26,6 +25,7 @@ using namespace counters;
#include <string> #include <string>
#include <vector> #include <vector>
#include "linux-perf-events.h"
#ifdef __linux__ #ifdef __linux__
#include <libgen.h> #include <libgen.h>
#endif #endif
@@ -204,8 +204,12 @@ struct feature_benchmarker {
} }
// Rate of 1-7-structural misses per 8-structural flip // Rate of 1-7-structural misses per 8-structural flip
double struct1_7_miss_rate(BenchmarkStage stage) const { double struct1_7_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped); return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped);
#endif
} }
// Extra cost of an 8-15 structural block over a 1-7 structural block // Extra cost of an 8-15 structural block over a 1-7 structural block
double struct8_15_cost(BenchmarkStage stage) const { double struct8_15_cost(BenchmarkStage stage) const {
@@ -217,8 +221,12 @@ struct feature_benchmarker {
} }
// Rate of 8-15-structural misses per 8-structural flip // Rate of 8-15-structural misses per 8-structural flip
double struct8_15_miss_rate(BenchmarkStage stage) const { double struct8_15_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped); return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped);
#endif
} }
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!) // Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
@@ -231,8 +239,12 @@ struct feature_benchmarker {
} }
// Rate of 16-structural misses per 16-structural flip // Rate of 16-structural misses per 16-structural flip
double struct16_miss_rate(BenchmarkStage stage) const { double struct16_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped); return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped);
#endif
} }
@@ -246,8 +258,12 @@ struct feature_benchmarker {
} }
// Rate of UTF-8 misses per UTF-8 flip // Rate of UTF-8 misses per UTF-8 flip
double utf8_miss_rate(BenchmarkStage stage) const { double utf8_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped); return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped);
#endif
} }
// Extra cost of having escapes in a block // Extra cost of having escapes in a block
double escape_cost(BenchmarkStage stage) const { double escape_cost(BenchmarkStage stage) const {
@@ -259,8 +275,12 @@ struct feature_benchmarker {
} }
// Rate of escape misses per escape flip // Rate of escape misses per escape flip
double escape_miss_rate(BenchmarkStage stage) const { double escape_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; } if (!has_events()) { return 1; }
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped); return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped);
#endif
} }
@@ -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) { void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks); double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results); double calc = features.calc_expected(stage, results);
@@ -381,6 +417,7 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
} }
printf("|\n"); printf("|\n");
} }
#endif
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Read options // Read options
+1 -2
View File
@@ -1,8 +1,7 @@
#ifndef _BENCHMARK_H_ #ifndef _BENCHMARK_H_
#define _BENCHMARK_H_ #define _BENCHMARK_H_
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
/* /*
* Prints the best number of operations per cycle where * Prints the best number of operations per cycle where
+11 -4
View File
@@ -1,8 +1,7 @@
#ifndef __BENCHMARKER_H #ifndef __BENCHMARKER_H
#define __BENCHMARKER_H #define __BENCHMARKER_H
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include "simdjson.h" #include "simdjson.h"
#include <cassert> #include <cassert>
@@ -29,9 +28,11 @@ using namespace counters;
#include <string> #include <string>
#include <vector> #include <vector>
#include "linux-perf-events.h"
#ifdef __linux__ #ifdef __linux__
#include <libgen.h> #include <libgen.h>
#endif #endif
#include "simdjson.h"
#include <functional> #include <functional>
@@ -422,12 +423,18 @@ struct benchmarker {
stage.instructions() / static_cast<double>(stats->structurals), stage.instructions() / static_cast<double>(stats->structurals),
stage.instructions() / static_cast<double>(stage.cycles()) stage.instructions() / static_cast<double>(stage.cycles())
); );
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, prefix,
"Misses", "Misses",
stage.branch_misses(), stage.branch_misses(),
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()) percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
stage.cache_misses(),
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
stage.cache_references()
); );
#endif
} }
} }
@@ -1,5 +1,4 @@
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include <random> #include <random>
#include <vector> #include <vector>
+2 -3
View File
@@ -1,12 +1,11 @@
include_directories( .. ) include_directories( .. ../linux )
link_libraries(simdjson-windows-headers test-data) link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson) link_libraries(simdjson)
link_libraries(counters)
add_executable(perfdiff perfdiff.cpp) add_executable(perfdiff perfdiff.cpp)
add_executable(parse parse.cpp) add_executable(parse parse.cpp)
add_executable(parse_stream parse_stream.cpp) add_executable(parse_stream parse_stream.cpp)
add_executable(statisticalmodel statisticalmodel.cpp)
add_executable(parse_noutf8validation parse.cpp) add_executable(parse_noutf8validation parse.cpp)
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION) target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
+2 -2
View File
@@ -1,5 +1,4 @@
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
@@ -25,6 +24,7 @@ using namespace counters;
#include <string> #include <string>
#include <vector> #include <vector>
#include "linux-perf-events.h"
#ifdef __linux__ #ifdef __linux__
#include <libgen.h> #include <libgen.h>
#endif #endif
+207
View File
@@ -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;
}
+201
View File
@@ -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 -2
View File
@@ -1,8 +1,7 @@
#ifndef BENCHMARK_HELPERS_H #ifndef BENCHMARK_HELPERS_H
#define BENCHMARK_HELPERS_H #define BENCHMARK_HELPERS_H
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include <atomic> #include <atomic>
event_collector collector; event_collector collector;
+13 -2
View File
@@ -1,8 +1,7 @@
#pragma once #pragma once
#include "simdjson.h" #include "simdjson.h"
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include <iostream> #include <iostream>
namespace json_benchmark { namespace json_benchmark {
@@ -59,7 +58,11 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) { if (collector.has_events()) {
state.counters["instructions"] = events.instructions(); state.counters["instructions"] = events.instructions();
state.counters["cycles"] = events.cycles(); state.counters["cycles"] = events.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["branch_miss"] = events.branch_misses(); state.counters["branch_miss"] = events.branch_misses();
state.counters["cache_miss"] = events.cache_misses();
state.counters["cache_ref"] = events.cache_references();
#endif
state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration()); state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration());
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles(); state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration()); state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration());
@@ -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_instructions"] = events.best.instructions();
state.counters["best_cycles"] = events.best.cycles(); state.counters["best_cycles"] = events.best.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["best_branch_miss"] = events.best.branch_misses(); state.counters["best_branch_miss"] = events.best.branch_misses();
state.counters["best_cache_miss"] = events.best.cache_misses();
state.counters["best_cache_ref"] = events.best.cache_references();
#endif
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration()); state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration());
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles(); state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
@@ -88,7 +95,11 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) { if (collector.has_events()) {
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0); label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0); label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0); label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
#endif
} }
label << " items=" << setw(10) << bench.items_per_iteration() << setw(0); label << " items=" << setw(10) << bench.items_per_iteration() << setw(0);
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "json_benchmark/string_runner.h" #include "json_benchmark/string_runner.h"
#include <fstream>
#include <map> #include <map>
#include <string> #include <string>
@@ -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
+1 -6
View File
@@ -103,12 +103,7 @@ error_code Sax::RunNoExcept(const padded_string &json) noexcept {
error_code Sax::Allocate(size_t new_capacity) { error_code Sax::Allocate(size_t new_capacity) {
// string_capacity copied from document::allocate // string_capacity copied from document::allocate
// a document with only zero-length strings... could have capacity/3 string size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64);
// and we would need capacity/3 * 5 bytes on the string buffer
if(5 * (new_capacity / 3) + SIMDJSON_PADDING < SIMDJSON_PADDING) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * (new_capacity / 3) + SIMDJSON_PADDING, 64);
string_buf.reset(new (std::nothrow) uint8_t[string_capacity]); string_buf.reset(new (std::nothrow) uint8_t[string_capacity]);
if (auto error = dom_parser.set_capacity(new_capacity)) { return error; } if (auto error = dom_parser.set_capacity(new_capacity)) { return error; }
if (capacity == 0) { // set max depth the first time only if (capacity == 0) { // set max depth the first time only
+105
View File
@@ -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 -1
View File
@@ -10,7 +10,7 @@ namespace partial_tweets {
// { // {
// "created_at": "Sun Aug 31 00:29:15 +0000 2014", // "created_at": "Sun Aug 31 00:29:15 +0000 2014",
// "id": 505874924095815700, // "id": 505874924095815700,
// "text": "@aym0566x ...", // "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
// "in_reply_to_status_id": null, // "in_reply_to_status_id": null,
// "user": { // "user": {
// "id": 1186275104, // "id": 1186275104,
+8 -15
View File
@@ -61,29 +61,22 @@ struct yyjson_base {
}; };
struct yyjson : 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) { bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; } yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
doc = yyjson_read(json.data(), json.size(), 0); bool b = yyjson_base::run(doc, result);
return yyjson_base::run(doc, result); yyjson_doc_free(doc);
return b;
} }
}; };
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime(); BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU #if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base { 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) { bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; } yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0); bool b = yyjson_base::run(doc, result);
return yyjson_base::run(doc, result); yyjson_doc_free(doc);
return b;
} }
}; };
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime(); BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
@@ -1,7 +1,6 @@
#ifndef BENCHMARK_HELPER_HPP #ifndef BENCHMARK_HELPER_HPP
#define BENCHMARK_HELPER_HPP #define BENCHMARK_HELPER_HPP
#include <counters/event_counter.h> #include "event_counter.h"
using namespace counters;
#include <atomic> #include <atomic>
inline event_collector &get_collector() { inline event_collector &get_collector() {
-8
View File
@@ -20,14 +20,6 @@ if(SIMDJSON_GOOGLE_BENCHMARKS)
) )
endif() endif()
CPMAddPackage(
NAME counters
URL https://github.com/lemire/counters/archive/refs/tags/v3.1.0.zip
OPTIONS
"COUNTERS_BUILD_TESTS OFF"
"COUNTERS_INSTALL OFF"
)
CPMAddPackage( CPMAddPackage(
NAME simdjson-data NAME simdjson-data
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
+79 -301
View File
@@ -23,7 +23,6 @@ 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) * [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) * [3. Using static reflection (C++26)](#3-using-static-reflection-c26)
+ [Special cases](#special-cases) + [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) * [The simdjson::from shortcut (experimental, C++20)](#the-simdjsonfrom-shortcut-experimental-c20)
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing) - [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
- [UTF-8 validation (alone)](#utf-8-validation-alone) - [UTF-8 validation (alone)](#utf-8-validation-alone)
@@ -31,7 +30,6 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
- [JSONPath](#jsonpath) - [JSONPath](#jsonpath)
* [Using `for_each_at_path_with_wildcard` for JSONPath Queries (On-Demand)](#using-for_each_at_path_with_wildcard-for-jsonpath-queries-on-demand) * [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) + [Example Usage](#example-usage)
- [C++20 Ranges Support](#c20-ranges-support)
- [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection) - [Compile-Time JSONPath and JSON Pointer (C++26 Reflection)](#compile-time-jsonpath-and-json-pointer-c26-reflection)
- [Error handling](#error-handling) - [Error handling](#error-handling)
* [Error handling examples without exceptions](#error-handling-examples-without-exceptions) * [Error handling examples without exceptions](#error-handling-examples-without-exceptions)
@@ -43,7 +41,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) - [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
- [Parsing numbers inside strings](#parsing-numbers-inside-strings) - [Parsing numbers inside strings](#parsing-numbers-inside-strings)
- [Dynamic Number Types](#dynamic-number-types) - [Dynamic Number Types](#dynamic-number-types)
- [Infinity and NaN support](#infinity-and-nan-support)
- [Raw strings from keys](#raw-strings-from-keys) - [Raw strings from keys](#raw-strings-from-keys)
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string) - [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) * [Raw JSON string for objects and arrays](#raw-json-string-for-objects-and-arrays)
@@ -61,7 +58,7 @@ Requirements
The simdjson library is widely deployed in popular systems such as the Node.js runtime The simdjson library is widely deployed in popular systems such as the Node.js runtime
environment. environment.
- A recent compiler (LLVM clang 6 or better, GNU GCC 7.4 or better, Xcode 11 or better) on POSIX systems such as macOS, FreeBSD or Linux. We require that the compiler supports the C++11 standard or better. We test the library on a big-endian system (IBM s390x with Linux). 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). - Visual Studio 2017 or better. We support the LLVM clang compiler under Visual Studio (clang-cl) as well as the regular Visual Studio compiler. For better release performance (both compile time and execution time), we recommend Visual Studio users adopt LLVM (clang-cl). We discourage using GCC under Windows: there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
Support for AVX-512 require a processor with AVX512-VBMI2 support (Ice Lake or better, AMD Zen 4 or better) under a 64-bit system and a recent compiler (LLVM clang 6 or better, GCC 8 or better, Visual Studio 2019 or better). You need a correspondingly recent assembler such as gas (2.30+) or nasm (2.14+): recent compilers usually come with recent assemblers. If you mix a recent compiler with an incompatible/old assembler (e.g., when using a recent compiler with an old Linux distribution), you may get errors at build time because the compiler produces instructions that the assembler does not recognize: you should update your assembler to match your compiler (e.g., upgrade binutils to version 2.30 or better under Linux) or use an older compiler matching the capabilities of your assembler. Support for AVX-512 require a processor with AVX512-VBMI2 support (Ice Lake or better, AMD Zen 4 or better) under a 64-bit system and a recent compiler (LLVM clang 6 or better, GCC 8 or better, Visual Studio 2019 or better). You need a correspondingly recent assembler such as gas (2.30+) or nasm (2.14+): recent compilers usually come with recent assemblers. If you mix a recent compiler with an incompatible/old assembler (e.g., when using a recent compiler with an old Linux distribution), you may get errors at build time because the compiler produces instructions that the assembler does not recognize: you should update your assembler to match your compiler (e.g., upgrade binutils to version 2.30 or better under Linux) or use an older compiler matching the capabilities of your assembler.
@@ -166,77 +163,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)). 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. For efficiency reasons, simdjson requires a string with a few bytes (`simdjson::SIMDJSON_PADDING`)
The simplest way to load a JSON file is with `padded_string::load`: 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 ```cpp
ondemand::parser parser; ondemand::parser parser;
auto json = padded_string::load("twitter.json"); auto json = padded_string::load("twitter.json"); // load JSON file 'twitter.json'.
ondemand::document doc = parser.iterate(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 If you prefer not to create your own `ondemand::parser` instance, you can access
ondemand::parser parser; a thread-local version by calling `ondemand::parser.get_parser()`.
auto json = "[1,2,3]"_padded;
ondemand::document doc = parser.iterate(json);
```
If you are compiling with C++17 or better, you can use `simdjson::padded_input`
which accepts any string-like input and handles padding automatically:
```cpp
ondemand::parser parser;
std::string_view json = "[1,2,3]";
simdjson::padded_input input(json);
ondemand::document doc = parser.iterate(input);
// Also works with std::string, considering reserved capacity
std::string json_str = "[1,2,3]";
json_str.reserve(100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying
ondemand::document doc2 = parser.iterate(input2);
```
The simdjson library also accepts `std::string` instances directly---if the provided
reference is non-const, it will allocate padding as needed:
```cpp
ondemand::parser parser;
std::string json = "[1,2,3]";
ondemand::document doc = parser.iterate(json);
```
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
### Advanced input options
This section covers additional ways to provide JSON input to simdjson, including
options for fine-grained control over padding and memory.
**Thread-local parser.** If you prefer not to create your own `ondemand::parser` instance, you can access
a thread-local version by calling `ondemand::parser.get_parser()`:
```cpp ```cpp
ondemand::document doc = ondemand::parser.get_parser().iterate(json); ondemand::document doc = ondemand::parser.get_parser().iterate(json);
``` ```
A parser instance can only be used for one document at a time, so However, you should be careful because a parser instance can only be used for one
the thread-local parser is only applicable when you parse one document at a time, thus it is only applicable when you are only parsing one
document per thread at any one time. 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 You can also create a padded string---and call `iterate()`:
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).
**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 ```cpp
ondemand::parser parser; ondemand::parser parser;
@@ -245,97 +219,73 @@ strcpy(json, "[1]");
ondemand::document doc = parser.iterate(json, strlen(json), sizeof(json)); 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 ```cpp
const char * data = "my data"; // 7 bytes const char * data = "my data"; // 7 bytes
simdjson::padded_string my_padded_data(data, 7); // copies to a padded buffer simdjson::padded_string my_padded_data(data, 7); // copies to a padded buffer
``` ```
Or from a `std::string`: Or as follows...
```cpp ```cpp
std::string data = "my data"; std::string data = "my data";
simdjson::padded_string my_padded_data(data); // copies to a padded buffer simdjson::padded_string my_padded_data(data); // copies to a padded buffer
``` ```
**`std::string` and sanitizer warnings.** Whenever you pass an `std::string` reference to `parser::iterate`, You can then parse the JSON data from the `simdjson::padded_string` instance:
the parser may access bytes beyond the end of
```cpp
ondemand::document doc = parser.iterate(my_padded_data);
```
Whenever you pass an `std::string` reference to `parser::iterate`,
the parser will access the bytes beyond the end of
the string but before the end of the allocated memory (`std::string::capacity()`). the string but before the end of the allocated memory (`std::string::capacity()`).
Sanitizers that check for reading uninitialized bytes may produce warnings. If you are using a sanitizer that checks for reading uninitialized bytes or `std::string`'s
You can safely ignore these warnings, or call `simdjson::pad(std::string&)` to pad the container-overflow checks, you may encounter sanitizer warnings.
string explicitly: 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 ```cpp
std::string json = "[1]"; std::string json = "[1]";
ondemand::document doc = parser.iterate(simdjson::pad(json)); ondemand::document doc = parser.iterate(simdjson::pad(json));
``` ```
We recommend against creating many `std::string` or many `std::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. Consider reusing the same buffers and limiting memory allocations.
**Memory-file mapping.** You can use `simdjson::padded_memory_map` to create a 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.
`simdjson::padded_string_view` from a file on disk. On POSIX systems (Linux,
macOS, BSD, ...) it uses `mmap` for true zero-copy access and is always
available. On Windows it is an **opt-in** feature because it relies on the
`CreateFileMapping2` / `MapViewOfFile3` APIs (Windows 10, version 1803 or
later) which are exported from `onecore.lib` rather than the default
`kernel32.lib`. To enable it, you must satisfy **all** of the following:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples:
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before including simdjson, in every translation
unit that uses `padded_memory_map`.
The Windows implementation then uses `CreateFileMapping2` / `MapViewOfFile3` * [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
for true zero-copy access whenever possible, with a transparent * [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo)
buffered-read fallback for files that end too close to a page boundary.
The availability of the class can be tested with the preprocessor macro *Windows-specific*: Windows users who need to read files with
`SIMDJSON_HAS_PADDED_MEMORY_MAP`.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view();
ondemand::document doc = parser.iterate(view);
```
**Windows-specific notes.** Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
filenames: `padded_string::load(L"twitter.json")`. Windows users who need to read files with
non-ANSI characters in the name should set their code page to non-ANSI characters in the name should set their code page to
UTF-8 (65001). This should be the default with Windows 11 and better. UTF-8 (65001). This should be the default with Windows 11 and better.
Further, they may use the AreFileApisANSI function to determine whether Further, they may use the AreFileApisANSI function to determine whether
the filename is interpreted using the ANSI or the system default OEM the filename is interpreted using the ANSI or the system default OEM
codepage, and they may call SetFileApisToOEM accordingly. codepage, and they may call SetFileApisToOEM accordingly.
Some users may want to browse code along with the compiled assembly:
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn) **Advanced feature:**
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo) On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
**Summary of input types:** ```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
| Input Type / Method | Padding Requirement | How Padding is Handled | Ownership / Copying | Notes / Warnings | simdjson::padded_memory_map map(myfilename);
|----------------------------------------------|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------|----------------------------------------------------------------------------------| if (!map.is_valid()) { /* handle error */ }
| `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. | simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
| `"...json..."_padded` literal | Automatic (built-in padding) | Creates `padded_string` with padding | Owned by `padded_string` | Convenient for small hardcoded JSON. | ondemand::document doc = parser.iterate(view); // parse the JSON
| `simdjson::padded_input` (C++17+) | Automatic when needed | Adds padding **only** if the string ends near a memory page boundary. For `std::string`, considers `capacity()` | Usually a non-owning view (no copy most times) | Safe on standard OS (page size ≥ 4096). May trigger sanitizer/valgrind warnings (harmless). Avoid on niche embedded systems. | ```
| User buffer with explicit padding | Must have at least `SIMDJSON_PADDING` extra allocated bytes after JSON content | Pass via `iterate(ptr, json_length, total_allocated_size)` or `padded_string_view` | User-owned (no copy) | Use `char buf[len + SIMDJSON_PADDING]`. Library reads (but never writes) into padding. |
| `std::string` (non-const) | Library checks `capacity()` | If insufficient, library may allocate a padded copy | May copy (depends on capacity) | Can trigger sanitizer warnings on uninitialized bytes. Use `simdjson::pad(json)` to avoid. |
| `simdjson::pad(std::string&)` | Adds padding if needed | Returns `padded_string_view` pointing to the (possibly resized) string | References original string | Recommended to silence sanitizers when using `std::string`. |
| `padded_string(data, length)` or `padded_string(std::string)` | Automatic (copies into padded buffer) | Explicit copy into owned padded buffer | Owned by `padded_string` | Safe when you want full ownership and padding guaranteed. |
| `padded_string_view` (manual) | User guarantees `SIMDJSON_PADDING` extra bytes after the viewed length | User provides pointer + length + capacity | Non-owning view | Low-level; requires careful buffer management. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping / padded read | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Always available on POSIX (zero-copy `mmap`). On Windows, opt-in via `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON` (requires Windows 10 1803+ and links `onecore.lib`) and `#include <windows.h>` before simdjson; uses `CreateFileMapping2` + `MapViewOfFile3`. |
Documents are iterators Documents are iterators
@@ -457,7 +407,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 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 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`, `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. 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 it becomes invalid: you should therefore consume the value immediately by converting it to a
scalar type, an array or an object. scalar type, an array or an object.
@@ -1352,6 +1302,10 @@ You can also use the custom `Car` type as part of a template such as `std::vecto
simdjson::ondemand::parser parser; simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(json); simdjson::ondemand::document doc = parser.iterate(json);
std::vector<Car> cars(doc); std::vector<Car> cars(doc);
// visual studio users need an explicit call:
// std::vector<Car> cars = doc.get<std::vector<Car>>();
// because the compiler does not know whether to convert
// doc to an unsigned int or to a vector.
for(Car& c : cars) { for(Car& c : cars) {
std::cout << c.year << std::endl; std::cout << c.year << std::endl;
} }
@@ -1445,8 +1399,6 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
that are not made by Toyota. that are not made by Toyota.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
### 3. Using static reflection (C++26) ### 3. Using static reflection (C++26)
@@ -1523,10 +1475,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.
#### Special cases #### Special cases
However, there are instances where the construction cannot However, there are instances where the construction cannot
@@ -1593,51 +1541,6 @@ You can also automatically serialize the `Car` instance to a JSON string, see
our [Builder documentation](builder.md). 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) ### The simdjson::from shortcut (experimental, C++20)
@@ -1649,10 +1552,6 @@ type without a document instance like so:
Car car = simdjson::from(json); Car car = simdjson::from(json);
``` ```
The string must be a `simdjson::padded_string_view`, which can be created from an std::string
instance with `simdjson::pad()` function, from a `simdjson::padded_string` instance, or string literal using the `_padded` user-defined literal.
You can also use the `simdjson::from` syntax without exceptions, like so: You can also use the `simdjson::from` syntax without exceptions, like so:
```cpp ```cpp
Car car; Car car;
@@ -1950,66 +1849,6 @@ doc.for_each_at_path_with_wildcard("$.phoneNumbers[*].numbers[*]",
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations. This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
## C++20 Ranges Support
When compiling with C++20 (or later), you can use `std::ranges` with the On-Demand API
via the `get_range()` helper. This enables use of range adaptors such as `std::views::transform`.
```cpp
#include "simdjson.h"
#include <ranges>
#include <string>
#include <vector>
auto json = R"([
{ "name": "Alice", "age": 30 },
{ "name": "Bob", "age": 25 },
{ "name": "Carol", "age": 35 }
])"_padded;
ondemand::parser parser;
auto doc = parser.iterate(json);
auto arr = doc.get_array();
// Use std::views::transform to extract names
auto names = ondemand::get_range(arr)
| std::views::transform([](auto elem) -> std::string {
return std::string(std::string_view(elem["name"]));
});
for (auto name : names) {
std::cout << name << std::endl; // Alice, Bob, Carol
}
```
The `get_range()` and `get_key_value_range()` functions wrap an `ondemand::array`
or `ondemand::object` in a `std::ranges::view` that satisfies `std::ranges::input_range`.
They work with both exception and non-exception code:
```cpp
// With exceptions:
auto range = ondemand::get_range(doc.get_array());
// Without exceptions:
ondemand::array arr;
if (doc.get_array().get(arr) == SUCCESS) {
auto range = ondemand::get_range(arr);
for (auto elem : range) { /* ... */ }
}
```
Object iteration uses `get_key_value_range()` and yields `simdjson_result<ondemand::field>` elements:
```cpp
auto obj = doc.get_object();
for (auto field_result : ondemand::get_key_value_range(obj)) {
std::cout << field_result.key() << std::endl;
}
```
The range wrappers are zero-cost: they forward directly to the underlying
On-Demand iterators with no value buffering or extra per-element overhead.
## Compile-Time JSONPath and JSON Pointer (C++26 Reflection) ## Compile-Time JSONPath and JSON Pointer (C++26 Reflection)
The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression
@@ -2694,7 +2533,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 ```cpp
ondemand::parser parser; ondemand::parser parser;
@@ -2709,7 +2548,7 @@ if (error) {
error = value.get_string().get(view); error = value.get_string().get(view);
if (error) { /* Handle error */ } if (error) { /* Handle error */ }
else if (view == "Infinity") { else if (view == "Infinity") {
d = std::numeric_limits<double>::infinity(); d = std::numeric_limits::infinity();
} }
else { /* Handle wrong value */ } else { /* Handle wrong value */ }
} }
@@ -2717,15 +2556,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 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. 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: The `*_in_string` methods can also be called on a single document instance:
e.g., when your document consist solely of a quoted number. e.g., when your document consist solely of a quoted number.
@@ -2865,53 +2695,6 @@ This code prints the following:
'99999999999999999999999 ' '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 Raw strings from keys
----------- -----------
@@ -3010,7 +2793,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. 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 ```cpp
padded_string json = "{\"key\": NaN}"_padded; padded_string json = "{\"key\": NaN}"_padded;
@@ -3032,9 +2815,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 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. 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 ### Raw JSON string for objects and arrays
If your value is an array or an object, `raw_json_token()` returns effectively a single If your value is an array or an object, `raw_json_token()` returns effectively a single
@@ -3058,10 +2838,8 @@ simdjson::ondemand::array arr = doc.get_array();
string_view token = arr.raw_json(); // gives you `[1,2,3]` 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 Because `raw_json()` consumes to object or the array, if you want to both have
substring and later field access on the same instance, call `reset()` on that access to the raw string, and also use the array or object, you should call `reset()`.
object or array (as below). To re-parse the whole document from the start,
use `document::rewind()` instead (see [Rewinding](#rewinding)).
```cpp ```cpp
simdjson::ondemand::parser parser; simdjson::ondemand::parser parser;
@@ -3069,7 +2847,7 @@ simdjson::padded_string docdata = R"({"value":123})"_padded;
simdjson::ondemand::document doc = parser.iterate(docdata); simdjson::ondemand::document doc = parser.iterate(docdata);
simdjson::ondemand::object obj = doc.get_object(); simdjson::ondemand::object obj = doc.get_object();
string_view token = obj.raw_json(); // gives you `{"value":123}` 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 uint64_t x = obj["value"]; // gives me 123
``` ```
-43
View File
@@ -12,7 +12,6 @@ speed and high convenience.
* [Overview: string_builder](#overview--string-builder) * [Overview: string_builder](#overview--string-builder)
* [Example: string_builder](#example--string-builder) * [Example: string_builder](#example--string-builder)
* [C++26 static reflection](#c--26-static-reflection) * [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](#without--string-buffer--instance)
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling) + [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
+ [Pretty formatted (fractured JSON)](#pretty-formatted-fractured-json) + [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 ### Without `string_buffer` instance
In some instances, you might want to create a string directly from your own data type. In some instances, you might want to create a string directly from your own data type.
-23
View File
@@ -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 `simdjson::compile_time::parse_json` function parses a JSON document at **compile time** and returns a `constexpr` structure reflecting its content. We support the full range of JSON values, which are mapped to C++ types as in
the following table. the following table.
For convenience, you can also use the `""_json` user-defined literal operator, which is available in the `simdjson::literals` namespace:
```cpp
using namespace simdjson::literals;
constexpr auto cfg = R"(
{
"port": 8080,
"host": "localhost"
}
)"_json;
```
Alternatively, you can use the qualified name `simdjson::literals::operator""_json`.
| JSON type | C++ type | | JSON type | C++ type |
|----------------|----------------------------------| |----------------|----------------------------------|
@@ -76,8 +61,6 @@ You can do so, at compile-time, as follows:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto cfg = R"( constexpr auto cfg = R"(
{ {
@@ -96,8 +79,6 @@ constexpr auto cfg = R"(
You can nest objects and arrays: You can nest objects and arrays:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto data = R"( constexpr auto data = R"(
{ {
@@ -117,8 +98,6 @@ constexpr auto data = R"(
Top-level arrays are allowed: Top-level arrays are allowed:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto arr = R"( constexpr auto arr = R"(
[1, 2, 3] [1, 2, 3]
@@ -137,8 +116,6 @@ want to check that it conforms to your expectation. You can do so with concepts.
Let us consider this example: Let us consider this example:
```cpp ```cpp
using namespace simdjson::literals;
constexpr auto config = R"( constexpr auto config = R"(
[ [
+3 -39
View File
@@ -127,31 +127,11 @@ codepage, and they may call SetFileApisToOEM accordingly.
**Advanced feature:** **Advanced feature:**
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view` On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk without copying the file contents into your own buffer. from a file on disk.
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 ```cpp
#ifdef _WIN32 // if the macro _WIN32 is defined, this will not work since we do not support Windows
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(TWITTER_JSON); simdjson::padded_memory_map map(TWITTER_JSON);
if (!map.is_valid()) { /* handle error */ } if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
@@ -790,9 +770,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 Reusing the parser for maximum efficiency
@@ -905,19 +882,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. Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes.
If you are compiling your project with C++17 or better, you can use a `simdjson::padded_input`:
```cpp
simdjson::dom::parser parser;
std::string_view json = "[1,2,3]";
simdjson::padded_input input(json); // Automatically pads if needed
simdjson::dom::element element = parser.parse(input);
```
The actual padding only occurs if the JSON string ends near the boundary of a memory page, which is uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input` on systems without a page size of at least 4096: virtually all systems qualify except for some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, an `simdjson::padded_input` instance will not copy the data and will only act
as a view (it does not own the memory).
Performance Tips Performance Tips
--------------------- ---------------------
+34 -352
View File
@@ -22,12 +22,10 @@ Contents
- [Threads](#threads) - [Threads](#threads)
- [Support](#support) - [Support](#support)
- [API](#api) - [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases) - [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position) - [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams) - [Incomplete streams](#incomplete-streams)
- [C++20 features](#c20-features) - [C++20 features](#c20-features)
- [C++26 features (static reflection)](#c26-features-static-reflection)
Motivation Motivation
----------- -----------
@@ -134,7 +132,7 @@ E.g., `[1,2]{"32":1}` is recognized as two documents.
Some official formats **(non-exhaustive list)**: Some official formats **(non-exhaustive list)**:
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/) - [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/)
- [JSON lines (JSONL)](http://jsonlines.org/) - [JSON lines (JSONL)](http://jsonlines.org/)
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) - [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) - [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
API API
@@ -157,79 +155,13 @@ for (auto doc : docs) {
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API. See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
Streaming directly from a memory-mapped file **Advanced feature:**
-------------------------------------------- On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
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 ```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename); simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ } if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
@@ -346,131 +278,39 @@ Importantly, you should only call `truncated_bytes()` after iterating through al
Comma-separated documents 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 ```cpp
auto json = R"({"a":1},{"b":2},{"c":3})"_padded; auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
ondemand::parser parser; ondemand::parser parser;
ondemand::document_stream stream; ondemand::document_stream doc_stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE, // We pass '32' as the batch size, but it is a bogus parameter because, since
simdjson::stream_format::comma_delimited).get(stream); // we pass 'true' to the allow_comma parameter, the batch size will be set to at least
if (error) { std::cerr << error << std::endl; return; } // the document size.
for (auto doc : stream) { auto error = parser.iterate_many(json, 32, true).get(doc_stream);
std::cout << doc << std::endl; if (error) { std::cerr << error << std::endl; return; }
} for (auto doc : doc_stream) {
// Prints: {"a":1} std::cout << doc.type() << std::endl;
// {"b":2} }
// {"c":3} ```
This will print:
``` ```
number
Whitespace around the commas is allowed: number
```cpp number
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works 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 C++20 features
-------------------- --------------------
@@ -578,161 +418,3 @@ Otherwise you may use this longer version for explicit handling of errors:
cars.push_back(c); cars.push_back(c);
} }
``` ```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
C++26 features (static reflection)
-----------------------------------
If you have a C++26 compatible compiler with [P2996](https://wg21.link/P2996)
static reflection support, you can compile the simdjson library with the
`SIMDJSON_STATIC_REFLECTION` macro set to `1`. When this is the case, simdjson
can deserialize a stream of JSON documents directly into your own structures
**without** writing any `tag_invoke` function. The library inspects the
non-static public members of your type at compile time and produces the
parsing code automatically.
```cpp
#define SIMDJSON_STATIC_REFLECTION 1
#include "simdjson.h"
```
Consider the same `Car` structure used in the C++20 example, but **without**
any `tag_invoke` glue:
```cpp
struct Car {
std::string make;
std::string model;
int year;
std::vector<double> tire_pressure;
};
```
With C++26 static reflection enabled, you can iterate a stream of cars and
push them into a `std::vector<Car>` directly:
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] }
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] }
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
This works for every `stream_format` value supported by `iterate_many`. The
following examples each parse the same three cars, but laid out using a
different streaming convention.
### Whitespace-delimited (default, NDJSON / JSON Lines)
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] }
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] }
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::whitespace_delimited).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
cars.push_back((Car)doc); // throws on error
}
```
### Comma-delimited documents
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
### A single JSON array as a stream of documents
When the input is a single JSON array, you can stream its elements one at a
time without materializing the entire array as a `std::vector` upfront:
```cpp
auto json = R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } ] )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
### JSON Text Sequences (RFC 7464)
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
auto append = [&](std::string_view doc) {
input_str += '\x1e'; input_str += doc; input_str += '\x0a';
};
append(R"({ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9 ] })");
append(R"({ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0 ] })");
append(R"({ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0 ] })");
simdjson::padded_string input(input_str);
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
In every case, the user-defined type (`Car` here) does not need a hand-written
`tag_invoke` overload: the library generates the deserialization code from the
type's public data members at compile time.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
+7 -186
View File
@@ -18,7 +18,6 @@ Contents
- [How it works](#how-it-works) - [How it works](#how-it-works)
- [Support](#support) - [Support](#support)
- [API](#api) - [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases) - [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position) - [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams) - [Incomplete streams](#incomplete-streams)
@@ -133,7 +132,7 @@ Whitespace Characters:
Some official formats **(non-exhaustive list)**: Some official formats **(non-exhaustive list)**:
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec) - [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec)
- [JSON lines (JSONL)](http://jsonlines.org/) - [JSON lines (JSONL)](http://jsonlines.org/)
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) - [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) - [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
API API
@@ -219,83 +218,17 @@ got full document at 29
Streaming directly from a memory-mapped file **Advanced feature:**
-------------------------------------------- On non-Windows systems, you can use memory-file mapping to create a `simdjson::padded_string_view`
from a file on disk.
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 ```cpp
// If the macro _WIN32 is defined, this will not work since we do not support memory-file mapping
// under Windows at this time.
simdjson::padded_memory_map map(myfilename); simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ } if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope 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 ondemand::document doc = parser.iterate(view); // parse the JSON
``` ```
Incomplete streams Incomplete streams
@@ -320,115 +253,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. 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
View File
@@ -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 In effect, it means that you can almost always read a few bytes beyond your current buffer---without
allocating extra memory. However, tools such as valgrind or memory sanitizers will flag such behavior as unsafe. allocating extra memory. However, tools such as valgrind or memory sanitizers will flag such behavior as unsafe.
You can still make sure of this capability in your code if you are an expert 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. programmer and you are willing to silence sanitizer warnings. The following code provides
a portable example.
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
Example usage:
```cpp
std::string_view json = get_json_data();
simdjson::padded_input input(json); // Automatically pads if needed
auto result = parser.parse(input);
// Also works with std::string, considering capacity
std::string json_str = get_json_string();
json_str.reserve(json_str.size() + 100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
auto result2 = parser.parse(input2);
```
This simplifies padding management compared to manually checking and allocating.
More generally, the following code provides a portable example.
The conditional compilation checks for the `_MSC_VER` macro (indicating Microsoft Visual Studio) The conditional compilation checks for the `_MSC_VER` macro (indicating Microsoft Visual Studio)
@@ -272,7 +250,7 @@ long page_size() {
} }
// Returns true if the buffer + len + simdjson::SIMDJSON_PADDING crosses the // Returns true if the buffer + len + simdjson::SIMDJSON_PADDING crosses the
// page boundary. Assumes len != 0. // page boundary.
bool need_allocation(const char *buf, size_t len) { bool need_allocation(const char *buf, size_t len) {
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size()) return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size())
+ simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size())); + simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size()));
-71
View File
@@ -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
-19
View File
@@ -21,10 +21,6 @@ SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS
/** The maximum document size supported by simdjson. */ /** The maximum document size supported by simdjson. */
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF; constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
/** The maximum depth of nested objects and arrays supported by simdjson.
A depth of SIMDJSON_MAXSIZE_BYTES/2 is not reasonable and would be
adversarial, but it serves as an upper bound for validation purposes. */
constexpr size_t SIMDJSON_MAX_DEPTH = SIMDJSON_MAXSIZE_BYTES/2;
/** /**
* The amount of padding needed in a buffer to parse JSON. * The amount of padding needed in a buffer to parse JSON.
@@ -50,21 +46,6 @@ struct padded_string;
class padded_string_view; class padded_string_view;
enum class stage1_mode; enum class stage1_mode;
/**
* Stream format for parse_many/iterate_many.
*/
enum class stream_format {
whitespace_delimited, ///< Whitespace-delimited JSON documents (default, includes NDJSON/JSONL)
json_sequence, ///< RFC 7464 JSON text sequences (RS-delimited)
comma_delimited, ///< Comma-separated JSON documents (e.g., `{...},{...},{...}`)
comma_delimited_array ///< A single JSON array whose elements are iterated as
///< comma-separated documents (e.g., `[{...},{...},{...}]`).
///< The parser strips the outer `[` / `]` plus any
///< surrounding JSON whitespace (space, tab, LF, CR)
///< and then behaves like `comma_delimited` over the
///< remaining bytes.
};
namespace internal { namespace internal {
template<typename T> template<typename T>
+3 -15
View File
@@ -30,10 +30,6 @@ double from_chars(const char *first, const char* end) noexcept;
#endif #endif
#endif #endif
#ifndef SIMDJSON_ENABLE_NAN_INF
#define SIMDJSON_ENABLE_NAN_INF 0
#endif
} // namespace simdjson } // namespace simdjson
#if defined(__GNUC__) #if defined(__GNUC__)
@@ -49,6 +45,9 @@ double from_chars(const char *first, const char* end) noexcept;
// Align to N-byte boundary // Align to N-byte boundary
#define SIMDJSON_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1)) #define SIMDJSON_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1))
#define SIMDJSON_ROUNDDOWN_N(a, n) ((a) & ~((n)-1))
#define SIMDJSON_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0)
#if SIMDJSON_REGULAR_VISUAL_STUDIO #if SIMDJSON_REGULAR_VISUAL_STUDIO
// We could use [[deprecated]] but it requires C++14 // We could use [[deprecated]] but it requires C++14
@@ -56,7 +55,6 @@ double from_chars(const char *first, const char* end) noexcept;
#define simdjson_really_inline __forceinline #define simdjson_really_inline __forceinline
#define simdjson_never_inline __declspec(noinline) #define simdjson_never_inline __declspec(noinline)
#define simdjson_really_flatten [[msvc::flatten]]
#define simdjson_unused #define simdjson_unused
#define simdjson_warn_unused #define simdjson_warn_unused
@@ -97,7 +95,6 @@ double from_chars(const char *first, const char* end) noexcept;
#define simdjson_really_inline inline __attribute__((always_inline)) #define simdjson_really_inline inline __attribute__((always_inline))
#define simdjson_never_inline inline __attribute__((noinline)) #define simdjson_never_inline inline __attribute__((noinline))
#define simdjson_really_flatten [[gnu::flatten]]
#define simdjson_unused __attribute__((unused)) #define simdjson_unused __attribute__((unused))
#define simdjson_warn_unused __attribute__((warn_unused_result)) #define simdjson_warn_unused __attribute__((warn_unused_result))
@@ -174,15 +171,6 @@ double from_chars(const char *first, const char* end) noexcept;
#define simdjson_inline simdjson_really_inline #define simdjson_inline simdjson_really_inline
#endif #endif
#if defined(simdjson_flatten)
// Prefer the user's definition of simdjson_flatten; don't define it ourselves.
#elif (defined(__GNUC__) && !defined(__OPTIMIZE__)) || (defined(_DEBUG) && _MSC_VER )
// Flattening can lead to significant code bloat and high compile times. Don't use it for unoptimized builds.
#define simdjson_flatten
#else
#define simdjson_flatten simdjson_really_flatten
#endif
#if SIMDJSON_VISUAL_STUDIO #if SIMDJSON_VISUAL_STUDIO
/** /**
* Windows users need to do some extra work when building * Windows users need to do some extra work when building
+34 -35
View File
@@ -266,12 +266,6 @@ consteval std::pair<double, size_t> parse_double(const char *src,
} }
} // namespace number_parsing } // namespace number_parsing
consteval auto make_data_member_options(auto&& name_str) {
std::meta::data_member_options options{};
options.name = std::forward<decltype(name_str)>(name_str);
return options;
}
// JSON string may contain embedded nulls, and C++26 reflection does not yet // JSON string may contain embedded nulls, and C++26 reflection does not yet
// support std::string_view as a data member type. As a workaround, we define // support std::string_view as a data member type. As a workaround, we define
// a custom type that holds a const char* and a size. // a custom type that holds a const char* and a size.
@@ -326,8 +320,7 @@ using class_type = type_builder<meta_info...>::constructed_type;
/** /**
* @brief Variable template for constructing instances with values * @brief Variable template for constructing instances with values
*/ */
template <typename T, auto... Vs> constexpr 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 // in JSON, there are only a few whitespace characters that are allowed
// outside of objects, arrays, strings, and numbers. // outside of objects, arrays, strings, and numbers.
@@ -417,7 +410,7 @@ parse_number(std::string_view json,
std::from_chars(json.data(), json.data() + json.size(), int_value); std::from_chars(json.data(), json.data() + json.size(), int_value);
if (res.ec == std::errc()) { if (res.ec == std::errc()) {
out = int_value; out = int_value;
if (static_cast<std::size_t>(res.ptr - json.data()) != scope) { if ((res.ptr - json.data()) != scope) {
simdjson_consteval_error( simdjson_consteval_error(
"Internal error: cannot agree on the character range of the float"); "Internal error: cannot agree on the character range of the float");
} }
@@ -431,7 +424,7 @@ parse_number(std::string_view json,
std::from_chars(json.data(), json.data() + json.size(), uint_value); std::from_chars(json.data(), json.data() + json.size(), uint_value);
if (res.ec == std::errc()) { if (res.ec == std::errc()) {
out = uint_value; out = uint_value;
if (static_cast<std::size_t>(res.ptr - json.data()) != scope) { if ((res.ptr - json.data()) != scope) {
simdjson_consteval_error( simdjson_consteval_error(
"Internal error: cannot agree on the character range of the float"); "Internal error: cannot agree on the character range of the float");
} }
@@ -540,7 +533,7 @@ parse_string(std::string_view json) {
// present, we have an error (isolated high surrogate), which we // present, we have an error (isolated high surrogate), which we
// tolerate by substituting the substitution_code_point. // tolerate by substituting the substitution_code_point.
if (end - cursor < 6 || *cursor != '\\' || if (end - cursor < 6 || *cursor != '\\' ||
*(cursor + 1) != 'u') { *(cursor + 1) != 'u' > 0xFFFF) {
code_point = substitution_code_point; code_point = substitution_code_point;
} else { // we have \u following the high surrogate } else { // we have \u following the high surrogate
cursor += 2; // skip \u cursor += 2; // skip \u
@@ -894,19 +887,10 @@ parse_json_array_impl(const std::string_view json) {
std::size_t count = values.size() - 1; std::size_t count = values.size() - 1;
// We assume all elements have the same type as the first element. // We assume all elements have the same type as the first element.
// However, if the array is heterogeneous, we should use std::variant. // However, if the array is heterogeneous, we should use std::variant.
auto elem_type = std::meta::type_of(values[1]);
// String literals reflected via reflect_constant_string have type const
// char[N], but when passed as template auto parameters they decay to
// const char*. Use const char* as the element type so that
// construct_from can aggregate-initialize the array.
if (std::meta::is_array_type(elem_type) &&
std::meta::remove_all_extents(elem_type) == ^^const char) {
elem_type = ^^const char *;
}
auto array_type = std::meta::substitute( auto array_type = std::meta::substitute(
^^std::array, ^^std::array,
{ {
elem_type, std::meta::reflect_constant(count)}); std::meta::type_of(values[1]), std::meta::reflect_constant(count)});
// Create array instance with values // Create array instance with values
values[0] = array_type; values[0] = array_type;
@@ -973,7 +957,8 @@ parse_json_object_impl(std::string_view json) {
simdjson_consteval_error("Expected '}'"); simdjson_consteval_error("Expected '}'");
} }
cursor += object_size; cursor += object_size;
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), 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)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(parsed); values.push_back(parsed);
@@ -982,7 +967,8 @@ parse_json_object_impl(std::string_view json) {
case '[': { case '[': {
std::string_view value(cursor, end); std::string_view value(cursor, end);
auto [parsed, array_size] = parse_json_array_impl(value); auto [parsed, array_size] = parse_json_array_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), 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)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(parsed); values.push_back(parsed);
if (*(cursor + array_size - 1) != ']') { if (*(cursor + array_size - 1) != ']') {
@@ -1003,7 +989,8 @@ parse_json_object_impl(std::string_view json) {
} }
} }
auto dms = 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)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant_string(value)); values.push_back(std::meta::reflect_constant_string(value));
break; break;
@@ -1014,7 +1001,8 @@ parse_json_object_impl(std::string_view json) {
} }
cursor += 4; 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)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(true)); values.push_back(std::meta::reflect_constant(true));
break; break;
@@ -1025,7 +1013,8 @@ parse_json_object_impl(std::string_view json) {
} }
cursor += 5; 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)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(false)); values.push_back(std::meta::reflect_constant(false));
break; break;
@@ -1036,7 +1025,9 @@ parse_json_object_impl(std::string_view json) {
} }
cursor += 4; 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)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(nullptr)); values.push_back(std::meta::reflect_constant(nullptr));
break; break;
@@ -1060,19 +1051,22 @@ parse_json_object_impl(std::string_view json) {
if (std::holds_alternative<int64_t>(out)) { if (std::holds_alternative<int64_t>(out)) {
int64_t int_value = std::get<int64_t>(out); int64_t int_value = std::get<int64_t>(out);
auto dms = auto dms =
std::meta::data_member_spec(^^int64_t, make_data_member_options(field_name)); std::meta::data_member_spec(^^int64_t, {
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(int_value)); values.push_back(std::meta::reflect_constant(int_value));
} else if (std::holds_alternative<uint64_t>(out)) { } else if (std::holds_alternative<uint64_t>(out)) {
uint64_t uint_value = std::get<uint64_t>(out); uint64_t uint_value = std::get<uint64_t>(out);
auto dms = auto dms =
std::meta::data_member_spec(^^uint64_t, make_data_member_options(field_name)); std::meta::data_member_spec(^^uint64_t, {
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(uint_value)); values.push_back(std::meta::reflect_constant(uint_value));
} else { } else {
double float_value = std::get<double>(out); double float_value = std::get<double>(out);
auto dms = auto dms =
std::meta::data_member_spec(^^double, make_data_member_options(field_name)); std::meta::data_member_spec(^^double, {
.name = field_name});
members.push_back(std::meta::reflect_constant(dms)); members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(float_value)); values.push_back(std::meta::reflect_constant(float_value));
} }
@@ -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 " "Only JSON objects and arrays are supported at the top level, this "
"limitation will be lifted in the future.");*/ "limitation will be lifted in the future.");*/
if constexpr (json.front() == '[') { constexpr auto result = json.front() == '['
return [: parse_json_array_impl(json).first :]; ? parse_json_array_impl(json)
} else { : parse_json_object_impl(json);
return [: parse_json_object_impl(json).first :]; 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 } // namespace compile_time
+1 -4
View File
@@ -63,16 +63,13 @@ namespace compile_time {
template <constevalutil::fixed_string json_str> consteval auto parse_json(); template <constevalutil::fixed_string json_str> consteval auto parse_json();
} // namespace compile_time } // namespace compile_time
} // namespace simdjson
inline namespace literals {
template <simdjson::constevalutil::fixed_string str> template <simdjson::constevalutil::fixed_string str>
consteval auto operator ""_json() { consteval auto operator ""_json() {
return simdjson::compile_time::parse_json<str>(); return simdjson::compile_time::parse_json<str>();
} }
} // namespace literals
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION #endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H #endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
-5
View File
@@ -58,11 +58,6 @@ struct fixed_string {
data[i] = str[i]; data[i] = str[i];
} }
} }
constexpr fixed_string(const unsigned char (&str)[N]) {
for (std::size_t i = 0; i < N; ++i) {
data[i] = static_cast<char>(str[i]);
}
}
char data[N]; char data[N];
constexpr std::string_view view() const { return {data, N - 1}; } constexpr std::string_view view() const { return {data, N - 1}; }
constexpr size_t size() const { return N ; } constexpr size_t size() const { return N ; }
+13
View File
@@ -37,6 +37,9 @@ inline auto_parser<parser_type>::auto_parser(parser_type parser, ondemand::docum
: auto_parser{*parser, std::move(doc)} {} : auto_parser{*parser, std::move(doc)} {}
template <typename parser_type> template <typename parser_type>
inline std::remove_pointer_t<parser_type> &auto_parser<parser_type>::parser() noexcept { inline std::remove_pointer_t<parser_type> &auto_parser<parser_type>::parser() noexcept {
if constexpr (std::is_pointer_v<parser_type>) { if constexpr (std::is_pointer_v<parser_type>) {
@@ -115,6 +118,16 @@ template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_view const str) const noexcept { inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_view const str) const noexcept {
return auto_parser<ondemand::parser *>{parser, str}; return auto_parser<ondemand::parser *>{parser, str};
} }
template <typename T>
inline auto to_adaptor<T>::operator()(std::string str) const noexcept {
return auto_parser<ondemand::parser *>{pad_with_reserve(str)};
}
template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, std::string str) const noexcept {
return auto_parser<ondemand::parser *>{parser, pad_with_reserve(str)};
}
} // namespace internal } // namespace internal
} // namespace convert } // namespace convert
} // namespace simdjson } // namespace simdjson
+12 -12
View File
@@ -36,12 +36,12 @@ public:
explicit auto_parser(std::remove_pointer_t<parser_type> &parser, padded_string_view const str) noexcept requires(std::is_pointer_v<parser_type>); explicit auto_parser(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(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>); explicit auto_parser(parser_type parser, ondemand::document &&doc) noexcept requires(std::is_pointer_v<parser_type>);
auto_parser(auto_parser const &) = delete; auto_parser(auto_parser const &) = delete;
auto_parser &operator=(auto_parser const &) = delete; auto_parser &operator=(auto_parser const &) = delete;
~auto_parser() = default; auto_parser(auto_parser &&) noexcept = default;
// Prevent moving auto_parser &operator=(auto_parser &&) noexcept = default;
auto_parser(auto_parser&&) = delete; ~auto_parser() = default;
auto_parser &operator=(auto_parser &&) noexcept = delete;
simdjson_warn_unused std::remove_pointer_t<parser_type> &parser() noexcept; simdjson_warn_unused std::remove_pointer_t<parser_type> &parser() noexcept;
template <typename T> template <typename T>
@@ -75,6 +75,11 @@ struct to_adaptor {
T operator()(simdjson_result<ondemand::value> &val) const noexcept; T operator()(simdjson_result<ondemand::value> &val) const noexcept;
auto operator()(padded_string_view const str) const noexcept; auto operator()(padded_string_view const str) const noexcept;
auto operator()(ondemand::parser &parser, padded_string_view const str) const noexcept; auto operator()(ondemand::parser &parser, padded_string_view const str) const noexcept;
// The std::string is padded with reserve to ensure there is enough space for padding.
// Some sanitizers may not like this, so you can use simdjson::pad instead.
// simdjson::from(simdjson::pad(str))
auto operator()(std::string str) const noexcept;
auto operator()(ondemand::parser &parser, std::string str) const noexcept;
}; };
// deduction guide // deduction guide
auto_parser(padded_string_view const str) -> auto_parser<ondemand::parser*>; auto_parser(padded_string_view const str) -> auto_parser<ondemand::parser*>;
@@ -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 simdjson::from instance is EXPERIMENTAL AND SUBJECT TO CHANGES.
* *
* The `from` instance is a utility adaptor for parsing JSON strings into objects. * The `from` instance is a utility adaptor for parsing JSON strings into objects.
* * It provides a convenient way to convert JSON data into C++ objects using the `auto_parser`.
* The string must be a simdjson::padded_string_view, which can be created from a std::string
* with simdjson::pad(), from a simdjson::padded_string, or string literal using the `_padded`
* user-defined literal.
*
* The `from` instance provides a convenient way to convert JSON data into C++ objects using the `auto_parser`.
* *
* Example usage: * Example usage:
* *
+13 -1
View File
@@ -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. // We don't support this, because we're returning a real element, not a position.
if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; }
// Read the array index
size_t array_index = 0; size_t array_index = 0;
size_t i; size_t i;
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 // Get the child
auto child = array(tape).at(array_index); auto child = array(tape).at(array_index);
+1 -10
View File
@@ -33,25 +33,16 @@ inline error_code document::allocate(size_t capacity) noexcept {
allocated_capacity = 0; allocated_capacity = 0;
return SUCCESS; return SUCCESS;
} }
if (capacity > SIMDJSON_MAXSIZE_BYTES) {
return CAPACITY;
}
// a pathological input like "[[[[..." would generate capacity tape elements, so // a pathological input like "[[[[..." would generate capacity tape elements, so
// need a capacity of at least capacity + 1, but it is also possible to do // need a capacity of at least capacity + 1, but it is also possible to do
// worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6" // worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6"
//where capacity + 1 tape elements are //where capacity + 1 tape elements are
// generated, see issue https://github.com/simdjson/simdjson/issues/345 // generated, see issue https://github.com/simdjson/simdjson/issues/345
if(capacity + 3 < capacity) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t tape_capacity = SIMDJSON_ROUNDUP_N(capacity + 3, 64); size_t tape_capacity = SIMDJSON_ROUNDUP_N(capacity + 3, 64);
// a document with only zero-length strings... could have capacity/3 string // a document with only zero-length strings... could have capacity/3 string
// and we would need capacity/3 * 5 bytes on the string buffer // and we would need capacity/3 * 5 bytes on the string buffer
if(5 * (capacity / 3) + SIMDJSON_PADDING < SIMDJSON_PADDING) { size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * capacity / 3 + SIMDJSON_PADDING, 64);
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * (capacity / 3) + SIMDJSON_PADDING, 64);
string_buf.reset( new (std::nothrow) uint8_t[string_capacity]); string_buf.reset( new (std::nothrow) uint8_t[string_capacity]);
tape.reset(new (std::nothrow) uint64_t[tape_capacity]); tape.reset(new (std::nothrow) uint64_t[tape_capacity]);
if(!(string_buf && tape)) { if(!(string_buf && tape)) {
+4 -39
View File
@@ -89,14 +89,12 @@ simdjson_inline document_stream::document_stream(
dom::parser &_parser, dom::parser &_parser,
const uint8_t *_buf, const uint8_t *_buf,
size_t _len, size_t _len,
size_t _batch_size, size_t _batch_size
stream_format _format
) noexcept ) noexcept
: parser{&_parser}, : parser{&_parser},
buf{_buf}, buf{_buf},
len{_len}, len{_len},
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
format{_format},
error{SUCCESS} error{SUCCESS}
#ifdef SIMDJSON_THREADS_ENABLED #ifdef SIMDJSON_THREADS_ENABLED
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
@@ -114,7 +112,6 @@ simdjson_inline document_stream::document_stream() noexcept
buf{nullptr}, buf{nullptr},
len{0}, len{0},
batch_size{0}, batch_size{0},
format{stream_format::whitespace_delimited},
error{UNINITIALIZED} error{UNINITIALIZED}
#ifdef SIMDJSON_THREADS_ENABLED #ifdef SIMDJSON_THREADS_ENABLED
, use_thread(false) , use_thread(false)
@@ -227,14 +224,7 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
} else { } else {
size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index]; size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index];
size_t svlen = next_doc_index - current_index(); size_t svlen = next_doc_index - current_index();
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464 json_sequence while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
// mode the scanner classifies RS as a scalar character, so an RS-prefixed
// scalar document (number/true/false/null/string) has no closing structural
// index and the slice runs all the way up to the next document's RS. RS
// cannot legally appear in a JSON value at the source level (control
// characters in strings must be escaped as \u001E), so stripping it is
// safe in every stream_format.
while(svlen > 1 && (std::isspace(static_cast<unsigned char>(start[svlen-1])) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E || (stream->format == stream_format::comma_delimited && start[svlen-1] == ','))) {
svlen--; svlen--;
} }
return std::string_view(start, svlen); return std::string_view(start, svlen);
@@ -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 { inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
size_t remaining = len - _batch_start; size_t remaining = len - _batch_start;
stage1_mode mode;
if (remaining <= batch_size) { if (remaining <= batch_size) {
// Final batch return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_final;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_final;
break;
default:
mode = stage1_mode::streaming_final;
break;
}
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
} else { } else {
// Partial batch return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
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);
} }
} }
+1 -5
View File
@@ -206,14 +206,12 @@ private:
* @param buf is the raw byte buffer we need to process * @param buf is the raw byte buffer we need to process
* @param len is the length of the raw byte buffer in bytes * @param len is the length of the raw byte buffer in bytes
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document) * @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
* @param format is the stream format
*/ */
simdjson_inline document_stream( simdjson_inline document_stream(
dom::parser &parser, dom::parser &parser,
const uint8_t *buf, const uint8_t *buf,
size_t len, size_t len,
size_t batch_size, size_t batch_size
stream_format format = stream_format::whitespace_delimited
) noexcept; ) noexcept;
/** /**
@@ -263,8 +261,6 @@ private:
const uint8_t *buf; const uint8_t *buf;
size_t len; size_t len;
size_t batch_size; size_t batch_size;
/** The stream format. */
stream_format format;
/** The error (or lack thereof) from the current document. */ /** The error (or lack thereof) from the current document. */
error_code error; error_code error;
size_t batch_start{0}; size_t batch_start{0};
-2
View File
@@ -22,8 +22,6 @@ enum class element_type {
STRING = '"', ///< std::string_view STRING = '"', ///< std::string_view
BOOL = 't', ///< bool BOOL = 't', ///< bool
NULL_VALUE = 'n', ///< null NULL_VALUE = 'n', ///< null
/// The BIGINT type is for integers that do not fit in 64 bits. It is only present
// if you set parser.number_as_string(true).
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
}; };
+25 -43
View File
@@ -72,7 +72,7 @@ inline element_metrics structure_analyzer::analyze_scalar(const dom::element& el
metrics.complexity = 0; metrics.complexity = 0;
metrics.child_count = 0; metrics.child_count = 0;
metrics.can_inline = true; metrics.can_inline = true;
metrics.recommended_layout = layout_mode::single_line; metrics.recommended_layout = layout_mode::INLINE;
switch (elem.type()) { switch (elem.type()) {
case dom::element_type::STRING: { case dom::element_type::STRING: {
@@ -158,16 +158,16 @@ inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
// Decide layout // Decide layout
if (metrics.child_count == 0) { if (metrics.child_count == 0) {
metrics.recommended_layout = layout_mode::single_line; metrics.recommended_layout = layout_mode::INLINE;
} else if (metrics.can_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()) { } 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 && } else if (current_opts_->enable_compact_multiline &&
max_child_complexity <= current_opts_->max_compact_array_complexity) { max_child_complexity <= current_opts_->max_compact_array_complexity) {
metrics.recommended_layout = layout_mode::compact_multiline; metrics.recommended_layout = layout_mode::COMPACT_MULTILINE;
} else { } else {
metrics.recommended_layout = layout_mode::expanded; metrics.recommended_layout = layout_mode::EXPANDED;
} }
return metrics; 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) // Objects use inline or expanded (no table/compact for objects)
if (metrics.child_count == 0 || metrics.can_inline) { if (metrics.child_count == 0 || metrics.can_inline) {
metrics.recommended_layout = layout_mode::single_line; metrics.recommended_layout = layout_mode::INLINE;
} else { } else {
metrics.recommended_layout = layout_mode::expanded; metrics.recommended_layout = layout_mode::EXPANDED;
} }
return metrics; 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 { inline size_t structure_analyzer::estimate_number_length(double d) const {
if (!std::isfinite(d)) { if (std::isnan(d) || std::isinf(d)) {
#if SIMDJSON_ENABLE_NAN_INF
if (std::isnan(d)) {
return 3; // "NaN"
} else if (d < 0) {
return 9; // "-Infinity"
} else {
return 8; // "Infinity"
}
#else
return 4; // "null" for invalid numbers return 4; // "null" for invalid numbers
#endif
} }
// Rough estimate: up to 17 significant digits + sign + decimal point + exponent // Rough estimate: up to 17 significant digits + sign + decimal point + exponent
char buf[32]; char buf[32];
@@ -356,28 +346,28 @@ inline layout_mode structure_analyzer::decide_layout(const element_metrics& metr
size_t depth, size_t depth,
size_t available_width) const { size_t available_width) const {
if (metrics.child_count == 0) { if (metrics.child_count == 0) {
return layout_mode::single_line; return layout_mode::INLINE;
} }
// Check inline feasibility // Check inline feasibility
size_t indent_width = depth * current_opts_->indent_spaces; size_t indent_width = depth * current_opts_->indent_spaces;
if (metrics.can_inline && if (metrics.can_inline &&
metrics.estimated_inline_len + indent_width <= available_width) { metrics.estimated_inline_len + indent_width <= available_width) {
return layout_mode::single_line; return layout_mode::INLINE;
} }
// Check table mode // Check table mode
if (metrics.is_uniform_array && !metrics.common_keys.empty()) { if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
return layout_mode::table; return layout_mode::TABLE;
} }
// Check compact multiline // Check compact multiline
if (current_opts_->enable_compact_multiline && if (current_opts_->enable_compact_multiline &&
metrics.complexity <= current_opts_->max_compact_array_complexity + 1) { 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_{} {} : options_(opts), column_widths_{} {}
simdjson_inline void fractured_formatter::print_newline() { 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 return; // No newlines in inline mode
} }
one_char('\n'); one_char('\n');
@@ -396,7 +386,7 @@ simdjson_inline void fractured_formatter::print_newline() {
} }
simdjson_inline void fractured_formatter::print_indents(size_t depth) { 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 return; // No indentation in inline mode
} }
for (size_t i = 0; i < depth * options_.indent_spaces; i++) { 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, const element_metrics& metrics,
size_t depth) { size_t depth) {
switch (metrics.recommended_layout) { switch (metrics.recommended_layout) {
case layout_mode::single_line: case layout_mode::INLINE:
format_array_inline(arr, metrics); format_array_inline(arr, metrics);
break; break;
case layout_mode::compact_multiline: case layout_mode::COMPACT_MULTILINE:
format_array_compact_multiline(arr, metrics, depth); format_array_compact_multiline(arr, metrics, depth);
break; break;
case layout_mode::table: case layout_mode::TABLE:
format_array_as_table(arr, metrics, depth); format_array_as_table(arr, metrics, depth);
break; break;
case layout_mode::expanded: case layout_mode::EXPANDED:
default: default:
format_array_expanded(arr, metrics, depth); format_array_expanded(arr, metrics, depth);
break; 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, inline void fractured_string_builder::format_array_inline(const dom::array& arr,
const element_metrics& metrics) { const element_metrics& metrics) {
layout_mode prev_layout = format_.get_layout_mode(); 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(); format_.start_array();
@@ -623,7 +613,7 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom::
// Format element inline // Format element inline
layout_mode prev_layout = format_.get_layout_mode(); 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()) const element_metrics& child_metrics = (child_idx < metrics.children.size())
? metrics.children[child_idx] : element_metrics{}; ? metrics.children[child_idx] : element_metrics{};
format_element(elem, child_metrics, depth + 1); 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 // Write value
if (found) { if (found) {
layout_mode prev_layout = format_.get_layout_mode(); 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()) const element_metrics& value_metrics = (field_idx < row_metrics.children.size())
? row_metrics.children[field_idx] : element_metrics{}; ? row_metrics.children[field_idx] : element_metrics{};
format_element(value, value_metrics, depth + 1); 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, inline void fractured_string_builder::format_object(const dom::object& obj,
const element_metrics& metrics, const element_metrics& metrics,
size_t depth) { 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); format_object_inline(obj, metrics);
} else { } else {
format_object_expanded(obj, metrics, depth); 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, inline void fractured_string_builder::format_object_inline(const dom::object& obj,
const element_metrics& metrics) { const element_metrics& metrics) {
layout_mode prev_layout = format_.get_layout_mode(); 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(); format_.start_object();
@@ -960,14 +950,6 @@ inline size_t fractured_string_builder::measure_value_length(const dom::element&
case dom::element_type::DOUBLE: { case dom::element_type::DOUBLE: {
double val; double val;
if (elem.get_double().get(val) == SUCCESS) { if (elem.get_double().get(val) == SUCCESS) {
#if SIMDJSON_ENABLE_NAN_INF
if (!std::isfinite(val)) {
if (std::isnan(val))
return 3; // "NaN"
// "-Infinity" (9) or "Infinity" (8)
return val < 0 ? 9 : 8;
}
#endif
char buf[32]; char buf[32];
int len = snprintf(buf, sizeof(buf), "%.17g", val); int len = snprintf(buf, sizeof(buf), "%.17g", val);
return len > 0 ? static_cast<size_t>(len) : 1; return len > 0 ? static_cast<size_t>(len) : 1;
+6 -43
View File
@@ -170,7 +170,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 { 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 { inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept {
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size); return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
@@ -181,48 +186,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 { inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size) noexcept {
return parse_many(s.data(), s.length(), batch_size); return parse_many(s.data(), s.length(), batch_size);
} }
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size) noexcept {
return parse_many(v.data(), v.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
if (format == stream_format::comma_delimited_array) {
// Strip leading JSON whitespace.
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
buf++; len--;
}
// Expect the opening '['.
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
buf++; len--;
// Strip trailing JSON whitespace.
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
len--;
}
// Expect the closing ']'.
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
len--;
// Fall through to comma_delimited over the array contents.
format = stream_format::comma_delimited;
}
return document_stream(*this, buf, len, batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept {
return parse_many(v.data(), v.length(), batch_size, format);
}
simdjson_inline size_t parser::capacity() const noexcept { simdjson_inline size_t parser::capacity() const noexcept {
return implementation ? implementation->capacity() : 0; return implementation ? implementation->capacity() : 0;
-29
View File
@@ -490,39 +490,10 @@ public:
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
*
* Because padded_string_view guarantees SIMDJSON_PADDING trailing bytes, this
* overload is safe to use with buffers that the caller owns elsewhere (for
* example, a padded_memory_map), with no extra copy. Without this overload,
* passing a padded_string_view would silently bind to the padded_string
* overload via an implicit conversion, allocating and copying the input, and
* because that temporary is destroyed at the end of the full-expression
* leaving the returned document_stream pointing at freed memory. */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
/** @private We do not want to allow implicit conversion from C string to std::string. */ /** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete; simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
/**
* Parse a stream of JSON documents with explicit format specification.
*
* @param buf The concatenated JSON documents.
* @param len The length of the buffer.
* @param batch_size The batch size to use.
* @param format The stream format.
* @return A stream of documents, or an error.
*/
inline simdjson_result<document_stream> parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept;
/** /**
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
* and `max_depth` depth. * and `max_depth` depth.
-17
View File
@@ -11,7 +11,6 @@
#include "simdjson/dom/object-inl.h" #include "simdjson/dom/object-inl.h"
#include "simdjson/internal/tape_ref-inl.h" #include "simdjson/internal/tape_ref-inl.h"
#include <cmath>
#include <cstring> #include <cstring>
namespace simdjson { namespace simdjson {
@@ -182,22 +181,6 @@ simdjson_inline void base_formatter<formatter>::number(int64_t x) {
template <class formatter> template <class formatter>
simdjson_inline void base_formatter<formatter>::number(double x) { simdjson_inline void base_formatter<formatter>::number(double x) {
#if SIMDJSON_ENABLE_NAN_INF
if (simdjson_unlikely(!std::isfinite(x))) {
if (std::isnan(x)) {
char const *s = "NaN";
chars(s, s + 3);
} else {
if (x < 0) {
one_char('-');
}
char const *s = "Infinity";
chars(s, s + 8);
}
return;
}
#endif
char number_buffer[24]; char number_buffer[24];
// Currently, passing the nullptr to the second argument is // Currently, passing the nullptr to the second argument is
// safe because our implementation does not check the second // safe because our implementation does not check the second
-90
View File
@@ -22,9 +22,6 @@ namespace atomparsing {
// to the compile-time constant 1936482662. // to the compile-time constant 1936482662.
simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; }
// Acts on the same principle as string_to_uint32, but on an 8-byte block of memory
simdjson_inline uint64_t string_to_uint64(const char* str) { uint64_t val; std::memcpy(&val, str, sizeof(uint64_t)); return val; }
// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. // Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive.
// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. // Yet all decent optimizing compilers will compile memcpy to a single instruction, just about.
@@ -36,28 +33,6 @@ simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) {
return srcval ^ string_to_uint32(atom); return srcval ^ string_to_uint32(atom);
} }
// Checks that the first 8 characters of the input string match the given atom in a case-insensitive manner.
//
// 'atom' must consist of only lowercase letters.
simdjson_warn_unused
simdjson_inline uint64_t str8ncmp_case_insensitive(const uint8_t *src, const char* atom) {
uint64_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++)
static_assert(sizeof(uint64_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 8 bytes");
std::memcpy(&srcval, src, sizeof(uint64_t));
return (srcval | 0x2020202020202020ull) ^ string_to_uint64(atom);
}
// Checks that the first 3 characters of 'src' match 'atom' in a case-insensitive way.
//
// 'atom' must consist of only lowercase letters.
simdjson_warn_unused
simdjson_inline uint32_t str3ncmp_case_insensitive(const uint8_t *src, const char* atom) {
return ((src[0] | 0x20) ^ atom[0]) //
| ((src[1] | 0x20) ^ atom[1]) //
| ((src[2] | 0x20) ^ atom[2]);
}
simdjson_warn_unused simdjson_warn_unused
simdjson_inline bool is_valid_true_atom(const uint8_t *src) { simdjson_inline bool is_valid_true_atom(const uint8_t *src) {
return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0;
@@ -94,71 +69,6 @@ simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) {
else { return false; } else { return false; }
} }
#if SIMDJSON_ENABLE_NAN_INF
// "nan" is 3 bytes; we check characters and then verify the next
// character is structural or whitespace. We accept both "nan" and "NaN".
simdjson_warn_unused
simdjson_inline bool is_valid_nan_atom(const uint8_t *src) {
return (str3ncmp_case_insensitive(src, "nan")
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
}
// checks that the next four characters of a string are 'nan"', where the 'nan'
// is checked in a case-insensitive way.
simdjson_warn_unused
simdjson_inline bool is_valid_nan_in_string(const uint8_t *src) {
return (str3ncmp_case_insensitive(src, "nan") | (src[3] ^ '"')) == 0;
}
simdjson_warn_unused
simdjson_inline bool is_valid_nan_atom(const uint8_t *src, size_t len) {
if (len > 3) { return is_valid_nan_atom(src); }
if (len == 3) { return str3ncmp_case_insensitive(src, "nan") == 0; }
return false;
}
// This function will accept any case-insensitive 3-character spelling of
// infinity: 'inf', 'INF', and 'Inf' are all accepted.
//
// Any capitalization of 'infinity' is also accepted.
simdjson_warn_unused
simdjson_inline bool is_valid_inf_atom(const uint8_t *src) {
bool is_short_inf = (str3ncmp_case_insensitive(src, "inf")
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
if(is_short_inf) return true;
// Check for 'infinity' (any capitalization)
return (str8ncmp_case_insensitive(src, "infinity") | jsoncharutils::is_not_structural_or_whitespace(src[8])) == 0;
}
simdjson_warn_unused
simdjson_inline bool is_valid_inf_in_string(const uint8_t *src) {
bool is_short_inf = (str3ncmp_case_insensitive(src, "inf") | (src[3] ^ '"')) == 0;
if(is_short_inf) return true;
return (str8ncmp_case_insensitive(src, "infinity") | (src[8] ^ '"')) == 0;
}
// This function will accept any case-insensitive 3-character spelling of
// infinity: 'inf', 'INF', and 'Inf' are all accepted.
//
// Any capitalization of 'infinity' is also accepted.
simdjson_warn_unused
simdjson_inline bool is_valid_inf_atom(const uint8_t *src, size_t len) {
if (len > 8) { return is_valid_inf_atom(src); }
if (len == 8 && str8ncmp_case_insensitive(src, "infinity") == 0) {
return true;
}
if (len > 3) {
return (str3ncmp_case_insensitive(src, "inf")
| jsoncharutils::is_not_structural_or_whitespace(src[3])) == 0;
}
if (len == 3) { return str3ncmp_case_insensitive(src, "inf") == 0; }
return false;
}
#endif // SIMDJSON_ENABLE_NAN_INF
} // namespace atomparsing } // namespace atomparsing
} // unnamed namespace } // unnamed namespace
} // namespace SIMDJSON_IMPLEMENTATION } // namespace SIMDJSON_IMPLEMENTATION
@@ -10,6 +10,5 @@
// Otherwise, amalgamation will fail. // Otherwise, amalgamation will fail.
#include "simdjson/concepts.h" #include "simdjson/concepts.h"
#include "simdjson/dom/fractured_json.h" #include "simdjson/dom/fractured_json.h"
#include "simdjson/annotations.h"
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H #endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
+113 -292
View File
@@ -4,7 +4,6 @@
#define SIMDJSON_GENERIC_STRING_BUILDER_H #define SIMDJSON_GENERIC_STRING_BUILDER_H
#include "simdjson/generic/builder/json_string_builder.h" #include "simdjson/generic/builder/json_string_builder.h"
#include "simdjson/concepts.h" #include "simdjson/concepts.h"
#include "simdjson/annotations.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE #endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION #if SIMDJSON_STATIC_REFLECTION
@@ -22,113 +21,23 @@ namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION { namespace SIMDJSON_IMPLEMENTATION {
namespace builder { namespace builder {
// Forward-declare helpers defined in json_string_builder-inl.h so the
// writer-based atom code below can call them (the -inl.h is not yet
// included at the point this header is parsed; without these forwards,
// name lookup falls back to the wrong outer namespace).
namespace internal {
simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept;
} // namespace internal
inline size_t write_string_escaped(const std::string_view input, char *out);
// =============================================================
// `writer`: position-as-local hot-path writer used by the reflection
// atom code below. Holds the buffer pointer, write position and
// capacity in three fields that, once `writer` itself is a stack-local
// in the caller and all atom() functions are inlined, become true
// register-resident locals after SROA. Glaze achieves the same effect
// by passing `B&& b, auto&& ix` through every helper. Holding `pos`
// in a register (rather than as a member of string_builder) is what
// breaks the strict-aliasing penalty on every char* write through the
// buffer, which forces a reload of `b.position` and `b.capacity`
// after every byte.
// =============================================================
struct writer {
char *ptr; // buffer pointer (refreshed after a grow)
size_t pos; // write position (local)
size_t cap; // capacity (refreshed after a grow)
string_builder &sb; // back-ref for grow / sync
// Snapshot string_builder state into a writer for the duration of
// a write chain.
simdjson_really_inline writer(string_builder &builder) noexcept
: ptr(builder.unsafe_data())
, pos(builder.unsafe_position())
, cap(builder.unsafe_capacity())
, sb(builder) {}
// Write the local position back to the underlying string_builder.
// Caller is responsible for invoking before the writer is dropped
// (otherwise data is lost). Idempotent.
simdjson_really_inline void sync() noexcept {
sb.unsafe_set_position(pos);
}
// Ensure at least `n` more bytes of free capacity. Grows the
// underlying buffer if needed (rare path). Returns false on
// allocation failure.
simdjson_really_inline bool ensure(size_t n) noexcept {
// Use subtraction (relying on the pos <= cap invariant) so a huge n
// cannot wrap pos + n to a small value that spuriously passes the test.
// This is pedantic except maybe on 32-bit targets.
if (simdjson_likely(n <= cap - pos)) return true;
return grow_slow(n);
}
// Slow path of ensure(). Out-of-line via simdjson_inline (not
// simdjson_really_inline) to keep the hot path short.
simdjson_inline bool grow_slow(size_t n) noexcept {
// Detect overflow.
// This is pedantic except maybe on 32-bit targets.
if (simdjson_unlikely(pos + n < pos)) return false;
sb.unsafe_set_position(pos);
// even if 2*capacity overflows, the (std::max) below will pick the needed value,
// so we do not need a separate overflow check here.
if (!sb.unsafe_grow((std::max)(cap * 2, pos + n))) {
return false;
}
ptr = sb.unsafe_data();
cap = sb.unsafe_capacity();
return true;
}
};
// === Helper: invoke a string_builder member that writes variable-length
// content (escape_and_append_with_quotes etc), syncing the writer's local
// state before the call and reloading after. Used for string fields where
// rewriting the entire SIMD escape path through the writer would be a much
// bigger refactor.
template <class F>
simdjson_really_inline void call_through_string_builder(writer &w, F &&f) noexcept {
w.sync();
f(w.sb);
w.ptr = w.sb.unsafe_data();
w.pos = w.sb.unsafe_position();
w.cap = w.sb.unsafe_capacity();
}
template <class T> template <class T>
requires(concepts::container_but_not_string<T> && ! concepts::optional_type<T> && !require_custom_serialization<T>) requires(concepts::container_but_not_string<T> && !require_custom_serialization<T>)
simdjson_really_inline constexpr void atom(writer &w, const T &t) { constexpr void atom(string_builder &b, const T &t) {
auto it = t.begin(); auto it = t.begin();
auto end = t.end(); auto end = t.end();
if (it == end) { if (it == end) {
if (!w.ensure(2)) return; b.append_raw("[]");
std::memcpy(w.ptr + w.pos, "[]", 2);
w.pos += 2;
return; return;
} }
if (!w.ensure(1)) return; b.append('[');
w.ptr[w.pos++] = '['; atom(b, *it);
atom(w, *it);
++it; ++it;
for (; it != end; ++it) { for (; it != end; ++it) {
if (!w.ensure(1)) return; b.append(',');
w.ptr[w.pos++] = ','; atom(b, *it);
atom(w, *it);
} }
if (!w.ensure(1)) return; b.append(']');
w.ptr[w.pos++] = ']';
} }
template <class T> template <class T>
@@ -136,100 +45,37 @@ template <class T>
std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> || std::is_same_v<T, const char *> ||
std::is_same_v<T, char>) std::is_same_v<T, char>)
simdjson_really_inline constexpr void atom(writer &w, const T &t) { constexpr void atom(string_builder &b, const T &t) {
// Inline the escape path through the writer so we never round-trip b.escape_and_append_with_quotes(t);
// pos through memory for string fields (Twitter is dominated by
// these — sync/reload around each string was a real cost).
std::string_view input;
if constexpr (std::is_same_v<T, char>) {
input = std::string_view(&t, 1);
} else {
input = std::string_view(t);
}
// Worst-case escape: every byte expands to \uXXXX (6 chars), plus 2 quotes.
// Guard against 2 + 6 * input.size() wrapping for huge inputs — if it
// wrapped to a small value, ensure() would spuriously succeed and the
// subsequent escape would overflow the buffer.
// Note that this is pedantic except maybe on 32-bit targets.
if (simdjson_unlikely(input.size() > ((std::numeric_limits<size_t>::max)() - 2) / 6)) { return; }
if (!w.ensure(2 + 6 * input.size())) { return; }
w.ptr[w.pos++] = '"';
w.pos += write_string_escaped(input, w.ptr + w.pos);
w.ptr[w.pos++] = '"';
} }
template <concepts::string_view_keyed_map T> template <concepts::string_view_keyed_map T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
simdjson_really_inline constexpr void atom(writer &w, const T &m) { constexpr void atom(string_builder &b, const T &m) {
if (m.empty()) { if (m.empty()) {
if (!w.ensure(2)) return; b.append_raw("{}");
std::memcpy(w.ptr + w.pos, "{}", 2);
w.pos += 2;
return; return;
} }
if (!w.ensure(1)) return; b.append('{');
w.ptr[w.pos++] = '{';
bool first = true; bool first = true;
for (const auto& [key, value] : m) { for (const auto& [key, value] : m) {
if (!first) { if (!first) {
if (!w.ensure(1)) return; b.append(',');
w.ptr[w.pos++] = ',';
} }
first = false; first = false;
// Keys must be convertible to string_view per the concept. // Keys must be convertible to string_view per the concept
std::string_view key_sv(key); b.escape_and_append_with_quotes(key);
// Guard against 3 + 6 * key_sv.size() wrapping for huge keys, if it b.append(':');
// wrapped to a small value, ensure() would spuriously succeed and the atom(b, value);
// 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);
} }
if (!w.ensure(1)) return; b.append('}');
w.ptr[w.pos++] = '}';
} }
template<typename number_type, template<typename number_type,
typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type> typename = typename std::enable_if<std::is_arithmetic<number_type>::value && !std::is_same_v<number_type, char>>::type>
simdjson_really_inline constexpr void atom(writer &w, const number_type t) { constexpr void atom(string_builder &b, const number_type t) {
// Booleans / floats: defer to string_builder (rare path; keeps writer hot b.append(t);
// path free of float-formatter machinery). For integers, write directly
// via jeaiii using local pos.
if constexpr (std::is_same_v<number_type, bool>) {
if (t) {
if (!w.ensure(4)) return;
std::memcpy(w.ptr + w.pos, "true", 4);
w.pos += 4;
} else {
if (!w.ensure(5)) return;
std::memcpy(w.ptr + w.pos, "false", 5);
w.pos += 5;
}
} else if constexpr (std::is_floating_point_v<number_type>) {
call_through_string_builder(w, [&](string_builder &b) { b.append(t); });
} else if constexpr (std::is_unsigned_v<number_type>) {
if (!w.ensure(20)) return;
char *end = internal::write_uint_jeaiii(
w.ptr + w.pos, static_cast<uint64_t>(t));
w.pos = static_cast<size_t>(end - w.ptr);
} else {
// signed integral
if (!w.ensure(20)) return;
using U = typename std::make_unsigned<number_type>::type;
bool negative = t < 0;
U pv = negative ? U(0) - static_cast<U>(t) : static_cast<U>(t);
w.ptr[w.pos] = '-';
w.pos += negative;
char *end = internal::write_uint_jeaiii(
w.ptr + w.pos, static_cast<uint64_t>(pv));
w.pos = static_cast<size_t>(end - w.ptr);
}
} }
template <class T> template <class T>
@@ -242,86 +88,61 @@ template <class T>
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, std::string_view> &&
!std::is_same_v<T, const char*> && !std::is_same_v<T, const char*> &&
!std::is_same_v<T, char> && !require_custom_serialization<T>) !std::is_same_v<T, char> && !require_custom_serialization<T>)
simdjson_really_inline constexpr void atom(writer &w, const T &t) { constexpr void atom(string_builder &b, const T &t) {
// Per-field block: ensure key+value worst case, then write key + value int i = 0;
// through the writer's local pos. For arithmetic fields, the integer b.append('{');
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
// never round-trips through memory.
bool first = true;
if (!w.ensure(1)) return;
w.ptr[w.pos++] = '{';
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { 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()) { if (i != 0)
constexpr const char* key_name = simdjson::get_json_key_name<dm>(); b.append(',');
constexpr auto first_key = std::define_static_string( constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
constevalutil::consteval_to_quoted_escaped(key_name) + ":"); b.append_raw(key);
constexpr auto rest_key = std::define_static_string( b.append(':');
std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":"); atom(b, t.[:dm:]);
constexpr size_t first_key_len = std::char_traits<char>::length(first_key); i++;
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 (!w.ensure(1)) return; b.append('}');
w.ptr[w.pos++] = '}';
} }
// Support for optional types (std::optional, etc.) // Support for optional types (std::optional, etc.)
template <concepts::optional_type T> template <concepts::optional_type T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
simdjson_really_inline constexpr void atom(writer &w, const T &opt) { constexpr void atom(string_builder &b, const T &opt) {
if (opt) { if (opt) {
atom(w, opt.value()); atom(b, opt.value());
} else { } else {
if (!w.ensure(4)) return; b.append_raw("null");
std::memcpy(w.ptr + w.pos, "null", 4);
w.pos += 4;
} }
} }
// Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.) // Support for smart pointers (std::unique_ptr, std::shared_ptr, etc.)
template <concepts::smart_pointer T> template <concepts::smart_pointer T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
simdjson_really_inline constexpr void atom(writer &w, const T &ptr) { constexpr void atom(string_builder &b, const T &ptr) {
if (ptr) { if (ptr) {
atom(w, *ptr); atom(b, *ptr);
} else { } else {
if (!w.ensure(4)) return; b.append_raw("null");
std::memcpy(w.ptr + w.pos, "null", 4);
w.pos += 4;
} }
} }
// Support for enums - serialize as string representation using expand approach from P2996R12 // Support for enums - serialize as string representation using expand approach from P2996R12
template <typename T> template <typename T>
requires(std::is_enum_v<T> && !require_custom_serialization<T>) requires(std::is_enum_v<T> && !require_custom_serialization<T>)
simdjson_really_inline void atom(writer &w, const T &e) { void atom(string_builder &b, const T &e) {
#if SIMDJSON_STATIC_REFLECTION #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) { template for (constexpr auto enum_val : enumerators) {
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val))); constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
constexpr size_t enum_str_len = std::char_traits<char>::length(enum_str);
if (e == [:enum_val:]) { if (e == [:enum_val:]) {
if (!w.ensure(enum_str_len)) return; b.append_raw(enum_str);
std::memcpy(w.ptr + w.pos, enum_str, enum_str_len);
w.pos += enum_str_len;
return; return;
} }
}; };
// Fallback to integer if enum value not found // Fallback to integer if enum value not found
atom(w, static_cast<std::underlying_type_t<T>>(e)); atom(b, static_cast<std::underlying_type_t<T>>(e));
#else #else
// Fallback: serialize as integer if reflection not available // Fallback: serialize as integer if reflection not available
atom(w, static_cast<std::underlying_type_t<T>>(e)); atom(b, static_cast<std::underlying_type_t<T>>(e));
#endif #endif
} }
@@ -331,37 +152,28 @@ template <concepts::appendable_containers T>
!concepts::optional_type<T> && !concepts::smart_pointer<T> && !concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!std::is_same_v<T, std::string> && !std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>) !std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
simdjson_really_inline constexpr void atom(writer &w, const T &container) { constexpr void atom(string_builder &b, const T &container) {
if (container.empty()) { if (container.empty()) {
if (!w.ensure(2)) return; b.append_raw("[]");
std::memcpy(w.ptr + w.pos, "[]", 2);
w.pos += 2;
return; return;
} }
if (!w.ensure(1)) return; b.append('[');
w.ptr[w.pos++] = '[';
bool first = true; bool first = true;
for (const auto& item : container) { for (const auto& item : container) {
if (!first) { if (!first) {
if (!w.ensure(1)) return; b.append(',');
w.ptr[w.pos++] = ',';
} }
first = false; first = false;
atom(w, item); atom(b, item);
} }
if (!w.ensure(1)) return; b.append(']');
w.ptr[w.pos++] = ']';
} }
// append() — top-level entry. Each overload constructs a stack-local // append functions that delegate to atom functions for primitive types
// writer, runs atom(w, t) through the inlined call chain, then syncs
// the local position back into the string_builder.
template <class T> template <class T>
requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>) requires(std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
simdjson_inline void append(string_builder &b, const T &t) { void append(string_builder &b, const T &t) {
writer w(b); atom(b, t);
atom(w, t);
w.sync();
} }
template <class T> template <class T>
@@ -369,26 +181,20 @@ template <class T>
std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string_view> ||
std::is_same_v<T, const char *> || std::is_same_v<T, const char *> ||
std::is_same_v<T, char>) std::is_same_v<T, char>)
simdjson_inline void append(string_builder &b, const T &t) { void append(string_builder &b, const T &t) {
writer w(b); atom(b, t);
atom(w, t);
w.sync();
} }
template <concepts::optional_type T> template <concepts::optional_type T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
simdjson_inline void append(string_builder &b, const T &t) { void append(string_builder &b, const T &t) {
writer w(b); atom(b, t);
atom(w, t);
w.sync();
} }
template <concepts::smart_pointer T> template <concepts::smart_pointer T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
simdjson_inline void append(string_builder &b, const T &t) { void append(string_builder &b, const T &t) {
writer w(b); atom(b, t);
atom(w, t);
w.sync();
} }
template <concepts::appendable_containers T> template <concepts::appendable_containers T>
@@ -396,18 +202,14 @@ template <concepts::appendable_containers T>
!concepts::optional_type<T> && !concepts::smart_pointer<T> && !concepts::optional_type<T> && !concepts::smart_pointer<T> &&
!std::is_same_v<T, std::string> && !std::is_same_v<T, std::string> &&
!std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>) !std::is_same_v<T, std::string_view> && !std::is_same_v<T, const char*> && !require_custom_serialization<T>)
simdjson_inline void append(string_builder &b, const T &t) { void append(string_builder &b, const T &t) {
writer w(b); atom(b, t);
atom(w, t);
w.sync();
} }
template <concepts::string_view_keyed_map T> template <concepts::string_view_keyed_map T>
requires(!require_custom_serialization<T>) requires(!require_custom_serialization<T>)
simdjson_inline void append(string_builder &b, const T &t) { void append(string_builder &b, const T &t) {
writer w(b); atom(b, t);
atom(w, t);
w.sync();
} }
// works for struct // works for struct
@@ -421,19 +223,39 @@ template <class Z>
!std::is_same_v<Z, std::string_view> && !std::is_same_v<Z, std::string_view> &&
!std::is_same_v<Z, const char*> && !std::is_same_v<Z, const char*> &&
!std::is_same_v<Z, char> && !require_custom_serialization<Z>) !std::is_same_v<Z, char> && !require_custom_serialization<Z>)
simdjson_inline void append(string_builder &b, const Z &z) { void append(string_builder &b, const Z &z) {
writer w(b); int i = 0;
atom(w, z); b.append('{');
w.sync(); template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked()))) {
if (i != 0)
b.append(',');
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
b.append_raw(key);
b.append(':');
atom(b, z.[:dm:]);
i++;
};
b.append('}');
} }
// works for container that have begin() and end() iterators // works for container that have begin() and end() iterators
template <class Z> template <class Z>
requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>) requires(concepts::container_but_not_string<Z> && !require_custom_serialization<Z>)
simdjson_inline void append(string_builder &b, const Z &z) { void append(string_builder &b, const Z &z) {
writer w(b); auto it = z.begin();
atom(w, z); auto end = z.end();
w.sync(); 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> template <class Z>
@@ -472,41 +294,40 @@ string_builder& operator<<(string_builder& b, const Z& z) {
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0)) requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
void extract_from(string_builder &b, const T &obj) { void extract_from(string_builder &b, const T &obj) {
writer w(b); // Helper to check if a field name matches any of the requested fields
if (!w.ensure(1)) { w.sync(); return; } auto should_extract = [](std::string_view field_name) constexpr -> bool {
w.ptr[w.pos++] = '{'; return ((FieldNames.view() == field_name) || ...);
};
b.append('{');
bool first = true; bool first = true;
// Iterate through all members of T using reflection // 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 : std::define_static_array(
template for (constexpr auto mem : members) { std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (std::meta::is_public(mem)) { 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 // Only serialize this field if it's in our list of requested fields
if constexpr (((FieldNames.view() == key) || ...)) { if constexpr (should_extract(key)) {
static constexpr auto first_key = std::define_static_string( if (!first) {
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":"); b.append(',');
static constexpr auto rest_key = std::define_static_string(
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)) + ":");
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
if (!w.ensure(rest_key_len)) { w.sync(); return; }
if (first) {
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
w.pos += first_key_len;
} else {
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
w.pos += rest_key_len;
} }
first = false; first = false;
atom(w, obj.[:mem:]);
// Serialize the key
constexpr auto quoted_key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)));
b.append_raw(quoted_key);
b.append(':');
// Serialize the value
atom(b, obj.[:mem:]);
} }
} }
}; };
if (!w.ensure(1)) { w.sync(); return; } b.append('}');
w.ptr[w.pos++] = '}';
w.sync();
} }
template<constevalutil::fixed_string... FieldNames, typename T> template<constevalutil::fixed_string... FieldNames, typename T>
@@ -1,7 +1,5 @@
#include <array> #include <array>
#include <cmath>
#include <cstring> #include <cstring>
#include <limits>
#include <type_traits> #include <type_traits>
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H #ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
@@ -173,35 +171,6 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
} }
return _mm_movemask_epi8(running) != 0; return _mm_movemask_epi8(running) != 0;
} }
#elif SIMDJSON_EXPERIMENTAL_HAS_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 #elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
simdjson_inline bool fast_needs_escaping(std::string_view view) { simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) { if (view.size() < 16) {
@@ -536,7 +505,7 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
return is_valid; 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) { if (!is_valid) {
return; return;
} }
@@ -592,6 +561,62 @@ simdjson_inline void string_builder::clear() noexcept {
namespace internal { namespace internal {
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline int int_log2(number_type x) {
return 63 - leading_zeroes(uint64_t(x) | 1);
}
simdjson_really_inline int fast_digit_count_32(uint32_t x) {
static uint64_t table[] = {
4294967296, 8589934582, 8589934582, 8589934582, 12884901788,
12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
21474826480, 21474826480, 21474826480, 21474826480, 25769703776,
25769703776, 25769703776, 30063771072, 30063771072, 30063771072,
34349738368, 34349738368, 34349738368, 34349738368, 38554705664,
38554705664, 38554705664, 41949672960, 41949672960, 41949672960,
42949672960, 42949672960};
return uint32_t((x + table[int_log2(x)]) >> 32);
}
simdjson_really_inline int fast_digit_count_64(uint64_t x) {
static uint64_t table[] = {9,
99,
999,
9999,
99999,
999999,
9999999,
99999999,
999999999,
9999999999,
99999999999,
999999999999,
9999999999999,
99999999999999,
999999999999999ULL,
9999999999999999ULL,
99999999999999999ULL,
999999999999999999ULL,
9999999999999999999ULL};
int y = (19 * int_log2(x) >> 6);
y += x > table[y];
return y + 1;
}
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline size_t digit_count(number_type v) noexcept {
static_assert(sizeof(number_type) == 8 || sizeof(number_type) == 4 ||
sizeof(number_type) == 2 || sizeof(number_type) == 1,
"We only support 8-bit, 16-bit, 32-bit and 64-bit numbers");
SIMDJSON_IF_CONSTEXPR(sizeof(number_type) <= 4) {
return fast_digit_count_32(static_cast<uint32_t>(v));
}
else {
return fast_digit_count_64(static_cast<uint64_t>(v));
}
}
static const char decimal_table[200] = { static const char decimal_table[200] = {
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
@@ -611,78 +636,6 @@ static const char decimal_table[200] = {
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, 0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, 0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39,
}; };
// Forward unsigned-int writer (cascade-on-magnitude, no upfront digit_count).
// Built from a non-recursive DAG of always_inline helpers — gcc and MSVC
// refuse to inline recursive `always_inline`/`__forceinline` functions.
// Caller must guarantee at least 20 bytes available at p. All helpers
// return pointer past the last digit written.
// Caller guarantees v < 100. Writes 1-2 digits.
simdjson_really_inline char* write_lt100(char* p, uint64_t v) noexcept {
if (v < 10) { *p++ = char('0' + v); return p; }
std::memcpy(p, &decimal_table[v * 2], 2);
return p + 2;
}
// Caller guarantees v < 10000. Writes 1-4 digits.
simdjson_really_inline char* write_lt10000(char* p, uint64_t v) noexcept {
if (v < 100) return write_lt100(p, v);
uint64_t hi = v / 100, lo = v % 100;
if (v < 1000) {
*p++ = char('0' + hi);
} else {
std::memcpy(p, &decimal_table[hi * 2], 2);
p += 2;
}
std::memcpy(p, &decimal_table[lo * 2], 2);
return p + 2;
}
// Caller guarantees v < 10000. Always writes exactly 4 digits.
simdjson_really_inline void write_4_digits(char* p, uint64_t v) noexcept {
uint64_t hi = v / 100, lo = v % 100;
std::memcpy(p, &decimal_table[hi * 2], 2);
std::memcpy(p + 2, &decimal_table[lo * 2], 2);
}
// Caller guarantees v < 10^8. Writes 1-8 digits.
simdjson_really_inline char* write_lt1e8(char* p, uint64_t v) noexcept {
if (v < 10000) return write_lt10000(p, v);
uint64_t hi = v / 10000, lo = v % 10000;
p = write_lt10000(p, hi);
write_4_digits(p, lo);
return p + 4;
}
simdjson_really_inline char* write_uint_jeaiii(char* p, uint64_t v) noexcept {
if (v < 10000ULL) return write_lt10000(p, v);
if (v < 100000000ULL) { // 5-8 digits
uint64_t hi = v / 10000, lo = v % 10000;
p = write_lt10000(p, hi);
write_4_digits(p, lo);
return p + 4;
}
if (v < 10000000000000000ULL) { // 9-16 digits
uint64_t hi = v / 100000000ULL, lo = v % 100000000ULL;
p = write_lt1e8(p, hi);
uint64_t lo_hi = lo / 10000, lo_lo = lo % 10000;
write_4_digits(p, lo_hi);
write_4_digits(p + 4, lo_lo);
return p + 8;
}
// 17-20 digits
uint64_t hi = v / 10000000000000000ULL, lo = v % 10000000000000000ULL;
p = write_lt10000(p, hi);
uint64_t lo_a = lo / 100000000ULL, lo_b = lo % 100000000ULL;
uint64_t lo_a_hi = lo_a / 10000, lo_a_lo = lo_a % 10000;
uint64_t lo_b_hi = lo_b / 10000, lo_b_lo = lo_b % 10000;
write_4_digits(p, lo_a_hi);
write_4_digits(p + 4, lo_a_lo);
write_4_digits(p + 8, lo_b_hi);
write_4_digits(p + 12, lo_b_lo);
return p + 16;
}
} // namespace internal } // namespace internal
template <typename number_type, typename> template <typename number_type, typename>
@@ -710,59 +663,87 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
} }
} }
else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) { else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) {
// Process 4 digits at a time instead of 2, reducing store operations
// and divisions by approximately half for large numbers.
constexpr size_t max_number_size = 20; constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) { if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type; using unsigned_type = typename std::make_unsigned<number_type>::type;
char* end = internal::write_uint_jeaiii( unsigned_type pv = static_cast<unsigned_type>(v);
buffer.get() + position, size_t dc = internal::digit_count(pv);
static_cast<uint64_t>(static_cast<unsigned_type>(v))); char *write_pointer = buffer.get() + position + dc - 1;
position = end - buffer.get();
// 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) { 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; constexpr size_t max_number_size = 20;
if (capacity_check(max_number_size)) { if (capacity_check(max_number_size)) {
using unsigned_type = typename std::make_unsigned<number_type>::type; using unsigned_type = typename std::make_unsigned<number_type>::type;
bool negative = v < 0; bool negative = v < 0;
// 0 - pv (rather than -pv) avoids an MSVC unary-minus warning. unsigned_type pv = static_cast<unsigned_type>(v);
unsigned_type pv = negative if (negative) {
? unsigned_type(0) - static_cast<unsigned_type>(v) pv = 0 - pv; // the 0 is for Microsoft
: static_cast<unsigned_type>(v); }
// Branchless: always write '-', advance only if negative. size_t dc = internal::digit_count(pv);
// by always writing the minus sign, we avoid the branch.
buffer.get()[position] = '-'; buffer.get()[position] = '-';
position += negative; position += negative ? 1 : 0;
char* end = internal::write_uint_jeaiii( char *write_pointer = buffer.get() + position + dc - 1;
buffer.get() + position, static_cast<uint64_t>(pv));
position = end - buffer.get(); // 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) { else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) {
constexpr size_t max_number_size = 24; constexpr size_t max_number_size = 24;
if (capacity_check(max_number_size)) { if (capacity_check(max_number_size)) {
#if SIMDJSON_ENABLE_NAN_INF
// Check if the input might be NaN or infinity
if (simdjson_unlikely(!std::isfinite(v))) {
if (std::isnan(v)) {
constexpr char nan_literal[] = "NaN";
constexpr size_t nan_len = sizeof(nan_literal) - 1;
std::memcpy(buffer.get() + position, nan_literal, nan_len);
position += nan_len;
} else {
constexpr char inf_literal[] = "Infinity";
constexpr size_t inf_len = sizeof(inf_literal) - 1;
if (v < 0) {
buffer.get()[position] = '-';
++position;
}
std::memcpy(buffer.get() + position, inf_literal, inf_len);
position += inf_len;
}
return;
}
#endif
// We could specialize for float. // We could specialize for float.
char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr,
double(v)); double(v));
@@ -774,11 +755,6 @@ simdjson_inline void string_builder::append(number_type v) noexcept {
simdjson_inline void simdjson_inline void
string_builder::escape_and_append(std::string_view input) noexcept { string_builder::escape_and_append(std::string_view input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters. // escaping might turn a control character into \x00xx so 6 characters.
// Guard against size_t overflow in the multiplication below.
if (input.size() > (std::numeric_limits<size_t>::max)() / 6) {
set_valid(false);
return;
}
if (capacity_check(6 * input.size())) { if (capacity_check(6 * input.size())) {
position += write_string_escaped(input, buffer.get() + position); position += write_string_escaped(input, buffer.get() + position);
} }
@@ -787,11 +763,6 @@ string_builder::escape_and_append(std::string_view input) noexcept {
simdjson_inline void simdjson_inline void
string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { string_builder::escape_and_append_with_quotes(std::string_view input) noexcept {
// escaping might turn a control character into \x00xx so 6 characters. // escaping might turn a control character into \x00xx so 6 characters.
// Guard against size_t overflow in the arithmetic below.
if (input.size() > ((std::numeric_limits<size_t>::max)() - 2) / 6) {
set_valid(false);
return;
}
if (capacity_check(2 + 6 * input.size())) { if (capacity_check(2 + 6 * input.size())) {
buffer.get()[position++] = '"'; buffer.get()[position++] = '"';
position += write_string_escaped(input, buffer.get() + position); position += write_string_escaped(input, buffer.get() + position);
@@ -823,9 +794,7 @@ simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
#endif #endif
simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(const char *c) noexcept {
// char_traits::length is constexpr; lets the compiler fold the length size_t len = std::strlen(c);
// when called with a pointer to a compile-time-constant string.
size_t len = std::char_traits<char>::length(c);
append_raw(c, len); append_raw(c, len);
} }
@@ -844,14 +813,6 @@ simdjson_inline void string_builder::append_raw(const char *str,
position += len; position += len;
} }
} }
template <size_t N>
simdjson_inline void string_builder::append_raw_n(const char *str) noexcept {
if (capacity_check(N)) {
std::memcpy(buffer.get() + position, str, N);
position += N;
}
}
#if SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_CONCEPTS
// Support for optional types (std::optional, etc.) // Support for optional types (std::optional, etc.)
template <concepts::optional_type T> template <concepts::optional_type T>
@@ -881,7 +842,7 @@ simdjson_inline void string_builder::append(const T &value) {
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.) // Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R> template <std::ranges::range R>
requires(!std::is_convertible<R, std::string_view>::value && !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 { simdjson_inline void string_builder::append(const R &range) noexcept {
auto it = std::ranges::begin(range); auto it = std::ranges::begin(range);
auto end = std::ranges::end(range); auto end = std::ranges::end(range);
@@ -171,7 +171,7 @@ public:
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS #if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.) // Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R> template <std::ranges::range R>
requires (!std::is_convertible<R, std::string_view>::value && !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; simdjson_inline void append(const R &range) noexcept;
#endif #endif
/** /**
@@ -185,15 +185,6 @@ requires (!std::is_convertible<R, std::string_view>::value && !concepts::optiona
* There is no UTF-8 validation. * There is no UTF-8 validation.
*/ */
simdjson_inline void append_raw(const char *str, size_t len) noexcept; simdjson_inline void append_raw(const char *str, size_t len) noexcept;
/**
* Append exactly N characters from str. The length is a template parameter
* so the compiler can fully inline the memcpy with a compile-time-constant
* size, avoiding the libc call. Used for compile-time-constant keys in the
* reflection struct atom.
*/
template <size_t N>
simdjson_inline void append_raw_n(const char *str) noexcept;
#if SIMDJSON_EXCEPTIONS #if SIMDJSON_EXCEPTIONS
/** /**
* Creates an std::string from the written JSON buffer. * Creates an std::string from the written JSON buffer.
@@ -245,26 +236,6 @@ requires (!std::is_convertible<R, std::string_view>::value && !concepts::optiona
*/ */
simdjson_inline size_t size() const noexcept; simdjson_inline size_t size() const noexcept;
// ============================================================
// Internal hooks for the position-as-local writer in json_builder.h.
// These exist so the reflection atom code can hold buffer pointer,
// position and capacity in registers across long write chains rather
// than reloading them after every char* write (strict aliasing
// forces those reloads when accessed via members of *this). User
// code should NOT call these directly.
// ============================================================
simdjson_inline char *unsafe_data() noexcept { return buffer.get(); }
simdjson_inline size_t unsafe_position() const noexcept { return position; }
simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; }
simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; }
/// Make capacity available for at least `n` more bytes after the current
/// position. Returns false if the allocation failed.
simdjson_inline bool unsafe_grow(size_t needed_total_capacity) noexcept {
grow_buffer(needed_total_capacity);
return is_valid;
}
simdjson_inline bool unsafe_is_valid() const noexcept { return is_valid; }
private: private:
/** /**
* Returns true if we can write at least upcoming_bytes bytes. * Returns true if we can write at least upcoming_bytes bytes.
@@ -278,7 +249,7 @@ private:
* If the allocation fails, is_valid is set to false. We expect * If the allocation fails, is_valid is set to false. We expect
* that this function would not be repeatedly called. * that this function would not be repeatedly called.
*/ */
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. * 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 { inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept {
if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; }
// Stage 1 index output // Stage 1 index output
size_t rounded_capacity = SIMDJSON_ROUNDUP_N(capacity, 64); size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7;
if(rounded_capacity + 9 < rounded_capacity) {
return CAPACITY; // overflow, only happen on legacy 32-bit systems with very large capacity
}
size_t max_structures = rounded_capacity + 9;
structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] );
if (!structural_indexes) { _capacity = 0; return MEMALLOC; } if (!structural_indexes) { _capacity = 0; return MEMALLOC; }
structural_indexes[0] = 0; structural_indexes[0] = 0;
@@ -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 { inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept {
if(max_depth > SIMDJSON_MAX_DEPTH) { return CAPACITY; }
// Stage 2 stacks // Stage 2 stacks
open_containers.reset(new (std::nothrow) open_container[max_depth]); open_containers.reset(new (std::nothrow) open_container[max_depth]);
is_array.reset(new (std::nothrow) bool[max_depth]); is_array.reset(new (std::nothrow) bool[max_depth]);
+4 -62
View File
@@ -4,7 +4,6 @@
#define SIMDJSON_GENERIC_NUMBERPARSING_H #define SIMDJSON_GENERIC_NUMBERPARSING_H
#include "simdjson/generic/base.h" #include "simdjson/generic/base.h"
#include "simdjson/generic/jsoncharutils.h" #include "simdjson/generic/jsoncharutils.h"
#include "simdjson/generic/atomparsing.h"
#include "simdjson/internal/numberparsing_tables.h" #include "simdjson/internal/numberparsing_tables.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE #endif // SIMDJSON_CONDITIONAL_INCLUDE
@@ -300,24 +299,6 @@ simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative,
return true; return true;
} }
#if SIMDJSON_ENABLE_NAN_INF
// Parses a nan or infinity. Returns true on success, false on failure.
simdjson_unused simdjson_inline bool compute_nan_inf(const uint8_t* src, bool negative, double& d) noexcept {
if (atomparsing::is_valid_inf_atom(src)) {
double inf = std::numeric_limits<double>::infinity();
d = negative ? -inf : inf;
return true;
}
if (atomparsing::is_valid_nan_atom(src)) {
d = std::numeric_limits<double>::quiet_NaN();
return true;
}
return false;
}
#endif
// We call a fallback floating-point parser that might be slow. Note // We call a fallback floating-point parser that might be slow. Note
// it will accept JSON numbers, but the JSON spec. is more restrictive so // it will accept JSON numbers, but the JSON spec. is more restrictive so
// before you call parse_float_fallback, you need to have validated the input // before you call parse_float_fallback, you need to have validated the input
@@ -497,7 +478,7 @@ simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t d
} // unnamed namespace } // unnamed namespace
/** @private */ /** @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)) { if (parse_float_fallback(src, answer)) {
return SUCCESS; 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. // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error.
// Optimization note: size_t is expected to be unsigned. // Optimization note: size_t is expected to be unsigned.
size_t digit_count = size_t(p - start_digits); size_t digit_count = size_t(p - start_digits);
if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); }
#if SIMDJSON_ENABLE_NAN_INF
// By this point, we know that our input does not begin with a digit. We will attempt
// to handle NaN/Infinity.
double d;
if (compute_nan_inf(p, negative, d)) {
writer.append_double(d);
return SUCCESS;
}
#endif
return INVALID_NUMBER(src);
}
// //
// Handle floats if there is a . or e (or both) // Handle floats if there is a . or e (or both)
@@ -1064,17 +1031,7 @@ simdjson_unused simdjson_inline simdjson_result<double> parse_double(const uint8
bool leading_zero = (i == 0); bool leading_zero = (i == 0);
while (parse_digit(*p, i)) { p++; } while (parse_digit(*p, i)) { p++; }
// no integer digits, or 0123 (zero must be solo) // no integer digits, or 0123 (zero must be solo)
if ( p == src ) { if ( p == src ) { return INCORRECT_TYPE; }
#if SIMDJSON_ENABLE_NAN_INF
// If there are no loading digits, the number may be nan or infinity.
// Attempt to compute those, and return on success.
double d;
if (compute_nan_inf(p, negative, d)) { return d; }
#endif
return INCORRECT_TYPE;
}
if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; }
// //
@@ -1292,22 +1249,7 @@ simdjson_unused simdjson_inline simdjson_result<double> parse_double_in_string(c
bool leading_zero = (i == 0); bool leading_zero = (i == 0);
while (parse_digit(*p, i)) { p++; } while (parse_digit(*p, i)) { p++; }
// no integer digits, or 0123 (zero must be solo) // no integer digits, or 0123 (zero must be solo)
if ( p == src ) { if ( p == src ) { return INCORRECT_TYPE; }
#if SIMDJSON_ENABLE_NAN_INF
// If there are no leading digits, attempt to parse numbers that are either
// NaN or Infinity
if (atomparsing::is_valid_inf_in_string(src)) {
double inf = std::numeric_limits<double>::infinity();
return negative ? -inf : inf;
}
if (atomparsing::is_valid_nan_in_string(src)) {
return std::numeric_limits<double>::quiet_NaN();
}
#endif
return INCORRECT_TYPE;
}
if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; }
// //
@@ -22,7 +22,6 @@
#include "simdjson/generic/ondemand/field.h" #include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/object.h" #include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h" #include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/ranges.h"
#include "simdjson/generic/ondemand/serialization.h" #include "simdjson/generic/ondemand/serialization.h"
// Deserialization for standard types // Deserialization for standard types
@@ -40,7 +39,6 @@
#include "simdjson/generic/ondemand/logger-inl.h" #include "simdjson/generic/ondemand/logger-inl.h"
#include "simdjson/generic/ondemand/object-inl.h" #include "simdjson/generic/ondemand/object-inl.h"
#include "simdjson/generic/ondemand/object_iterator-inl.h" #include "simdjson/generic/ondemand/object_iterator-inl.h"
#include "simdjson/generic/ondemand/ranges-inl.h"
#include "simdjson/generic/ondemand/parser-inl.h" #include "simdjson/generic/ondemand/parser-inl.h"
#include "simdjson/generic/ondemand/raw_json_string-inl.h" #include "simdjson/generic/ondemand/raw_json_string-inl.h"
#include "simdjson/generic/ondemand/token_iterator-inl.h" #include "simdjson/generic/ondemand/token_iterator-inl.h"

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