mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 812f8823d2 | |||
| 1726505f66 | |||
| 98fde69253 | |||
| dc485f845d | |||
| 79bbba3e3e | |||
| 1a29a5e53a | |||
| 57561f64f4 | |||
| 00563f9133 | |||
| 5db72ab9d3 | |||
| d3d99b8fc5 | |||
| 168ef58075 | |||
| fb8c575121 | |||
| 7732480b25 | |||
| 1ae93a70d4 | |||
| 396ca141fd | |||
| 8e2e9b8e02 | |||
| 599895e54d | |||
| 77a53d19a2 | |||
| 8c7cc2f963 | |||
| 43766fc7ef | |||
| c2f25ab455 |
@@ -58,7 +58,7 @@ We support up-to-date 64-bit ARM and x64 FreeBSD, macOS, Windows and Linux syste
|
||||
* We do not support unreleased or experimental compilers. If you encounter an issue with a
|
||||
pre-release version of a compiler, do not report it as a bug to simdjson. However, we always
|
||||
invite contributions either in the form an analysis or of a code contribution.
|
||||
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support.
|
||||
* Vendors (e.g., Apple and Microsoft) stop supporting old systems. Once a compiler system is no longer supported by its vendor, we no longer support it. We will gladly accept code contributions, but we do not consider it a *bug* if you have issues with an obsolete compiler systems. This policy extends to obsolete standard libraries, linkers and other build tools. Please do not report it as an issue. If you cannot resolve the issue yourself, we encourage you to reach out to the vendor for legacy support. As of 2026, Windows 10 is no longer supported.
|
||||
|
||||
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: uraimo/run-on-arch-action@v3
|
||||
name: Test
|
||||
id: runcmd
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
image: debian:testing
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Doxygen
|
||||
run: sudo apt-get install doxygen graphviz -y
|
||||
- run: mkdir docs
|
||||
|
||||
@@ -4,14 +4,14 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14
|
||||
- name: Verify
|
||||
run: emcc -v
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.6.0
|
||||
uses: actions/checkout@v6
|
||||
- name: Configure
|
||||
run: emcmake cmake -B build
|
||||
- name: Build # We build but do not test
|
||||
run: cmake --build build
|
||||
run: cmake --build build
|
||||
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
whitespace:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Remove whitespace and check the diff
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh $CLANGVERSION
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
id: cache-corpus
|
||||
with:
|
||||
path: out/
|
||||
|
||||
@@ -11,13 +11,13 @@ jobs:
|
||||
platform:
|
||||
- { toolchain-version: 2023.08.08 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install build requirements
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y --no-install-recommends cmake
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v5
|
||||
id: restore-cache
|
||||
with:
|
||||
path: /opt/cross-tools
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
mkdir -p /opt
|
||||
tar -C /opt -x -f /tmp/toolchain.tar.xz
|
||||
|
||||
- uses: actions/cache/save@v3
|
||||
- uses: actions/cache/save@v5
|
||||
if: ${{ !steps.restore-cache.outputs.cache-hit }}
|
||||
with:
|
||||
path: /opt/cross-tools
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -27,8 +27,8 @@ jobs:
|
||||
CMAKE_GENERATOR: Ninja
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -29,8 +29,8 @@ jobs:
|
||||
CMAKE_GENERATOR: Ninja
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: uraimo/run-on-arch-action@v3
|
||||
name: Test
|
||||
id: runcmd
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: uraimo/run-on-arch-action@v3
|
||||
name: Test
|
||||
id: runcmd
|
||||
@@ -26,4 +26,4 @@ jobs:
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
|
||||
cmake --build build -j=2
|
||||
ctest --output-on-failure --test-dir build
|
||||
ctest --output-on-failure --test-dir build -E ondemand_cacheline
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install packages
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install packages
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install packages
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install packages
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install packages
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: uraimo/run-on-arch-action@v3
|
||||
name: Test
|
||||
id: runcmd
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
@@ -20,4 +20,4 @@ jobs:
|
||||
cd build &&
|
||||
CXX=clang++-13 cmake -DSIMDJSON_DEVELOPER_MODE=ON .. &&
|
||||
cmake --build . &&
|
||||
ctest --output-on-failure -LE explicitonly -j
|
||||
ctest --output-on-failure -LE explicitonly -j
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
@@ -20,4 +20,4 @@ jobs:
|
||||
cd build &&
|
||||
CXX=g++-12 cmake -DSIMDJSON_DEVELOPER_MODE=ON .. &&
|
||||
cmake --build . &&
|
||||
ctest --output-on-failure -LE explicitonly -j
|
||||
ctest --output-on-failure -LE explicitonly -j
|
||||
|
||||
@@ -8,8 +8,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
@@ -21,4 +21,4 @@ jobs:
|
||||
cd build &&
|
||||
CXX=g++-12 cmake -DCMAKE_BUILD_TYPE=Debug -DSIMDJSON_GLIBCXX_ASSERTIONS=ON -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_DEVELOPER_MODE=ON .. &&
|
||||
cmake --build . &&
|
||||
ctest . -E avoid_
|
||||
ctest . -E avoid_
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
@@ -21,4 +21,4 @@ jobs:
|
||||
CXX=g++-12 cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_SANITIZE_THREADS=ON .. &&
|
||||
cmake --build . --target document_stream_tests --target ondemand_document_stream_tests --target parse_many_test &&
|
||||
ctest --output-on-failure -R parse_many_test &&
|
||||
ctest --output-on-failure -R document_stream_tests
|
||||
ctest --output-on-failure -R document_stream_tests
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -15,8 +15,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Ubuntu 24.04 CI (CLANG 20)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
if: >-
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@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:
|
||||
cxx: [g++-13, clang++-16]
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
- uses: actions/checkout@v6
|
||||
- name: Prepare
|
||||
run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_EXCEPTIONS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -B build
|
||||
env:
|
||||
@@ -19,4 +19,4 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build build -j=2
|
||||
- name: Test
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
matrix:
|
||||
cxx: [g++-13, clang++-16]
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
- uses: actions/checkout@v6
|
||||
- name: Prepare
|
||||
run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_DEVELOPER_MODE=ON -B build
|
||||
env:
|
||||
@@ -19,4 +19,4 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build build -j=2
|
||||
- name: Test
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
run: ctest --output-on-failure --test-dir build
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -9,8 +9,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
@@ -34,8 +34,8 @@ jobs:
|
||||
! contains(toJSON(github.event.commits.*.message), '[skip github]')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
nan_inf: [ON, OFF]
|
||||
build_type: [RelWithDebInfo, Debug, Release]
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
- uses: actions/checkout@v6
|
||||
- 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
|
||||
env:
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
- {arch: ARM64EC}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Use cmake
|
||||
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 &&
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Configure
|
||||
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
|
||||
@@ -41,4 +41,4 @@ jobs:
|
||||
- name: Test Installation
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -B build_install_test tests/installation_tests/find
|
||||
cmake --build build_install_test --config Release
|
||||
cmake --build build_install_test --config Release
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSANITIZE=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
|
||||
@@ -27,4 +27,4 @@ jobs:
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd build
|
||||
ctest -C ${{matrix.build_type}} -LE explicitonly --output-on-failure
|
||||
ctest -C ${{matrix.build_type}} -LE explicitonly --output-on-failure
|
||||
|
||||
@@ -15,17 +15,15 @@ jobs:
|
||||
include:
|
||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF, nan_inf: ON}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF, nan_inf: ON}
|
||||
# Exercise the opt-in Windows memory-file mapping path at least once in CI.
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: ON, nan_inf: OFF}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: ON, nan_inf: ON}
|
||||
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON, nan_inf: ON}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=${{matrix.memory_map}} -DSIMDJSON_ENABLE_NAN_INF=${{matrix.nan_inf}} -B build
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- {gen: Visual Studio 17 2022, arch: x64, build_type: RelWithDebInfo}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
|
||||
@@ -34,4 +34,4 @@ jobs:
|
||||
- name: Test Installation
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -B build_install_test tests/installation_tests/find
|
||||
cmake --build build_install_test --config ${{matrix.build_type}}
|
||||
cmake --build build_install_test --config ${{matrix.build_type}}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- {gen: Visual Studio 17 2022, arch: x64, build_type: RelWithDebInfo}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -B build
|
||||
@@ -34,4 +34,4 @@ jobs:
|
||||
- name: Test Installation
|
||||
run: |
|
||||
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -B build_install_test tests/installation_tests/find
|
||||
cmake --build build_install_test --config ${{matrix.build_type}}
|
||||
cmake --build build_install_test --config ${{matrix.build_type}}
|
||||
|
||||
@@ -7,8 +7,8 @@ jobs:
|
||||
name: windows-vs17
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: dependencies/.cache
|
||||
key: ${{ hashFiles('dependencies/CMakeLists.txt') }}
|
||||
|
||||
@@ -190,6 +190,7 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
|
||||
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
|
||||
- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port.
|
||||
- [mruby-fast-json](https://github.com/Asmod4n/mruby-fast-json): mruby binding with high API coverage.
|
||||
- [simdjson-dart](https://github.com/xaldarof/simdjson-dart): Dart bindings for the simdjson project.
|
||||
|
||||
About simdjson
|
||||
--------------
|
||||
@@ -200,7 +201,7 @@ CPU's multiple execution cores.
|
||||
|
||||
Our default front-end is called On-Demand, and we wrote a paper about it:
|
||||
|
||||
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](http://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
|
||||
- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](https://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024.
|
||||
|
||||
Some people [enjoy reading the first (2019) simdjson paper](https://arxiv.org/abs/1902.08318): A description of the design
|
||||
and implementation of simdjson is in our research article:
|
||||
@@ -213,12 +214,12 @@ We have an in-depth paper focused on the UTF-8 validation:
|
||||
We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/).
|
||||
|
||||
For the video inclined, we had a talk at QCon San Francisco 2019<br />
|
||||
[](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
[](https://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
|
||||
(It was the best voted talk, we're kinda proud of it.)
|
||||
|
||||
We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether you’re a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk!
|
||||
|
||||
[](http://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
|
||||
[](https://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
|
||||
|
||||
|
||||
|
||||
@@ -271,7 +272,7 @@ This code is made available under the [Apache License 2.0](https://www.apache.or
|
||||
|
||||
Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it is under the liberal (business-friendly) MIT license.
|
||||
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](http://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](https://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution.
|
||||
|
||||
For efficient number serialization, we bundle Florian Loitsch's implementation of the Grisu2 algorithm for binary to decimal floating-point numbers. The implementation was slightly modified by JSON for Modern C++ library. Both Florian Loitsch's implementation and JSON for Modern C++ are provided under the MIT license.
|
||||
|
||||
|
||||
@@ -61,22 +61,29 @@ struct yyjson_base {
|
||||
};
|
||||
|
||||
struct yyjson : yyjson_base {
|
||||
// The document owns the string memory that result's string_views point into,
|
||||
// so it must outlive each run() (the verification diff happens after run()
|
||||
// returns). Free it on the next run() / at destruction, not before the views
|
||||
// are read.
|
||||
yyjson_doc *doc{};
|
||||
~yyjson() { if (doc != nullptr) { yyjson_doc_free(doc); } }
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
|
||||
bool b = yyjson_base::run(doc, result);
|
||||
yyjson_doc_free(doc);
|
||||
return b;
|
||||
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
|
||||
doc = yyjson_read(json.data(), json.size(), 0);
|
||||
return yyjson_base::run(doc, result);
|
||||
}
|
||||
};
|
||||
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
|
||||
|
||||
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
|
||||
struct yyjson_insitu : yyjson_base {
|
||||
// See the note on yyjson above: the document must outlive result's views.
|
||||
yyjson_doc *doc{};
|
||||
~yyjson_insitu() { if (doc != nullptr) { yyjson_doc_free(doc); } }
|
||||
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
|
||||
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
|
||||
bool b = yyjson_base::run(doc, result);
|
||||
yyjson_doc_free(doc);
|
||||
return b;
|
||||
if (doc != nullptr) { yyjson_doc_free(doc); doc = nullptr; }
|
||||
doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
|
||||
return yyjson_base::run(doc, result);
|
||||
}
|
||||
};
|
||||
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#ifndef GLAZE_CITM_CATALOG_DATA_H
|
||||
#define GLAZE_CITM_CATALOG_DATA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <glaze/glaze.hpp>
|
||||
|
||||
// Glaze-specific shadow types. We mirror the Rust serde struct here (rather
|
||||
// than the C++ CitmCatalog) so that fields the source JSON encodes as `null`
|
||||
// (e.g. CITMEvent.name) parse cleanly into std::optional. This matches what
|
||||
// the Rust benchmark does, and the resulting JSON output volume is therefore
|
||||
// directly comparable to the Rust numbers.
|
||||
|
||||
struct GlazeCITMPrice {
|
||||
uint64_t amount;
|
||||
uint64_t audienceSubCategoryId;
|
||||
uint64_t seatCategoryId;
|
||||
};
|
||||
|
||||
struct GlazeCITMArea {
|
||||
uint64_t areaId;
|
||||
std::vector<uint64_t> blockIds;
|
||||
};
|
||||
|
||||
struct GlazeCITMSeatCategory {
|
||||
std::vector<GlazeCITMArea> areas;
|
||||
uint64_t seatCategoryId;
|
||||
};
|
||||
|
||||
struct GlazeCITMPerformance {
|
||||
uint64_t id;
|
||||
uint64_t eventId;
|
||||
std::optional<std::string> logo;
|
||||
std::optional<std::string> name;
|
||||
std::vector<GlazeCITMPrice> prices;
|
||||
std::vector<GlazeCITMSeatCategory> seatCategories;
|
||||
std::optional<std::string> seatMapImage;
|
||||
uint64_t start;
|
||||
std::string venueCode;
|
||||
};
|
||||
|
||||
struct GlazeCITMEvent {
|
||||
uint64_t id;
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> logo;
|
||||
std::vector<uint64_t> subTopicIds;
|
||||
std::optional<std::string> subjectCode;
|
||||
std::optional<std::string> subtitle;
|
||||
std::vector<uint64_t> topicIds;
|
||||
};
|
||||
|
||||
struct GlazeCitmCatalog {
|
||||
std::map<std::string, GlazeCITMEvent> events;
|
||||
std::vector<GlazeCITMPerformance> performances;
|
||||
};
|
||||
|
||||
inline GlazeCitmCatalog glaze_deserialize_citm(const std::string &json_str) {
|
||||
GlazeCitmCatalog data;
|
||||
constexpr glz::opts opts{.error_on_unknown_keys = false};
|
||||
auto err = glz::read<opts>(data, json_str);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze citm parse error: " +
|
||||
glz::format_error(err, json_str));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
inline std::string glaze_serialize_citm(const GlazeCitmCatalog &data) {
|
||||
std::string out;
|
||||
// skip_null_members = false: emit `"field":null` for unset optionals so the
|
||||
// output has the same field count as simdjson's (which writes `"field":""`).
|
||||
// Glaze still produces 4-char `null` vs simdjson's 2-char `""`, so it's not
|
||||
// byte-identical, but the work-per-field is comparable.
|
||||
constexpr glz::opts opts{.skip_null_members = false};
|
||||
auto err = glz::write<opts>(data, out);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze citm write error");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // GLAZE_CITM_CATALOG_DATA_H
|
||||
@@ -1,95 +0,0 @@
|
||||
// Standalone Glaze benchmark for CITM Catalog (see glaze_twitter_bench.cpp
|
||||
// header comment for context).
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "../citm_catalog_benchmark/glaze_citm_catalog_data.h"
|
||||
#include "../benchmark_utils/benchmark_helper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::string read_file(const std::string &filename) {
|
||||
printf("# Reading file %s\n", filename.c_str());
|
||||
constexpr size_t read_size = 65536;
|
||||
std::ifstream stream(filename, std::ios::binary);
|
||||
if (!stream) {
|
||||
std::cerr << "Could not open file: " << filename << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
stream.exceptions(std::ios_base::badbit);
|
||||
std::string out;
|
||||
std::string buf(read_size, '\0');
|
||||
while (stream.read(&buf[0], read_size)) {
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
}
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
return out;
|
||||
}
|
||||
|
||||
void bench_glaze_serialization(GlazeCitmCatalog &data) {
|
||||
std::string output = glaze_serialize_citm(data);
|
||||
size_t output_volume = output.size();
|
||||
printf("# output volume: %zu bytes\n", output_volume);
|
||||
|
||||
volatile size_t measured_volume = 0;
|
||||
pretty_print(1, output_volume, "bench_glaze",
|
||||
bench([&data, &measured_volume, &output_volume]() {
|
||||
std::string output = glaze_serialize_citm(data);
|
||||
measured_volume = output.size();
|
||||
if (measured_volume != output_volume) {
|
||||
printf("mismatch\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void bench_glaze_parsing(const std::string &json_str) {
|
||||
size_t input_volume = json_str.size();
|
||||
printf("# input volume: %zu bytes\n", input_volume);
|
||||
|
||||
volatile bool result = true;
|
||||
pretty_print(1, input_volume, "bench_glaze_parsing",
|
||||
bench([&json_str, &result]() {
|
||||
try {
|
||||
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
|
||||
result = true;
|
||||
} catch (...) {
|
||||
result = false;
|
||||
printf("parse error\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *json_file = std::getenv("CITM_JSON");
|
||||
if (!json_file) {
|
||||
json_file = "buildreflect/jsonexamples/citm_catalog.json";
|
||||
}
|
||||
if (argc > 1) {
|
||||
json_file = argv[1];
|
||||
}
|
||||
|
||||
std::string json_str = read_file(json_file);
|
||||
|
||||
const char *mode = std::getenv("BENCH_MODE");
|
||||
if (!mode) mode = "all";
|
||||
|
||||
GlazeCitmCatalog data = glaze_deserialize_citm(json_str);
|
||||
|
||||
if (std::string(mode) == "all" || std::string(mode) == "parse") {
|
||||
printf("\n=== Glaze CITM Parsing ===\n");
|
||||
bench_glaze_parsing(json_str);
|
||||
}
|
||||
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
|
||||
printf("\n=== Glaze CITM Serialization ===\n");
|
||||
bench_glaze_serialization(data);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// Standalone Glaze benchmark for Twitter, built with g++ instead of the
|
||||
// p2996 clang fork (which crashes on Glaze's heavy template metaprogramming).
|
||||
// Reuses the same data structs and bench() helper as the in-tree benchmarks
|
||||
// so the throughput numbers are directly comparable.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../twitter_benchmark/twitter_data.h"
|
||||
#include "../twitter_benchmark/glaze_twitter_data.h"
|
||||
#include "../benchmark_utils/benchmark_helper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::string read_file(const std::string &filename) {
|
||||
printf("# Reading file %s\n", filename.c_str());
|
||||
constexpr size_t read_size = 4096;
|
||||
std::ifstream stream(filename);
|
||||
if (!stream) {
|
||||
std::cerr << "Could not open file: " << filename << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
stream.exceptions(std::ios_base::badbit);
|
||||
std::string out;
|
||||
std::string buf(read_size, '\0');
|
||||
while (stream.read(&buf[0], read_size)) {
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
}
|
||||
out.append(buf, 0, size_t(stream.gcount()));
|
||||
return out;
|
||||
}
|
||||
|
||||
void bench_glaze_serialization(TwitterData &data) {
|
||||
std::string output = glaze_serialize(data);
|
||||
size_t output_volume = output.size();
|
||||
printf("# output volume: %zu bytes\n", output_volume);
|
||||
|
||||
volatile size_t measured_volume = 0;
|
||||
pretty_print(1, output_volume, "bench_glaze",
|
||||
bench([&data, &measured_volume, &output_volume]() {
|
||||
std::string output = glaze_serialize(data);
|
||||
measured_volume = output.size();
|
||||
if (measured_volume != output_volume) {
|
||||
printf("mismatch\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void bench_glaze_parsing(const std::string &json_str) {
|
||||
size_t input_volume = json_str.size();
|
||||
printf("# input volume: %zu bytes\n", input_volume);
|
||||
|
||||
volatile bool result = true;
|
||||
pretty_print(1, input_volume, "bench_glaze_parsing",
|
||||
bench([&json_str, &result]() {
|
||||
try {
|
||||
TwitterData data = glaze_deserialize(json_str);
|
||||
result = true;
|
||||
} catch (...) {
|
||||
result = false;
|
||||
printf("parse error\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *json_file = std::getenv("TWITTER_JSON");
|
||||
if (!json_file) {
|
||||
json_file = "buildreflect/jsonexamples/twitter.json";
|
||||
}
|
||||
if (argc > 1) {
|
||||
json_file = argv[1];
|
||||
}
|
||||
|
||||
std::string json_str = read_file(json_file);
|
||||
|
||||
const char *mode = std::getenv("BENCH_MODE");
|
||||
if (!mode) mode = "all";
|
||||
TwitterData data = glaze_deserialize(json_str);
|
||||
|
||||
if (std::string(mode) == "all" || std::string(mode) == "parse") {
|
||||
printf("\n=== Glaze Twitter Parsing ===\n");
|
||||
bench_glaze_parsing(json_str);
|
||||
}
|
||||
if (std::string(mode) == "all" || std::string(mode) == "serialize") {
|
||||
printf("\n=== Glaze Twitter Serialization ===\n");
|
||||
bench_glaze_serialization(data);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef GLAZE_TWITTER_DATA_H
|
||||
#define GLAZE_TWITTER_DATA_H
|
||||
|
||||
#include "twitter_data.h"
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
// Glaze auto-reflects aggregate types whose field names already match JSON
|
||||
// keys (snake_case here matches the JSON), so no glz::meta is required.
|
||||
|
||||
inline TwitterData glaze_deserialize(const std::string &json_str) {
|
||||
TwitterData data;
|
||||
constexpr glz::opts opts{.error_on_unknown_keys = false};
|
||||
auto err = glz::read<opts>(data, json_str);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze parse error: " + glz::format_error(err, json_str));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
inline std::string glaze_serialize(const TwitterData &data) {
|
||||
std::string out;
|
||||
auto err = glz::write_json(data, out);
|
||||
if (err) {
|
||||
throw std::runtime_error("glaze write error");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // GLAZE_TWITTER_DATA_H
|
||||
+115
-7
@@ -23,6 +23,7 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
* [2. Use `tag_invoke` for custom types (C++20)](#2-use-tag_invoke-for-custom-types-c20)
|
||||
* [3. Using static reflection (C++26)](#3-using-static-reflection-c26)
|
||||
+ [Special cases](#special-cases)
|
||||
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
|
||||
* [The simdjson::from shortcut (experimental, C++20)](#the-simdjsonfrom-shortcut-experimental-c20)
|
||||
- [Minifying JSON strings without parsing](#minifying-json-strings-without-parsing)
|
||||
- [UTF-8 validation (alone)](#utf-8-validation-alone)
|
||||
@@ -42,6 +43,7 @@ separate document](https://github.com/simdjson/simdjson/blob/master/doc/builder.
|
||||
- [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines)
|
||||
- [Parsing numbers inside strings](#parsing-numbers-inside-strings)
|
||||
- [Dynamic Number Types](#dynamic-number-types)
|
||||
- [Infinity and NaN support](#infinity-and-nan-support)
|
||||
- [Raw strings from keys](#raw-strings-from-keys)
|
||||
- [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string)
|
||||
* [Raw JSON string for objects and arrays](#raw-json-string-for-objects-and-arrays)
|
||||
@@ -455,7 +457,7 @@ We also have a generic ephemeral type (`simdjson::ondemand::value`) which repres
|
||||
array or object, or scalar type (`double`, `uint64_t`, `int64_t`, `bool`, `null`, string) inside
|
||||
an array or an object. Both generic types (`simdjson::ondemand::document` and
|
||||
`simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing indicating the type (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`,
|
||||
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens such as the `NaN` token in `{"key":NaN}`. A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
|
||||
`json_type::boolean`, `json_type::null`, and `json_type::unknown` for unrecognized types). The `type()` method does not consume nor validate the value: e.g., you must still call `is_null()` to check that the value is a `null` even if `json_type::null` is returned. Starting with simdjson 4.0, we return `json_type::unknown` for bad tokens (such as the `NaN` token in `{"key":NaN}` when using the default strict parsing behavior). A `json_type::unknown` type value indicates an error in the JSON document but you might still be able to proceed, see [General direct access to the raw JSON string](#general-direct-access-to-the-raw-json-string). A generic value (`simdjson::ondemand::value`)
|
||||
is only valid temporarily, as soon as you access other values, other keys in objects, etc.
|
||||
it becomes invalid: you should therefore consume the value immediately by converting it to a
|
||||
scalar type, an array or an object.
|
||||
@@ -1591,6 +1593,51 @@ You can also automatically serialize the `Car` instance to a JSON string, see
|
||||
our [Builder documentation](builder.md).
|
||||
|
||||
|
||||
#### Renaming and skipping fields with annotations
|
||||
|
||||
**This is experimental: the syntax may change slightly in the future.**
|
||||
|
||||
C++26 annotations provide a convenient way to customize (de)serialization
|
||||
without writing `tag_invoke` functions. You can rename the JSON key that
|
||||
corresponds to a C++ data member, or skip a member entirely.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```cpp
|
||||
// rename cppFieldName (in C++) to json_key_name (in JSON)
|
||||
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
|
||||
// do not serialize or deserialize this field
|
||||
[[= simdjson::skip]] int internalState;
|
||||
```
|
||||
|
||||
Full examples:
|
||||
|
||||
```cpp
|
||||
struct RenamedFields {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
struct SkippedField {
|
||||
std::string name = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
};
|
||||
|
||||
struct MixedAnnotations {
|
||||
[[= simdjson::rename<"user_name">]] std::string userName = "";
|
||||
[[= simdjson::skip]] int sessionToken = 0;
|
||||
int age = 0;
|
||||
};
|
||||
```
|
||||
|
||||
- Serialization via `simdjson::to_json(r)` or `builder << r` will use the renamed
|
||||
keys and omit skipped fields.
|
||||
- Deserialization via `doc.get<RenamedFields>()` will map the JSON keys back
|
||||
to the C++ fields. Skipped fields are never written during deserialization
|
||||
(they keep their default-initialized value), and keys matching skipped fields
|
||||
in the JSON input are ignored.
|
||||
|
||||
|
||||
### The simdjson::from shortcut (experimental, C++20)
|
||||
|
||||
@@ -2647,7 +2694,7 @@ Market: btce Price: 432.89 Volume: 8561.06
|
||||
*/
|
||||
```
|
||||
|
||||
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value.
|
||||
Finally, here is an example dealing with errors where the user wants to convert the string `"Infinity"`(`"change"` key) to a float with infinity value when using the default strict parsing behavior:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
@@ -2662,7 +2709,7 @@ if (error) {
|
||||
error = value.get_string().get(view);
|
||||
if (error) { /* Handle error */ }
|
||||
else if (view == "Infinity") {
|
||||
d = std::numeric_limits::infinity();
|
||||
d = std::numeric_limits<double>::infinity();
|
||||
}
|
||||
else { /* Handle wrong value */ }
|
||||
}
|
||||
@@ -2670,6 +2717,15 @@ if (error) {
|
||||
It is also important to note that when dealing an invalid number inside a string, simdjson will report a `NUMBER_ERROR` error if the string begins with a number whereas simdjson
|
||||
will report an `INCORRECT_TYPE` error otherwise.
|
||||
|
||||
When `SIMDJSON_ENABLE_NAN_INF` is enabled, simdjson can parse `"Infinity"`, `"-Infinity"`, and `"NaN"` from a string using `get_double_in_string` without needing extra error handling:
|
||||
|
||||
```cpp
|
||||
ondemand::parser parser;
|
||||
auto doc = parser.iterate(json);
|
||||
// Get "change"/"Infinity" key/value pair as a double.
|
||||
double d = doc["ticker"]["change"].get_double_in_string();
|
||||
```
|
||||
|
||||
The `*_in_string` methods can also be called on a single document instance:
|
||||
e.g., when your document consist solely of a quoted number.
|
||||
|
||||
@@ -2809,6 +2865,53 @@ This code prints the following:
|
||||
'99999999999999999999999 '
|
||||
```
|
||||
|
||||
|
||||
Infinity and NaN support
|
||||
------------------------------
|
||||
|
||||
The JSON specification does not support `Infinity` and `NaN` literals. However, some engineers use literal `Infinity` and `NaN` tokens when serializing floating-point values to JSON.
|
||||
|
||||
The simdjson library achieves maximum JSON parsing performance by adhering to a strict interpretation of the JSON specification. Therefore strict parsing is enabled by default - `Infinity` and `NaN` literals are not parsed as valid JSON.
|
||||
|
||||
Users can opt-in to parsing `Infinity`, `-Infinity`, and `NaN` as `double` values by setting the `SIMDJSON_ENABLE_NAN_INF` flag to `ON` when building simdjson: `cmake -B build -D SIMDJSON_ENABLE_NAN_INF=ON` and setting `SIMDJSON_ENABLE_NAN_INF` to 1 before including `"simdjson.h"`. When enabled, `Infinity`, `-Infinity`, `Inf`, `-Inf`, and `NaN` literals are case-insensitively matched and parsed as `double`:
|
||||
|
||||
```cpp
|
||||
// The SIMDJSON_ENABLE_NAN_INF flag also needs to be set before including simdjson.h
|
||||
#define SIMDJSON_ENABLE_NAN_INF 1
|
||||
#include "simdjson.h"
|
||||
using namespace simdjson;
|
||||
|
||||
// ...
|
||||
|
||||
ondemand::parser parser;
|
||||
auto inf_nan_literals = "[Infinity, -Infinity, NaN, inf, -inf, nan]"_padded;
|
||||
ondemand::document doc = parser.iterate(inf_nan_literals);
|
||||
|
||||
// Parse and iterate through the array of literal inf/nan values.
|
||||
for (ondemand::value val: doc.get_array()) {
|
||||
ondemand::number num = val.get_number();
|
||||
ondemand::number_type t = num.get_number_type();
|
||||
|
||||
if (t == ondemand::number_type::floating_point_number) {
|
||||
std::cout << "Parsed floating-point number: " << num.get_double() << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to parse." << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
produces the following output:
|
||||
|
||||
```
|
||||
Parsed floating-point number: inf
|
||||
Parsed floating-point number: -inf
|
||||
Parsed floating-point number: nan
|
||||
Parsed floating-point number: inf
|
||||
Parsed floating-point number: -inf
|
||||
Parsed floating-point number: nan
|
||||
```
|
||||
|
||||
|
||||
Raw strings from keys
|
||||
-----------
|
||||
|
||||
@@ -2907,7 +3010,7 @@ std::string_view noquote(std::string_view v) { return {v.data()+1, v.find_last_o
|
||||
|
||||
|
||||
The `raw_json_token()` method can enable you to provide fallbacks when parsing fails.
|
||||
Consider the following example.
|
||||
Consider the following example under the default strict parsing behavior (`NaN` parsing is disabled):
|
||||
|
||||
```cpp
|
||||
padded_string json = "{\"key\": NaN}"_padded;
|
||||
@@ -2929,6 +3032,9 @@ Consider the following example.
|
||||
The NaN is not supported in JSON. However, in the On-Demand API, you can check
|
||||
the string corresponding to the JSON token and determine how to handle it.
|
||||
|
||||
In this particular case, `SIMDJSON_ENABLE_NAN_INF` can be enabled to parse `Infinity` and `NaN` tokens.
|
||||
However other tokens will still need to be handled via the `raw_json_token()` method.
|
||||
|
||||
### Raw JSON string for objects and arrays
|
||||
|
||||
If your value is an array or an object, `raw_json_token()` returns effectively a single
|
||||
@@ -2952,8 +3058,10 @@ simdjson::ondemand::array arr = doc.get_array();
|
||||
string_view token = arr.raw_json(); // gives you `[1,2,3]`
|
||||
```
|
||||
|
||||
Because `raw_json()` consumes to object or the array, if you want to both have
|
||||
access to the raw string, and also use the array or object, you should call `reset()`.
|
||||
Because `raw_json()` consumes the object or the array, if you want both the raw
|
||||
substring and later field access on the same instance, call `reset()` on that
|
||||
object or array (as below). To re-parse the whole document from the start,
|
||||
use `document::rewind()` instead (see [Rewinding](#rewinding)).
|
||||
|
||||
```cpp
|
||||
simdjson::ondemand::parser parser;
|
||||
@@ -2961,7 +3069,7 @@ simdjson::padded_string docdata = R"({"value":123})"_padded;
|
||||
simdjson::ondemand::document doc = parser.iterate(docdata);
|
||||
simdjson::ondemand::object obj = doc.get_object();
|
||||
string_view token = obj.raw_json(); // gives you `{"value":123}`
|
||||
obj.reset(); // revise the object
|
||||
obj.reset(); // re-open the same object for further iteration
|
||||
uint64_t x = obj["value"]; // gives me 123
|
||||
```
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ speed and high convenience.
|
||||
* [Overview: string_builder](#overview--string-builder)
|
||||
* [Example: string_builder](#example--string-builder)
|
||||
* [C++26 static reflection](#c--26-static-reflection)
|
||||
+ [Renaming and skipping fields with annotations](#renaming-and-skipping-fields-with-annotations)
|
||||
+ [Without `string_buffer` instance](#without--string-buffer--instance)
|
||||
+ [Without `string_buffer` instance but with explicit error handling](#without--string-buffer--instance-but-with-explicit-error-handling)
|
||||
+ [Pretty formatted (fractured JSON)](#pretty-formatted-fractured-json)
|
||||
@@ -260,6 +261,48 @@ automatically. In most cases, it should work automatically:
|
||||
```
|
||||
|
||||
|
||||
#### Renaming and skipping fields with annotations
|
||||
|
||||
|
||||
**This is experimental: the syntax may change slightly in the future.**
|
||||
|
||||
When using C++26 static reflection for automatic serialization (and deserialization),
|
||||
you can annotate your struct members to rename the corresponding JSON keys or to
|
||||
exclude fields from the JSON representation.
|
||||
|
||||
|
||||
The syntax is:
|
||||
|
||||
```cpp
|
||||
// rename cppFieldName (in C++) to json_key_name (in JSON)
|
||||
[[= simdjson::rename<"json_key_name">]] std::string cppFieldName;
|
||||
// do not serialize or deserialize this field
|
||||
[[= simdjson::skip]] int internalState;
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
int age = 0;
|
||||
};
|
||||
```
|
||||
|
||||
Serialization then produces:
|
||||
|
||||
```cpp
|
||||
Person p{"Alice", "Smith", 999, 30};
|
||||
std::string json = simdjson::to_json(p);
|
||||
// json == R"({"first_name":"Alice","last_name":"Smith","age":30})"
|
||||
// Note: internalCache is omitted entirely.
|
||||
```
|
||||
|
||||
The `skip` annotation also affects deserialization: the field keeps its default value
|
||||
and any corresponding key in the input JSON is ignored.
|
||||
|
||||
### Without `string_buffer` instance
|
||||
|
||||
In some instances, you might want to create a string directly from your own data type.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef SIMDJSON_ANNOTATIONS_H
|
||||
#define SIMDJSON_ANNOTATIONS_H
|
||||
|
||||
/**
|
||||
* @file annotations.h
|
||||
* @brief Provides compile-time annotations for simdjson structures.
|
||||
* This header defines annotations that can be applied to data members of structures
|
||||
* to control how they are serialized/deserialized with simdjson.
|
||||
* This is currently experimental and subject to change (syntax and semantics may evolve).
|
||||
*/
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
#include <meta>
|
||||
#include <string_view>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// Structural compile-time string — char array avoids the pointer-based
|
||||
// 'reflect_constant failed' that occurs with const char* / string_view members.
|
||||
template <size_t N>
|
||||
struct fixed_string {
|
||||
char data[N];
|
||||
|
||||
consteval fixed_string(const char (&s)[N]) noexcept {
|
||||
for (size_t i = 0; i < N; ++i) { data[i] = s[i]; }
|
||||
}
|
||||
|
||||
consteval std::string_view view() const noexcept { return {data, N - 1}; }
|
||||
|
||||
consteval bool operator==(const fixed_string&) const noexcept = default;
|
||||
};
|
||||
|
||||
// Usage: [[= simdjson::rename<"first_name">]] std::string firstName;
|
||||
namespace detail {
|
||||
template <fixed_string Name>
|
||||
struct rename_t {
|
||||
static constexpr auto name = Name;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <fixed_string Name>
|
||||
inline constexpr detail::rename_t<Name> rename{};
|
||||
|
||||
// Usage: [[= simdjson::skip]] int internalCache;
|
||||
namespace detail {
|
||||
struct skip_tag {};
|
||||
} // namespace detail
|
||||
|
||||
inline constexpr detail::skip_tag skip{};
|
||||
|
||||
// Returns the JSON key for a reflected data member.
|
||||
template <auto dm>
|
||||
consteval const char* get_json_key_name() {
|
||||
template for (constexpr auto ann :
|
||||
std::define_static_array(std::meta::annotations_of(dm))) {
|
||||
constexpr auto ann_type = std::meta::type_of(ann);
|
||||
if constexpr (std::meta::has_template_arguments(ann_type) &&
|
||||
std::meta::template_of(ann_type) == ^^detail::rename_t) {
|
||||
constexpr auto args =
|
||||
std::define_static_array(std::meta::template_arguments_of(ann_type));
|
||||
return std::define_static_string([:args[0]:].view());
|
||||
}
|
||||
}
|
||||
return std::define_static_string(std::meta::identifier_of(dm));
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_ANNOTATIONS_H
|
||||
@@ -72,7 +72,7 @@ inline element_metrics structure_analyzer::analyze_scalar(const dom::element& el
|
||||
metrics.complexity = 0;
|
||||
metrics.child_count = 0;
|
||||
metrics.can_inline = true;
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
|
||||
switch (elem.type()) {
|
||||
case dom::element_type::STRING: {
|
||||
@@ -158,16 +158,16 @@ inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
|
||||
|
||||
// Decide layout
|
||||
if (metrics.child_count == 0) {
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
} else if (metrics.can_inline) {
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
} else if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
|
||||
metrics.recommended_layout = layout_mode::TABLE;
|
||||
metrics.recommended_layout = layout_mode::table;
|
||||
} else if (current_opts_->enable_compact_multiline &&
|
||||
max_child_complexity <= current_opts_->max_compact_array_complexity) {
|
||||
metrics.recommended_layout = layout_mode::COMPACT_MULTILINE;
|
||||
metrics.recommended_layout = layout_mode::compact_multiline;
|
||||
} else {
|
||||
metrics.recommended_layout = layout_mode::EXPANDED;
|
||||
metrics.recommended_layout = layout_mode::expanded;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
@@ -206,9 +206,9 @@ inline element_metrics structure_analyzer::analyze_object(const dom::object& obj
|
||||
|
||||
// Objects use inline or expanded (no table/compact for objects)
|
||||
if (metrics.child_count == 0 || metrics.can_inline) {
|
||||
metrics.recommended_layout = layout_mode::INLINE;
|
||||
metrics.recommended_layout = layout_mode::single_line;
|
||||
} else {
|
||||
metrics.recommended_layout = layout_mode::EXPANDED;
|
||||
metrics.recommended_layout = layout_mode::expanded;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
@@ -356,28 +356,28 @@ inline layout_mode structure_analyzer::decide_layout(const element_metrics& metr
|
||||
size_t depth,
|
||||
size_t available_width) const {
|
||||
if (metrics.child_count == 0) {
|
||||
return layout_mode::INLINE;
|
||||
return layout_mode::single_line;
|
||||
}
|
||||
|
||||
// Check inline feasibility
|
||||
size_t indent_width = depth * current_opts_->indent_spaces;
|
||||
if (metrics.can_inline &&
|
||||
metrics.estimated_inline_len + indent_width <= available_width) {
|
||||
return layout_mode::INLINE;
|
||||
return layout_mode::single_line;
|
||||
}
|
||||
|
||||
// Check table mode
|
||||
if (metrics.is_uniform_array && !metrics.common_keys.empty()) {
|
||||
return layout_mode::TABLE;
|
||||
return layout_mode::table;
|
||||
}
|
||||
|
||||
// Check compact multiline
|
||||
if (current_opts_->enable_compact_multiline &&
|
||||
metrics.complexity <= current_opts_->max_compact_array_complexity + 1) {
|
||||
return layout_mode::COMPACT_MULTILINE;
|
||||
return layout_mode::compact_multiline;
|
||||
}
|
||||
|
||||
return layout_mode::EXPANDED;
|
||||
return layout_mode::expanded;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -388,7 +388,7 @@ inline fractured_formatter::fractured_formatter(const fractured_json_options& op
|
||||
: options_(opts), column_widths_{} {}
|
||||
|
||||
simdjson_inline void fractured_formatter::print_newline() {
|
||||
if (current_layout_ == layout_mode::INLINE) {
|
||||
if (current_layout_ == layout_mode::single_line) {
|
||||
return; // No newlines in inline mode
|
||||
}
|
||||
one_char('\n');
|
||||
@@ -396,7 +396,7 @@ simdjson_inline void fractured_formatter::print_newline() {
|
||||
}
|
||||
|
||||
simdjson_inline void fractured_formatter::print_indents(size_t depth) {
|
||||
if (current_layout_ == layout_mode::INLINE) {
|
||||
if (current_layout_ == layout_mode::single_line) {
|
||||
return; // No indentation in inline mode
|
||||
}
|
||||
for (size_t i = 0; i < depth * options_.indent_spaces; i++) {
|
||||
@@ -543,16 +543,16 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
|
||||
const element_metrics& metrics,
|
||||
size_t depth) {
|
||||
switch (metrics.recommended_layout) {
|
||||
case layout_mode::INLINE:
|
||||
case layout_mode::single_line:
|
||||
format_array_inline(arr, metrics);
|
||||
break;
|
||||
case layout_mode::COMPACT_MULTILINE:
|
||||
case layout_mode::compact_multiline:
|
||||
format_array_compact_multiline(arr, metrics, depth);
|
||||
break;
|
||||
case layout_mode::TABLE:
|
||||
case layout_mode::table:
|
||||
format_array_as_table(arr, metrics, depth);
|
||||
break;
|
||||
case layout_mode::EXPANDED:
|
||||
case layout_mode::expanded:
|
||||
default:
|
||||
format_array_expanded(arr, metrics, depth);
|
||||
break;
|
||||
@@ -562,7 +562,7 @@ inline void fractured_string_builder::format_array(const dom::array& arr,
|
||||
inline void fractured_string_builder::format_array_inline(const dom::array& arr,
|
||||
const element_metrics& metrics) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
|
||||
format_.start_array();
|
||||
|
||||
@@ -623,7 +623,7 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom::
|
||||
|
||||
// Format element inline
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
const element_metrics& child_metrics = (child_idx < metrics.children.size())
|
||||
? metrics.children[child_idx] : element_metrics{};
|
||||
format_element(elem, child_metrics, depth + 1);
|
||||
@@ -719,7 +719,7 @@ inline void fractured_string_builder::format_array_as_table(const dom::array& ar
|
||||
// Write value
|
||||
if (found) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
const element_metrics& value_metrics = (field_idx < row_metrics.children.size())
|
||||
? row_metrics.children[field_idx] : element_metrics{};
|
||||
format_element(value, value_metrics, depth + 1);
|
||||
@@ -788,7 +788,7 @@ inline void fractured_string_builder::format_array_expanded(const dom::array& ar
|
||||
inline void fractured_string_builder::format_object(const dom::object& obj,
|
||||
const element_metrics& metrics,
|
||||
size_t depth) {
|
||||
if (metrics.recommended_layout == layout_mode::INLINE || metrics.can_inline) {
|
||||
if (metrics.recommended_layout == layout_mode::single_line || metrics.can_inline) {
|
||||
format_object_inline(obj, metrics);
|
||||
} else {
|
||||
format_object_expanded(obj, metrics, depth);
|
||||
@@ -798,7 +798,7 @@ inline void fractured_string_builder::format_object(const dom::object& obj,
|
||||
inline void fractured_string_builder::format_object_inline(const dom::object& obj,
|
||||
const element_metrics& metrics) {
|
||||
layout_mode prev_layout = format_.get_layout_mode();
|
||||
format_.set_layout_mode(layout_mode::INLINE);
|
||||
format_.set_layout_mode(layout_mode::single_line);
|
||||
|
||||
format_.start_object();
|
||||
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
// Otherwise, amalgamation will fail.
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/dom/fractured_json.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
|
||||
@@ -4,6 +4,7 @@
|
||||
#define SIMDJSON_GENERIC_STRING_BUILDER_H
|
||||
#include "simdjson/generic/builder/json_string_builder.h"
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
@@ -246,26 +247,29 @@ simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
// through the writer's local pos. For arithmetic fields, the integer
|
||||
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
|
||||
// never round-trips through memory.
|
||||
int i = 0;
|
||||
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()))) {
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) return;
|
||||
if (i == 0) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
if constexpr (std::meta::annotations_of_with_type(dm, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr const char* key_name = simdjson::get_json_key_name<dm>();
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) return;
|
||||
if (first) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
}
|
||||
first = false;
|
||||
atom(w, t.[:dm:]);
|
||||
}
|
||||
atom(w, t.[:dm:]);
|
||||
i++;
|
||||
};
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
|
||||
@@ -173,6 +173,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
}
|
||||
return _mm_movemask_epi8(running) != 0;
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
if (view.size() < 16) {
|
||||
return simple_needs_escaping(view);
|
||||
}
|
||||
size_t i = 0;
|
||||
__m128i running = __lsx_vreplgr2vr_b(0);
|
||||
__m128i v34 = __lsx_vreplgr2vr_b(34);
|
||||
__m128i v92 = __lsx_vreplgr2vr_b(92);
|
||||
__m128i v32 = __lsx_vreplgr2vr_b(32);
|
||||
|
||||
for (; i + 15 < view.size(); i += 16) {
|
||||
__m128i word =
|
||||
__lsx_vld(reinterpret_cast<const char *>(view.data() + i), 0);
|
||||
__m128i chunk = __lsx_vseq_b(word, v34);
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
|
||||
running = __lsx_vor_v(running, chunk);
|
||||
}
|
||||
if (i < view.size()) {
|
||||
__m128i word = __lsx_vld(
|
||||
reinterpret_cast<const char *>(view.data() + view.length() - 16), 0);
|
||||
__m128i chunk = __lsx_vseq_b(word, v34);
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92));
|
||||
chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32));
|
||||
running = __lsx_vor_v(running, chunk);
|
||||
}
|
||||
return !__lsx_bz_v(running);
|
||||
}
|
||||
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
|
||||
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
||||
if (view.size() < 16) {
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
#include "simdjson/padded_string_view.h"
|
||||
#include "simdjson/internal/dom_parser_implementation.h"
|
||||
#include "simdjson/jsonpathutil.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H
|
||||
@@ -24,6 +24,9 @@ simdjson_inline json_iterator::json_iterator(json_iterator &&other) noexcept
|
||||
_depth{other._depth},
|
||||
_root{other._root},
|
||||
_streaming{other._streaming}
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
, _allow_incomplete_json{other._allow_incomplete_json}
|
||||
#endif
|
||||
{
|
||||
other.parser = nullptr;
|
||||
}
|
||||
@@ -35,6 +38,9 @@ simdjson_inline json_iterator &json_iterator::operator=(json_iterator &&other) n
|
||||
_depth = other._depth;
|
||||
_root = other._root;
|
||||
_streaming = other._streaming;
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
_allow_incomplete_json = other._allow_incomplete_json;
|
||||
#endif
|
||||
other.parser = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@@ -61,7 +67,8 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse
|
||||
_string_buf_loc{parser->string_buf.get()},
|
||||
_depth{1},
|
||||
_root{parser->implementation->structural_indexes.get()},
|
||||
_streaming{streaming}
|
||||
_streaming{streaming},
|
||||
_allow_incomplete_json{true}
|
||||
|
||||
{
|
||||
logger::log_headers();
|
||||
@@ -193,6 +200,17 @@ simdjson_inline bool json_iterator::streaming() const noexcept {
|
||||
return _streaming;
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool json_iterator::allow_incomplete_json() const noexcept {
|
||||
return _allow_incomplete_json;
|
||||
}
|
||||
|
||||
simdjson_inline size_t json_iterator::remaining_input_length(const uint8_t *json) const noexcept {
|
||||
const uint8_t *end = token.buf + parser->_document_len;
|
||||
return json < end ? size_t(end - json) : 0;
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
simdjson_inline token_position json_iterator::root_position() const noexcept {
|
||||
return _root;
|
||||
}
|
||||
@@ -450,4 +468,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::json_iterator
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H
|
||||
|
||||
@@ -60,6 +60,9 @@ protected:
|
||||
* value of this attribute.
|
||||
*/
|
||||
bool _streaming{false};
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
bool _allow_incomplete_json{false};
|
||||
#endif
|
||||
|
||||
public:
|
||||
simdjson_inline json_iterator() noexcept = default;
|
||||
@@ -84,6 +87,10 @@ public:
|
||||
* start_root_array() and start_root_object().
|
||||
*/
|
||||
simdjson_inline bool streaming() const noexcept;
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool allow_incomplete_json() const noexcept;
|
||||
simdjson_inline size_t remaining_input_length(const uint8_t *json) const noexcept;
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
/**
|
||||
* Get the root value iterator
|
||||
@@ -335,4 +342,4 @@ public:
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H
|
||||
|
||||
@@ -55,6 +55,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate(p
|
||||
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
|
||||
json.remove_utf8_bom();
|
||||
_document_len = json.length();
|
||||
|
||||
// Allocate if needed
|
||||
if (capacity() < json.length() || !string_buf) {
|
||||
@@ -71,6 +72,7 @@ simdjson_warn_unused simdjson_inline simdjson_result<document> parser::iterate_a
|
||||
if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||
|
||||
json.remove_utf8_bom();
|
||||
_document_len = json.length();
|
||||
|
||||
// Allocate if needed
|
||||
if (capacity() < json.length() || !string_buf) {
|
||||
@@ -312,4 +314,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::parser>::simd
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H
|
||||
|
||||
@@ -431,6 +431,7 @@ private:
|
||||
size_t _capacity{0};
|
||||
size_t _max_capacity;
|
||||
size_t _max_depth{DEFAULT_MAX_DEPTH};
|
||||
size_t _document_len{0};
|
||||
std::unique_ptr<uint8_t[]> string_buf{};
|
||||
|
||||
#if SIMDJSON_DEVELOPMENT_CHECKS
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <concepts>
|
||||
@@ -267,22 +268,6 @@ constexpr bool user_defined_type = (std::is_class_v<T>
|
||||
!concepts::appendable_containers<T>);
|
||||
|
||||
|
||||
// Compile-time predicate: does T have any std::optional member?
|
||||
// Used to decide whether single-pass dispatch is worth it.
|
||||
template <typename T>
|
||||
consteval bool struct_has_optional_member() {
|
||||
bool result = false;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||
using FieldT = [: std::meta::type_of(mem) :];
|
||||
if constexpr (concepts::optional_type<FieldT>) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, typename ValT>
|
||||
requires(user_defined_type<T> && std::is_class_v<T>)
|
||||
error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
@@ -292,42 +277,26 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
} else {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
if constexpr (struct_has_optional_member<T>()) {
|
||||
// Single-pass dispatch: walk each JSON object field once, dispatching
|
||||
// to the matching struct member via a compile-time-generated key
|
||||
// comparison chain (with length pre-filter). This avoids the O(K)
|
||||
// full-object scan that obj[key] does for *absent* optional fields.
|
||||
// Worth it when the struct has optionals because some are usually missing.
|
||||
for (auto field : obj) {
|
||||
std::string_view key;
|
||||
SIMDJSON_TRY(field.unescaped_key().get(key));
|
||||
bool matched = false;
|
||||
const size_t key_size = key.size();
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||
constexpr std::string_view name = std::define_static_string(std::meta::identifier_of(mem));
|
||||
constexpr size_t name_size = name.size();
|
||||
if (!matched && key_size == name_size && key == name) {
|
||||
SIMDJSON_TRY(field.value().get(out.[:mem:]));
|
||||
matched = true;
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)
|
||||
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr std::string_view key = simdjson::get_json_key_name<mem>();
|
||||
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
// for optional members, it's ok if the key is missing
|
||||
auto error = obj[key].get(out.[:mem:]);
|
||||
if (error && error != NO_SUCH_FIELD) {
|
||||
if(error == NO_SUCH_FIELD) {
|
||||
out.[:mem:].reset();
|
||||
continue;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
};
|
||||
// Unmatched value is skipped automatically by object_iterator::operator++().
|
||||
(void)matched;
|
||||
}
|
||||
} else {
|
||||
// Per-field obj[key] dispatch: for structs with all-required fields,
|
||||
// this is O(1) per field when the JSON keys are in declaration order
|
||||
// (find_field_unordered's fast path). Beats single-pass on dense
|
||||
// structs (e.g. Twitter Status with 22 always-present fields).
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
||||
} else {
|
||||
// for non-optional members, the key must be present
|
||||
SIMDJSON_TRY(obj[key].get(out.[:mem:]));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
return simdjson::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,27 @@ namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace ondemand {
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
simdjson_inline bool raw_json_string_is_quote_terminated(const uint8_t *json, uint32_t max_len) noexcept {
|
||||
bool escaping{false};
|
||||
for (uint32_t i = 1; i < max_len; i++) {
|
||||
switch (json[i]) {
|
||||
case '"':
|
||||
if (!escaping) { return true; }
|
||||
escaping = false;
|
||||
break;
|
||||
case '\\':
|
||||
escaping = !escaping;
|
||||
break;
|
||||
default:
|
||||
escaping = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
|
||||
simdjson_inline value_iterator::value_iterator(
|
||||
json_iterator *json_iter,
|
||||
depth_t depth,
|
||||
@@ -530,6 +551,15 @@ simdjson_warn_unused simdjson_inline simdjson_result<std::string_view> value_ite
|
||||
simdjson_warn_unused simdjson_inline simdjson_result<raw_json_string> value_iterator::get_raw_json_string() noexcept {
|
||||
auto json = peek_scalar("string");
|
||||
if (*json != '"') { return incorrect_type_error("Not a string"); }
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
if (_json_iter->allow_incomplete_json()) {
|
||||
const size_t remaining_input_length = _json_iter->remaining_input_length(json);
|
||||
const uint32_t max_len = remaining_input_length < peek_start_length() ? uint32_t(remaining_input_length) : peek_start_length();
|
||||
if (!raw_json_string_is_quote_terminated(json, max_len)) {
|
||||
return STRING_ERROR;
|
||||
}
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
advance_scalar("string");
|
||||
return raw_json_string(json+1);
|
||||
}
|
||||
@@ -677,6 +707,15 @@ simdjson_warn_unused simdjson_inline simdjson_result<raw_json_string> value_iter
|
||||
auto json = peek_scalar("string");
|
||||
if (*json != '"') { return incorrect_type_error("Not a string"); }
|
||||
if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; }
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
if (_json_iter->allow_incomplete_json()) {
|
||||
const size_t remaining_input_length = _json_iter->remaining_input_length(json);
|
||||
const uint32_t max_len = remaining_input_length < peek_root_length() ? uint32_t(remaining_input_length) : peek_root_length();
|
||||
if (!raw_json_string_is_quote_terminated(json, max_len)) {
|
||||
return STRING_ERROR;
|
||||
}
|
||||
}
|
||||
#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
advance_scalar("string");
|
||||
return raw_json_string(json+1);
|
||||
}
|
||||
@@ -1104,4 +1143,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value_iterato
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
|
||||
private:
|
||||
fractured_json_options options_;
|
||||
layout_mode current_layout_ = layout_mode::EXPANDED;
|
||||
layout_mode current_layout_ = layout_mode::expanded;
|
||||
size_t current_depth_ = 0;
|
||||
size_t current_line_length_ = 0;
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace internal {
|
||||
* Layout mode for fractured JSON formatting.
|
||||
*/
|
||||
enum class layout_mode {
|
||||
INLINE, // Single line: [1, 2, 3] or {"a": 1}
|
||||
COMPACT_MULTILINE, // Multiple items per line with breaks
|
||||
TABLE, // Tabular format for arrays of similar objects
|
||||
EXPANDED // Traditional multi-line with indentation
|
||||
single_line, // Single line: [1, 2, 3] or {"a": 1}
|
||||
compact_multiline, // Multiple items per line with breaks
|
||||
table, // Tabular format for arrays of similar objects
|
||||
expanded // Traditional multi-line with indentation
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ struct element_metrics {
|
||||
std::vector<std::string> common_keys{};
|
||||
|
||||
/** Recommended layout mode based on analysis */
|
||||
layout_mode recommended_layout = layout_mode::EXPANDED;
|
||||
layout_mode recommended_layout = layout_mode::expanded;
|
||||
|
||||
/** Child metrics for arrays and objects (in order of iteration) */
|
||||
std::vector<element_metrics> children{};
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
simdjson_inline implementation() : simdjson::implementation(
|
||||
"rvv_vls",
|
||||
"RISC-V V extension",
|
||||
0
|
||||
internal::instruction_set::RVV_VLS
|
||||
) {}
|
||||
simdjson_warn_unused error_code create_dom_parser_implementation(
|
||||
size_t capacity,
|
||||
|
||||
@@ -25,7 +25,7 @@ if (Python3_Interpreter_FOUND AND (NOT WIN32))
|
||||
AMALGAMATE_SOURCE_PATH=${PROJECT_SOURCE_DIR}/src
|
||||
AMALGAMATE_INPUT_PATH=${PROJECT_SOURCE_DIR}/include
|
||||
AMALGAMATE_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
|
||||
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py
|
||||
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py --quiet
|
||||
#
|
||||
# This is the best way I could find to make amalgamation trigger whenever source files or
|
||||
# header files change: since the "simdjson" library has to get rebuilt when that happens, we
|
||||
|
||||
+161
-37
@@ -11,6 +11,7 @@ import re
|
||||
import shutil
|
||||
import datetime
|
||||
import json
|
||||
import argparse
|
||||
from typing import Dict, List, Optional, Set, TextIO, Union, cast
|
||||
|
||||
# Pre-compile regex patterns for performance
|
||||
@@ -30,6 +31,18 @@ if sys.version_info < (3, 0):
|
||||
sys.stdout.write("Sorry, requires Python 3.x or better\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse command-line arguments.
|
||||
arg_parser = argparse.ArgumentParser(description="Creates the amalgamated source files.")
|
||||
arg_parser.add_argument("--quiet", action="store_true",
|
||||
help="Suppress informational output (errors and warnings are still printed).")
|
||||
args = arg_parser.parse_args()
|
||||
QUIET = args.quiet
|
||||
|
||||
def log(*log_args, **log_kwargs):
|
||||
"""Print informational output unless --quiet was passed."""
|
||||
if not QUIET:
|
||||
print(*log_args, **log_kwargs)
|
||||
|
||||
rules = """
|
||||
|
||||
Amalgamation Rules for simdjson
|
||||
@@ -99,11 +112,11 @@ If adding a new implementation, edit the IMPLEMENTATIONS list in this script.
|
||||
|
||||
SCRIPTPATH = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
PROJECTPATH = os.path.dirname(SCRIPTPATH)
|
||||
print(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
|
||||
log(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
|
||||
|
||||
|
||||
print("We are about to amalgamate all simdjson files into one source file.")
|
||||
print("See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale.")
|
||||
log("We are about to amalgamate all simdjson files into one source file.")
|
||||
log("See https://www.sqlite.org/amalgamation.html and https://en.wikipedia.org/wiki/Single_Compilation_Unit for rationale.")
|
||||
if "AMALGAMATE_SOURCE_PATH" not in os.environ:
|
||||
AMALGAMATE_SOURCE_PATH = os.path.join(PROJECTPATH, "src")
|
||||
else:
|
||||
@@ -265,37 +278,93 @@ class SimdjsonFile:
|
||||
return self.filename == 'dependencies.h'
|
||||
|
||||
def add_include(self, include: 'SimdjsonFile'):
|
||||
print(f" Adding include: {self} includes {include}")
|
||||
log(f" Adding include: {self} includes {include}")
|
||||
if self.is_conditional_include:
|
||||
# If I have a dependency file, I can only include something that has a dependency file.
|
||||
if not include.is_conditional_include:
|
||||
dep = self.dependency_file
|
||||
dep_hint = f" and add it to the dependency file '{dep}'" if dep else ""
|
||||
if dep:
|
||||
step2 = (
|
||||
f" 2. Add '{include}' to the dependency manifest so the amalgamator\n"
|
||||
f" emits it before any file that needs it:\n\n"
|
||||
f" // in '{dep}'\n"
|
||||
f" #include \"{include}\"\n\n"
|
||||
)
|
||||
else:
|
||||
step2 = (
|
||||
f" 2. '{self}' has no associated dependencies.h file, so the\n"
|
||||
f" amalgamator cannot be told to emit '{include}' earlier.\n"
|
||||
f" Either give '{self}' a dependencies.h, or restructure so\n"
|
||||
f" the include is not needed here.\n\n"
|
||||
)
|
||||
raise AssertionError(
|
||||
f"Error: Amalgamated file '{self}' is trying to include '{include}', "
|
||||
f"but '{include}' is not an amalgamated file.\n\n"
|
||||
f"FIX: Wrap the #include \"{include}\" in a conditional block:\n\n"
|
||||
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
|
||||
f" #include \"{include}\"\n"
|
||||
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||
f"This makes the include editor-only (skipped during amalgamation){dep_hint}.\n\n"
|
||||
f"During amalgamation, '{include}' is already included earlier in the "
|
||||
f"amalgamated output, so it does not need to be included again.\n\n"
|
||||
f"{rules}"
|
||||
f"Error: amalgamated file '{self}' includes non-amalgamated header '{include}'.\n\n"
|
||||
f" Why: '{self}' is stitched into the single-header output. Top-level\n"
|
||||
f" headers like '{include}' are already emitted earlier in that\n"
|
||||
f" output, so re-including them inside an amalgamated file would\n"
|
||||
f" either duplicate content or break include ordering.\n\n"
|
||||
f"FIX (two steps):\n\n"
|
||||
f" 1. In '{self}', wrap the include so it is skipped during amalgamation\n"
|
||||
f" but still visible to your editor/IDE:\n\n"
|
||||
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
|
||||
f" #include \"{include}\"\n"
|
||||
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||
f" Place it alongside the existing '#ifndef SIMDJSON_CONDITIONAL_INCLUDE'\n"
|
||||
f" block near the top of the file, if there is one.\n\n"
|
||||
f"{step2}"
|
||||
f"After both edits, re-run:\n\n"
|
||||
f" python3 singleheader/amalgamate.py\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
|
||||
# if include.amalgamator_file:
|
||||
# assert include.amalgamator_file == self, f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
|
||||
else:
|
||||
assert include.is_amalgamator or not include.is_conditional_include, f"Error: Free dependency file '{self}' is trying to include '{include}', which is an amalgamated file. Free dependency files (top-level headers) can only include amalgamator files or other free files, not amalgamated files directly. This prevents improper layering. Move the include to an amalgamator or restructure dependencies. {rules}"
|
||||
if not (include.is_amalgamator or not include.is_conditional_include):
|
||||
raise AssertionError(
|
||||
f"Error: free (top-level) header '{self}' includes amalgamated file '{include}'.\n\n"
|
||||
f" Why: top-level headers are emitted once into the single-header output\n"
|
||||
f" and form the public API surface. Amalgamated files are emitted\n"
|
||||
f" multiple times (once per CPU implementation), so including one\n"
|
||||
f" from the top level would either duplicate them or pin them to a\n"
|
||||
f" single implementation.\n\n"
|
||||
f"FIX: move the include to an amalgamator file (e.g. simdjson/arm64.h,\n"
|
||||
f" simdjson/generic/amalgamated.h) or, if you actually need the\n"
|
||||
f" declarations at the top level, factor them out into a top-level\n"
|
||||
f" header that '{self}' can include instead.\n\n"
|
||||
f"After editing, re-run:\n\n"
|
||||
f" python3 singleheader/amalgamate.py\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
|
||||
self.includes.append(include)
|
||||
include.included_from.add(self)
|
||||
|
||||
def add_editor_only_include(self, include: 'SimdjsonFile'):
|
||||
assert self.is_conditional_include, f"Error: File '{self}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE', but '{self}' is not an amalgamated file. Conditional include blocks are only allowed in amalgamated files (those with dependencies). Remove the conditional block or ensure the file is amalgamated. {rules}"
|
||||
if not include.is_conditional_include:
|
||||
assert self.dependency_file, f"Error: In '{self}', editor-only include of '{include}' requires a dependency file, but '{self}' has none. Ensure '{self}' has an associated dependencies.h file. {rules}"
|
||||
if not self.is_conditional_include:
|
||||
raise AssertionError(
|
||||
f"Error: '{self}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE',\n"
|
||||
f" but '{self}' is not an amalgamated file (it has no\n"
|
||||
f" associated dependencies.h).\n\n"
|
||||
f" Why: conditional-include blocks only make sense in amalgamated files,\n"
|
||||
f" because the SIMDJSON_CONDITIONAL_INCLUDE macro is only defined\n"
|
||||
f" while the amalgamator is emitting them.\n\n"
|
||||
f"FIX: either remove the '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block from\n"
|
||||
f" '{self}' (and include the headers unconditionally), or — if '{self}'\n"
|
||||
f" really should be amalgamated — give it an associated dependencies.h\n"
|
||||
f" file under the same directory.\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
if not include.is_conditional_include and not self.dependency_file:
|
||||
raise AssertionError(
|
||||
f"Error: '{self}' editor-only-includes '{include}', but '{self}' has no\n"
|
||||
f" associated dependencies.h, so the amalgamator has nowhere to record\n"
|
||||
f" that '{include}' must be emitted earlier in the single-header output.\n\n"
|
||||
f"FIX: add a 'dependencies.h' file in the same directory as '{self}',\n"
|
||||
f" and list '{include}' inside it:\n\n"
|
||||
f" #include \"{include}\"\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
|
||||
# elif include.amalgamator_file:
|
||||
# assert self.is_amalgamated_before(self.amalgamator_file), f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
|
||||
@@ -310,11 +379,40 @@ class SimdjsonFile:
|
||||
if file.dependency_file == self:
|
||||
for editor_only_include in file.editor_only_includes:
|
||||
if not editor_only_include.is_conditional_include:
|
||||
assert editor_only_include in self.includes, f"Error: Dependency file '{self}' is missing an include for '{editor_only_include}', which is editor-only included in '{file}'. Add '{editor_only_include}' to '{self}' to ensure completeness. {rules}"
|
||||
if editor_only_include not in self.includes:
|
||||
raise AssertionError(
|
||||
f"Error: dependency manifest '{self}' is missing\n"
|
||||
f" '{editor_only_include}'.\n\n"
|
||||
f" It is editor-only-included by '{file}'\n"
|
||||
f" (inside a '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block),\n"
|
||||
f" which means the amalgamator does NOT see that include —\n"
|
||||
f" so unless '{self}' lists it, '{editor_only_include}' will\n"
|
||||
f" never be emitted in the single-header build.\n\n"
|
||||
f"FIX: add this line to '{self}':\n\n"
|
||||
f" #include \"{editor_only_include}\"\n\n"
|
||||
f"Then re-run:\n\n"
|
||||
f" python3 singleheader/amalgamate.py\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
if editor_only_include in extra_include_set:
|
||||
extra_include_set.remove(editor_only_include)
|
||||
|
||||
assert len(extra_include_set) == 0, f"Error: Dependency file '{self}' includes {extra_include_set}, which are not used in any amalgamated files. Remove these unnecessary includes to clean up dependencies. {rules}"
|
||||
if len(extra_include_set) > 0:
|
||||
extras = sorted(str(f) for f in extra_include_set)
|
||||
bullet_list = "\n".join(f" #include \"{e}\"" for e in extras)
|
||||
raise AssertionError(
|
||||
f"Error: dependency manifest '{self}' lists includes that no\n"
|
||||
f" amalgamated file actually needs:\n\n"
|
||||
f"{bullet_list}\n\n"
|
||||
f" Why: every include in a dependencies.h file should correspond to a\n"
|
||||
f" '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block in some amalgamated\n"
|
||||
f" sibling file. Stale entries here pull unused headers into the\n"
|
||||
f" single-header build.\n\n"
|
||||
f"FIX: either delete the unused entries from '{self}', or, if they were\n"
|
||||
f" supposed to support a real consumer, add the matching editor-only\n"
|
||||
f" '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block to that consumer.\n\n"
|
||||
f"See HACKING.md for the full amalgamation rules.\n"
|
||||
)
|
||||
|
||||
class SimdjsonRepository:
|
||||
def __init__(self, project_path: str, relative_roots: List[RelativeRoot]):
|
||||
@@ -366,12 +464,28 @@ class SimdjsonRepository:
|
||||
used_files = set([file.include_path for file in self if file.root == root])
|
||||
all_files.difference_update(used_files)
|
||||
all_files.difference_update(DEPRECATED_FILES)
|
||||
assert len(all_files) == 0, f"Error: The following files in '{root}' are not used in the amalgamation: {sorted(all_files)}. All .h and .cpp files must be included or added to DEPRECATED_FILES. Check for missing includes or deprecate unused files."
|
||||
if len(all_files) > 0:
|
||||
bullet_list = "\n".join(f" {root}/{f}" for f in sorted(all_files))
|
||||
raise AssertionError(
|
||||
f"Error: the following files under '{root}/' exist on disk but are not\n"
|
||||
f" reachable from any #include chain seen by the amalgamator:\n\n"
|
||||
f"{bullet_list}\n\n"
|
||||
f" Why: every .h/.cpp file in the tree must either be transitively included\n"
|
||||
f" from the amalgamation roots, or explicitly listed in DEPRECATED_FILES\n"
|
||||
f" in singleheader/amalgamate.py. This catches files that were forgotten\n"
|
||||
f" after a rename, or were added without a corresponding #include.\n\n"
|
||||
f"FIX: pick one for each file above:\n"
|
||||
f" - Add an #include for it in the appropriate amalgamator/dependencies file.\n"
|
||||
f" - If the file is genuinely obsolete, delete it.\n"
|
||||
f" - If the file is a deprecated public header kept only for source\n"
|
||||
f" compatibility, add it to the DEPRECATED_FILES set in\n"
|
||||
f" singleheader/amalgamate.py.\n"
|
||||
)
|
||||
|
||||
class Amalgamator:
|
||||
@classmethod
|
||||
def amalgamate(cls, output_path: str, filename: str, roots: List[RelativeRoot], timestamp: str, version: str):
|
||||
print(f"Creating {output_path}")
|
||||
log(f"Creating {output_path}")
|
||||
fid = open(output_path, 'w')
|
||||
print(f"/* auto-generated on {timestamp}. version {version} Do not edit! */", file=fid)
|
||||
amalgamator = cls(fid, SimdjsonRepository(PROJECTPATH, roots))
|
||||
@@ -425,7 +539,17 @@ class Amalgamator:
|
||||
|
||||
def write_file(self, file: SimdjsonFile):
|
||||
# Detect cyclic dependencies
|
||||
assert file not in self.include_stack, f"Error: Cyclic include detected: {self.include_stack} -> {file}. Remove the circular dependency by restructuring includes."
|
||||
if file in self.include_stack:
|
||||
cycle_start = self.include_stack.index(file)
|
||||
chain = self.include_stack[cycle_start:] + [file]
|
||||
arrow_chain = "\n -> ".join(str(f) for f in chain)
|
||||
raise AssertionError(
|
||||
f"Error: cyclic include detected:\n\n"
|
||||
f" {arrow_chain}\n\n"
|
||||
f"FIX: break the cycle by moving the shared declarations into a separate\n"
|
||||
f" header that both ends of the cycle can include, or by forward-declaring\n"
|
||||
f" instead of including.\n"
|
||||
)
|
||||
self.include_stack.append(file)
|
||||
|
||||
file.processed = False
|
||||
@@ -577,12 +701,12 @@ class Amalgamator:
|
||||
try:
|
||||
proc = subprocess.run(['git', 'show', '-s', '--format=%ci', 'HEAD'],
|
||||
stdout=subprocess.PIPE)
|
||||
print("the commandline is {}".format(proc.args))
|
||||
log("the commandline is {}".format(proc.args))
|
||||
timestamp = proc.stdout.decode('utf-8').strip()
|
||||
except:
|
||||
print("git not found, timestamp based on current time")
|
||||
timestamp = str(datetime.datetime.now())
|
||||
print(f"timestamp is {timestamp}")
|
||||
log(f"timestamp is {timestamp}")
|
||||
|
||||
os.makedirs(AMALGAMATE_OUTPUT_PATH, exist_ok=True)
|
||||
AMAL_H = os.path.join(AMALGAMATE_OUTPUT_PATH, "simdjson.h")
|
||||
@@ -625,7 +749,7 @@ if not validate_implementations():
|
||||
print("Validation failed. Please update IMPLEMENTATIONS list in amalgamate.py.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("implementation validated")
|
||||
log("implementation validated")
|
||||
Amalgamator.amalgamate(AMAL_H, "simdjson.h", ['include'], timestamp, version).validate_all_files_used('include')
|
||||
Amalgamator.amalgamate(AMAL_C, "simdjson.cpp", ['src', 'include'], timestamp, version).validate_all_files_used('src')
|
||||
|
||||
@@ -642,20 +766,20 @@ def create_zip():
|
||||
outdir = AMALGAMATE_OUTPUT_PATH
|
||||
|
||||
path = os.path.join(outdir, "singleheader.zip")
|
||||
print(f"Creating {path}")
|
||||
log(f"Creating {path}")
|
||||
with zipfile.ZipFile(path, 'w') as zf:
|
||||
for name in ["simdjson.cpp", "simdjson.h"]:
|
||||
source = os.path.join(outdir, name)
|
||||
print(f"Adding {source}")
|
||||
log(f"Adding {source}")
|
||||
zf.write(source, name)
|
||||
print(f"Created {path}")
|
||||
log(f"Created {path}")
|
||||
create_zip()
|
||||
print("Done with all files generation.")
|
||||
log("Done with all files generation.")
|
||||
|
||||
print(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
|
||||
print(subprocess.run(['ls', '-la', AMAL_C, AMAL_H, DEMOCPP, README],
|
||||
stdout=subprocess.PIPE).stdout.decode('utf-8').strip())
|
||||
print("Done with all files generation.")
|
||||
log(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
|
||||
log(subprocess.run(['ls', '-la', AMAL_C, AMAL_H, DEMOCPP, README],
|
||||
stdout=subprocess.PIPE).stdout.decode('utf-8').strip())
|
||||
log("Done with all files generation.")
|
||||
|
||||
|
||||
|
||||
@@ -664,7 +788,7 @@ print("Done with all files generation.")
|
||||
# Instructions to create demo
|
||||
#
|
||||
|
||||
print("\nGiving final instructions:")
|
||||
log("\nGiving final instructions:")
|
||||
with open(README) as r:
|
||||
for line in r:
|
||||
print(line)
|
||||
log(line)
|
||||
|
||||
@@ -8,6 +8,7 @@ if(SIMDJSON_STATIC_REFLECTION)
|
||||
add_cpp_test(static_reflection_edge_cases_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_enum_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_fractured_json_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_annotations_tests LABELS ondemand acceptance per_implementation)
|
||||
endif(SIMDJSON_STATIC_REFLECTION)
|
||||
# Copy the simdjson dll into the tests directory
|
||||
if(MSVC AND BUILD_SHARED_LIBS)
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_builder.h"
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
struct RenamedFields {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
struct SkippedField {
|
||||
std::string name = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
};
|
||||
|
||||
struct MixedAnnotations {
|
||||
[[= simdjson::rename<"user_name">]] std::string userName = "";
|
||||
[[= simdjson::skip]] int sessionToken = 0;
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
namespace annotation_tests {
|
||||
|
||||
bool rename_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
RenamedFields r{"Alice", "Smith", 30};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(r).get(out));
|
||||
ASSERT_EQUAL(out, "{\"first_name\":\"Alice\",\"last_name\":\"Smith\",\"age\":30}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool rename_deserialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string json = R"({"first_name":"Bob","last_name":"Jones","age":25})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
RenamedFields r;
|
||||
ASSERT_SUCCESS(doc.get<RenamedFields>().get(r));
|
||||
ASSERT_EQUAL(r.firstName, "Bob");
|
||||
ASSERT_EQUAL(r.lastName, "Jones");
|
||||
ASSERT_EQUAL(r.age, 25);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool rename_roundtrip_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
RenamedFields original{"Carol", "White", 40};
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(simdjson::to_json(original).get(json));
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
RenamedFields result;
|
||||
ASSERT_SUCCESS(doc.get<RenamedFields>().get(result));
|
||||
ASSERT_EQUAL(result.firstName, original.firstName);
|
||||
ASSERT_EQUAL(result.lastName, original.lastName);
|
||||
ASSERT_EQUAL(result.age, original.age);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool skip_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
SkippedField s{"Alice", 999};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(s).get(out));
|
||||
ASSERT_EQUAL(out, "{\"name\":\"Alice\"}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool skip_deserialize_ignores_field_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// The key "internalCache" is present in JSON but the field is annotated skip -
|
||||
// it should be ignored and the field should keep its default value.
|
||||
std::string json = R"({"name":"Carol","internalCache":999})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
SkippedField s;
|
||||
ASSERT_SUCCESS(doc.get<SkippedField>().get(s));
|
||||
ASSERT_EQUAL(s.name, "Carol");
|
||||
ASSERT_EQUAL(s.internalCache, 0);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool mixed_annotations_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
MixedAnnotations m{"dave", 12345, 28};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(m).get(out));
|
||||
// sessionToken must not appear; userName must appear as "user_name"
|
||||
ASSERT_EQUAL(out, "{\"user_name\":\"dave\",\"age\":28}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool mixed_annotations_deserialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string json = R"({"user_name":"eve","age":35})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
MixedAnnotations m;
|
||||
ASSERT_SUCCESS(doc.get<MixedAnnotations>().get(m));
|
||||
ASSERT_EQUAL(m.userName, "eve");
|
||||
ASSERT_EQUAL(m.age, 35);
|
||||
ASSERT_EQUAL(m.sessionToken, 0);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run_all() {
|
||||
return rename_serialize_test()
|
||||
&& rename_deserialize_test()
|
||||
&& rename_roundtrip_test()
|
||||
&& skip_serialize_test()
|
||||
&& skip_deserialize_ignores_field_test()
|
||||
&& mixed_annotations_serialize_test()
|
||||
&& mixed_annotations_deserialize_test();
|
||||
}
|
||||
|
||||
} // namespace annotation_tests
|
||||
|
||||
int main() {
|
||||
return annotation_tests::run_all() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
@@ -42,8 +42,8 @@ add_cpp_test(ondemand_convert_tests LABELS ondemand acceptance
|
||||
add_cpp_test(ondemand_unknown_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_wildcard_tests LABELS ondemand acceptance per_implementation)
|
||||
if(NOT SIMDJSON_SANITIZE)
|
||||
add_cpp_test(ondemand_padded_input LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_padded_input LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
||||
endif()
|
||||
|
||||
# Add the tests if we're on:
|
||||
|
||||
@@ -243,6 +243,28 @@ namespace json_path_tests {
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
bool incomplete_string_value_with_allow_incomplete_json() {
|
||||
TEST_START();
|
||||
ondemand::parser parser;
|
||||
ondemand::document doc;
|
||||
std::string_view val;
|
||||
|
||||
auto incomplete_string_value = R"({"key":")"_padded;
|
||||
auto complete_string_before_incomplete_value = R"({"key":"ok","incomplete":")"_padded;
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(incomplete_string_value).get(doc));
|
||||
ASSERT_ERROR(doc.at_path(".key").get_string().get(val), simdjson::STRING_ERROR);
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(complete_string_before_incomplete_value).get(doc));
|
||||
ASSERT_SUCCESS(doc.at_path(".key").get_string().get(val));
|
||||
ASSERT_EQUAL(val, "ok");
|
||||
|
||||
ASSERT_SUCCESS(parser.iterate_allow_incomplete_json(complete_string_before_incomplete_value).get(doc));
|
||||
ASSERT_ERROR(doc.at_path(".incomplete").get_string().get(val), simdjson::STRING_ERROR);
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool many_json_paths_object_array() {
|
||||
TEST_START();
|
||||
@@ -442,6 +464,9 @@ namespace json_path_tests {
|
||||
many_json_paths_object_array() &&
|
||||
many_json_paths_with_prefix() &&
|
||||
run_broken_tests() &&
|
||||
#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON
|
||||
incomplete_string_value_with_allow_incomplete_json() &&
|
||||
#endif
|
||||
json_path_invalidation() &&
|
||||
demo_test() &&
|
||||
demo_relative_path() &&
|
||||
|
||||
Reference in New Issue
Block a user