Compare commits

..

55 Commits

Author SHA1 Message Date
Daniel Lemire 5900ac42d9 better 2026-04-12 18:44:31 -04:00
Daniel Lemire 5bb9c8eed5 inlining. 2026-04-12 18:38:03 -04:00
Daniel Lemire 122bea262a proto 2026-04-12 18:13:58 -04:00
Daniel Lemire e48f7bf98b init 2026-04-12 18:13:58 -04:00
Daniel Lemire 4ec44e88c9 adding memory-file mapping to Windows + better doc (#2676)
* adding memory-file mapping to Windows.

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

* removing non-ascii

* saving.

* bumping up

* take 2
2026-04-12 18:12:31 -04:00
Daniel Lemire ede3129081 modernizing the benchmarks (#2679) 2026-04-12 18:09:46 -04:00
Daniel Lemire 8ea4c1c2e6 being more precise 2026-04-12 17:06:27 -04:00
Daniel Lemire 486b2a3828 minor update 2026-04-11 14:45:38 -04:00
Daniel Lemire 22773f3c70 documenting C++26 usage with iterate_many 2026-04-11 14:41:05 -04:00
Daniel Lemire 249eb2c28a better construction. 2026-04-11 14:23:15 -04:00
Daniel Lemire 94c429aa70 finishing up the new streaming (#2674)
* finishing up the new streaming

* fixed silly warnings.

* tweak

* adding more tests

* minor fixes

* more fixes
2026-04-11 14:16:43 -04:00
Jaël Champagne Gareau 72e51a9a81 Add support for RFC 7464 JSON text sequences and comma-delimited documents (#2664)
* add support for RFC 7464 documents

* add threaded comma-delimited parse_many support

* fix failing tests in CI
2026-04-10 20:26:00 -04:00
Daniel Lemire 1a57afec1f Various guards (#2673)
* adding a guard in document::allocate.

Co-authored-by: jmestwa-coder jmestwa@gmail.com

* adding a max depth

Co-authored-by: jmestwa-coder jmestwa@gmail.com
2026-04-10 20:19:46 -04:00
Daniel Lemire 4de7426b9f amalgamate should provide nicer error messages (#2672) 2026-04-10 17:42:57 -04:00
Daniel Lemire 85cadf4074 adding a C++17 padded_input for convenience. (#2667)
* adding a C++17 padded_input for convenience.

* adding header

* tuning.

* being explicit

* reworking the docu

* avoid windows.h

* Update include/simdjson/padded_string_view-inl.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/ondemand/ondemand_padded_input.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/ondemand/ondemand_readme_examples.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update doc/performance.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* minor update

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-09 14:07:44 -04:00
Daniel Lemire bc48e72070 literals should be guarded (#2669) 2026-04-09 14:07:29 -04:00
Jaël Champagne Gareau 8955c2c6d5 Fix failing just_ascii test (#2661)
* fix just_ascii errors in test/benchmark sources

* make just_ascii test run in CI
2026-04-05 11:11:35 -04:00
Daniel Lemire 73e69a5e84 Get reflection working without warnings under GCC 16 (#2658)
* wip gcc16

* progress

* minor gcc16 fixes

* final step

* Update include/simdjson/padded_string-inl.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update include/simdjson/padded_string.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* renaming test function

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-03 15:32:42 -04:00
Daniel Lemire fb83b114ef 4.6.1 2026-04-03 15:26:12 -04:00
jmestwa-coder 542cd7af71 parser.load(string_view) does not respect view length when opening files (#2659) 2026-04-03 15:25:03 -04:00
Daniel Lemire 769364abb2 simplifying the logic 2026-03-30 20:01:35 -04:00
Daniel Lemire f25d5f9fc2 4.6.0 (#2655) 2026-03-30 19:51:00 -04:00
Eyüp Can Akman 12fb30ae9c add get_int32() and get_uint32() to the On Demand API (#2638)
* add get_int32() and get_uint32() to the On Demand API

Add convenience methods that call get_int64()/get_uint64() and
range-check the result, returning NUMBER_OUT_OF_RANGE on overflow.

Added to value, document, document_reference, and their
simdjson_result wrappers.

Closes #1890

* fix document_reference streaming for get_int32/get_uint32

The document_reference getters delegated to document::get_int32/get_uint32
which call get_root_int64/get_root_uint64 with allow_trailing_content=true,
rejecting the next document in a stream as trailing content.

Call get_root_value_iterator().get_root_int64/get_root_uint64(false) directly
to allow trailing content, matching get_int64/get_uint64.

Add streaming tests for both getters.

* add get_int32/get_uint32 to basics.md

* add get_int32/get_uint32 to wrong-type error tests

* add get<uint32_t>/get<int32_t> template specializations

Wire the 32-bit getters into the generic get<T>() and get(T&)
dispatch so that doc.get<uint32_t>() compiles on C++11/14/17
without requiring the C++20 concepts path in std_deserialize.h.
2026-03-30 10:16:31 -04:00
uwezkhan c0a952687d Add overflow checks in padded_string_builder::reserve (#2653)
* Add overflow checks in padded_string_builder::reserve

* updated

---------

Co-authored-by: uwezkhan06 <uwezkhan055@gmail.com>
2026-03-30 10:16:05 -04:00
uwezkhan 307ddd4415 Avoid overflow in padded_string_builder::reserve (#2654)
Co-authored-by: uwezkhan06 <uwezkhan055@gmail.com>
2026-03-30 10:13:08 -04:00
Edoardo Bortolozzo 21657455ed build: add SIMDJSON_INSTALL option (#2649) 2026-03-29 15:54:44 -04:00
jmestwa-coder afb1c9f4b1 correct inverted clamp in set_max_capacity (#2651) 2026-03-29 15:54:28 -04:00
uwezkhan 9973f7b3a3 Fix fopen usage with string_view (#2652)
Co-authored-by: uwezkhan06 <uwezkhan055@gmail.com>
2026-03-29 15:54:11 -04:00
Daniel Lemire 18d612df15 4.5.0 2026-03-25 17:26:13 -04:00
harshanagd bddb23177e feat: Add DOM tape support for big integers (opt-in) (#2640)
When parser.number_as_string(true) is set and a number exceeds uint64
range, write the raw digits to the string buffer and emit a BIGINT ('Z')
tape tag instead of returning BIGINT_ERROR. Default behavior unchanged.

Changes:
- tape_type.h: add BIGINT = 'Z'
- dom_parser_implementation.h: add _number_as_string flag
- parser.h: add number_as_string() getter/setter
- parser-inl.h: propagate flag before parse
- tape_builder.h: visit_number checks flag on BIGINT_ERROR
- tape_writer.h: add append_bigint helper
- serialization-inl.h: handle BIGINT in serialization (raw digits)
- tape.md: document big integer tape format

Builds on PR #2139 which added BIGINT_ERROR detection.
All 119 existing tests pass — default behavior unchanged.

Closes #167

Co-authored-by: harshagd <harshagd@amazon.com>
2026-03-25 17:25:37 -04:00
Rajdeep 2c5de6c6ed Fix PCH build failure with <bit> on non-C++20 compilers (#2646)
* Added benchmarks for on-demand get_int64 and get_double

* Remove local IDE settings

* Fix PCH build failure with <bit> on non-C++20 compilers

* Update bench_dom_api.cpp
2026-03-25 17:20:43 -04:00
Daniel Lemire 844e5eac23 bump google bench 2026-03-25 09:29:33 -04:00
Daniel Lemire 45caa861cd Update README with talk information
Added details about talks at QCon San Francisco 2019 and CppCon 2025.
2026-03-24 22:00:42 -04:00
Andrey Abramov 50bf372d3f Add SereneDB to the list of projects (#2641)
Add SereneDB to the list of projects
2026-03-22 12:37:23 -04:00
Daniel Lemire b4a0fd3ff4 4.4.2 2026-03-20 11:12:35 -04:00
Levi Zim a1ff05a5e8 Fix RISC-V compilation without RVV (#2637)
#2617 introduced an RVV check that only checks if the compiler supports RVV intrinsics without checking if RVV is enabled at compile time.

This has caused compilation failure of node.js on riscv64. Fix it by appending a check for __riscv_vector.
2026-03-20 11:11:38 -04:00
Daniel Lemire 5395aacb8b v4.4.1 2026-03-17 12:39:07 -04:00
Daniel Lemire 781ea4b495 fixing issue https://github.com/nodejs/node/pull/62257 (#2631) 2026-03-17 12:38:11 -04:00
Jaël Champagne Gareau 03e8d14eee fix failing CI and rename misleading workflow name (#2630) 2026-03-15 18:40:08 -04:00
Daniel Lemire be94694290 v4.4.0 2026-03-12 20:43:36 -04:00
Daniel Lemire 6065b3b424 disabling loongson LASX runtime dispatching everywhere but GCC15+ (#2626) 2026-03-12 20:40:43 -04:00
Daniel Lemire 019bf1dfe0 adding memory-file mapping (#2625)
* adding memory-file mapping

* tuning

* init _capacity

* adding missing header

* update doc
2026-03-12 14:36:01 -04:00
Daniel Lemire 2922822622 altivec version of find_next_json_quotable_character (#2622)
* altivec version of find_next_json_quotable_character

* simplify

* saving.
2026-03-08 14:06:01 -04:00
Daniel Lemire 262ddad037 Update bug report template with support policy details
Clarified support policy for obsolete compiler systems and vendors.
2026-03-02 14:08:04 -05:00
Hendrik ba007b0cb9 add mruby-fast-json binding (#2616)
Hi there,

i took simdjson as a learning opportunity to learn more about c++, as such i have covered most of the public API Surface simdjson got.

Have fun :)
2026-02-28 14:58:01 -05:00
Sudhanshu Shukla ec675d9c61 Add RISC-V RVV support for find_next_json_quotable_character (#2617) 2026-02-28 13:11:47 -05:00
Daniel Lemire 7dcc196f66 iterating over a simdjson_result<padded_string> (#2614) 2026-02-27 11:01:39 -05:00
Daniel Lemire 7ec4572d33 trimming whitespace 2026-02-26 16:09:34 -05:00
Daniel Lemire 5891d4b64b adding AI policy 2026-02-26 15:09:40 -05:00
Daniel Lemire 3cd9875f1f simplifying the find_next_json_quotable_character fnc for neon processors (#2613) 2026-02-25 01:06:08 -05:00
Daniel Lemire 6cf1bdba43 saving. 2026-02-24 18:01:16 -05:00
Sudhanshu Shukla 817f10dd7d Add LoongArch LSX support for find_next_json_quotable_character (#2608) 2026-02-23 15:00:26 -05:00
Daniel Lemire e829566589 4.3.1 2026-02-20 16:17:16 -05:00
Daniel Lemire dbe0c1543e fix bad hint (#2610) 2026-02-20 16:16:37 -05:00
Daniel Lemire 06c774c655 4.3.0 2026-02-18 23:50:58 -05:00
124 changed files with 40750 additions and 3045 deletions
+4
View File
@@ -29,6 +29,9 @@ We accept the identification of an issue by a sanitizer or some checker tool (e.
We recommend that you run your tests using different optimization levels. In particular, we recommend your run tests with the simdjson library and you code compiled in debug mode. The simdjson then sets the SIMDJSON_DEVELOPMENT_CHECKS macro to 1, and this triggers additional checks on your code and on the internals of the library. If possible, we recommend that you run tests with sanitizers (e.g., see [No more leaks with sanitize flags in gcc and clang](https://lemire.me/blog/2016/04/20/no-more-leaks-with-sanitize-flags-in-gcc-and-clang/)). You can compile the library with sanitizers for debugging purposes (e.g., set SIMDJSON_SANITIZE to ON using CMake), but you should also turn on sanitizers on your own code. You may also use tools like valgrind or the commercial equivalent.
Mixing debug and release simdjson code is unsafe: you either build all your code using simdjson in
release mode or all of it in debug mode.
Before reporting a bug, please ensure that you have read our documentation.
**To Reproduce**
@@ -55,6 +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.
Under Windows, we support Visual Studio (both with LLVM and without). We do not support MinGW and other alternate compiler systems. Windows users should be aware that there [is a long-running bug with GCC under Windows](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412).
+1 -1
View File
@@ -20,7 +20,7 @@ How to verify / test
Please read before contributing:
- CONTRIBUTING: https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md
- HACKING: https://github.com/simdjson/simdjson/blob/master/HACKING.md
- AI Usage Policy: https://github.com/simdjson/simdjson/blob/master/AI_USAGE_POLICY.md
If you can, we recommend running our tests with the sanitizers turned on.
+1 -1
View File
@@ -1,4 +1,4 @@
name: Performance check on Ubuntu 20.04 CI (GCC 9)
name: Performance check on Ubuntu 24.04 CI (GCC 13)
on:
push:
+2 -2
View File
@@ -1,4 +1,4 @@
name: Ubuntu 20.04 CI (GCC 9) without exceptions
name: Ubuntu 24.04 CI (GCC 13) without exceptions
on: [push, pull_request]
@@ -24,7 +24,7 @@ jobs:
cd .. &&
mkdir build &&
cd build &&
cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_EXCEPTIONS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_GOOGLE_BENCHMARKS=ON -DSIMDJSON_EXCEPTIONS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . &&
ctest --output-on-failure -LE explicitonly -j &&
make install &&
+1 -1
View File
@@ -1,4 +1,4 @@
name: Ubuntu 20.04 CI (GCC 9) Without Threads
name: Ubuntu 24.04 CI (GCC 13) Without Threads
on: [push, pull_request]
+2 -2
View File
@@ -1,9 +1,9 @@
name: Ubuntu 20.04 CI (GCC 9) With Memory Sanitizer
name: Ubuntu 24.04 CI (GCC 13) With Memory Sanitizer
on: [push, pull_request]
jobs:
ubuntu-build-address-sanitizier:
ubuntu-build-address-sanitizer:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
+1 -1
View File
@@ -22,4 +22,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 -7
View File
@@ -13,18 +13,20 @@ jobs:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo}
- {gen: Visual Studio 17 2022, arch: Win32, shared: ON, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: Win32, shared: OFF, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: ON, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Debug, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: OFF}
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: RelWithDebInfo, memory_map: OFF}
# Exercise the opt-in Windows memory-file mapping path at least once in CI.
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF, build_type: Release, memory_map: ON}
steps:
- name: checkout
uses: actions/checkout@v4
- name: Configure
run: |
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_COMPETITION=OFF -DBUILD_SHARED_LIBS=${{matrix.shared}} -DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=${{matrix.memory_map}} -B build
- name: Build Debug
run: cmake --build build --config ${{matrix.build_type}} --verbose
- name: Run tests
+56
View File
@@ -0,0 +1,56 @@
# AI Usage Policy
Contributors can use whatever tools they would like to
craft their contributions, but there must be a **human in the loop**.
**Contributors must read and review all LLM-generated code or text before they
ask other project members to review it.** The contributor is always the author
and is fully accountable for their contributions. Contributors should be
sufficiently confident that the contribution is high enough quality that asking
for a review is a good use of scarce maintainer time, and they should be **able
to answer questions about their work** during review.
We expect that new contributors will be less confident in their contributions,
and our guidance to them is to **start with small contributions** that they can
fully understand to build confidence. We aspire to be a welcoming community
that helps new contributors grow their expertise, but learning involves taking
small steps, getting feedback, and iterating. Passing maintainer feedback to an
LLM doesn't help anyone grow, and does not sustain our community.
This policy includes, but is not limited to, the following kinds of
contributions:
- Code, usually in the form of a pull request
- Issues or security vulnerabilities
- Comments and feedback on pull requests
## Extractive Contributions
The reason for our "human-in-the-loop" contribution policy is that processing
patches, PRs, RFCs, and comments is not free -- it takes a lot of
maintainer time and energy to review those contributions! Sending the
unreviewed output of an LLM to open source project maintainers *extracts* work
from them in the form of design and code review, so we call this kind of
contribution an "extractive contribution".
## Transparency
For contributions involving significant AI assistance, we encourage you to disclose
its use and explain your process. If a submission appears to rely heavily on AI
without disclosure, we may doubt that the **human-in-the-loop** requirement has
been met. Please show awareness of your use of AI.
## Copyright
Artificial intelligence systems raise many questions around copyright that have
yet to be answered. Our policy on AI tools is similar to our copyright policy:
Contributors are responsible for ensuring that they have the right to
contribute code under the terms of our license, typically meaning that either
they, their employer, or their collaborators hold the copyright. Using AI tools
to regenerate copyrighted material does not remove the copyright, and
contributors are responsible for ensuring that such material does not appear in
their contributions. Contributions found to violate this policy will be removed
just like any other offending contribution.
## Reference
- [LLVM AI Tool Use Policy](https://discourse.llvm.org/t/rfc-llvm-ai-tool-policy-human-in-the-loop/89159)
+170 -90
View File
@@ -12,7 +12,7 @@ endif()
project(
simdjson
# The version number is modified by tools/release.py
VERSION 4.2.4
VERSION 4.6.1
DESCRIPTION "Parsing gigabytes of JSON per second"
HOMEPAGE_URL "https://simdjson.org/"
LANGUAGES CXX C
@@ -29,8 +29,8 @@ string(
# ---- Options, variables ----
# These version numbers are modified by tools/release.py
set(SIMDJSON_LIB_VERSION "29.0.0" CACHE STRING "simdjson library version")
set(SIMDJSON_LIB_SOVERSION "29" CACHE STRING "simdjson library soversion")
set(SIMDJSON_LIB_VERSION "33.0.0" CACHE STRING "simdjson library version")
set(SIMDJSON_LIB_SOVERSION "33" CACHE STRING "simdjson library soversion")
option(SIMDJSON_BUILD_STATIC_LIB "Build simdjson_static library along with simdjson (only makes sense if BUILD_SHARED_LIBS=ON)" OFF)
if(SIMDJSON_BUILD_STATIC_LIB AND NOT BUILD_SHARED_LIBS)
@@ -75,10 +75,48 @@ if(SIMDJSON_DEVELOPMENT_CHECKS)
)
endif()
# padded_memory_map is always available on POSIX. On Windows it is disabled
# by default because it depends on the `CreateFileMapping2` / `MapViewOfFile3`
# APIs, which require Windows 10 version 1803 or later and are exported via
# onecore.lib rather than the default kernel32.lib. Turn this option ON to
# opt into the feature on Windows; simdjson will then set the appropriate
# Windows version macros and link onecore, so everything that links
# simdjson picks up both the compile-time declarations and the import
# library automatically. The option is a no-op on POSIX (where the feature
# is unconditionally enabled).
option(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
"Enable simdjson::padded_memory_map on Windows (requires Windows 10 \
version 1803 or later). Always enabled on POSIX." OFF)
if(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS)
simdjson_add_props(
target_compile_definitions PUBLIC
SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1
)
if(WIN32)
# Raise the Windows version floor so that <windows.h> declares the
# modern memory-mapping APIs, and link the import library that
# actually exports them. _WIN32_WINNT / WINVER / NTDDI_VERSION together
# tell <sdkddkver.h> which APIs to light up.
simdjson_add_props(
target_compile_definitions PUBLIC
_WIN32_WINNT=0x0A00
WINVER=0x0A00
NTDDI_VERSION=0x0A000006 # NTDDI_WIN10_RS5, Windows 10 version 1809
)
simdjson_add_props(
target_link_libraries PUBLIC
onecore
)
endif()
endif()
if(is_top_project)
option(SIMDJSON_INSTALL "Enable target install" ON)
option(SIMDJSON_DEVELOPER_MODE "Enable targets for developing simdjson" OFF)
option(BUILD_SHARED_LIBS "Build simdjson as a shared library" OFF)
option(SIMDJSON_SINGLEHEADER "Disable singleheader generation" ON)
else()
option(SIMDJSON_INSTALL "Enable target install" ${BUILD_SHARED_LIBS})
endif()
include(cmake/handle-deprecations.cmake)
@@ -92,13 +130,16 @@ add_library(simdjson ${SIMDJSON_SOURCES})
add_library(simdjson::simdjson ALIAS simdjson)
set(SIMDJSON_LIBRARIES simdjson)
# Check for <bit> header compatibility
include(CheckIncludeFileCXX)
check_include_file_cxx(bit SIMDJSON_HAS_BIT_HEADER)
# Enable precompiled headers for faster builds
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
target_precompile_headers(simdjson PRIVATE
set(SIMDJSON_PRECOMPILE_HEADERS
<algorithm>
<array>
<atomic>
<bit>
<cassert>
<cctype>
<cerrno>
@@ -111,6 +152,12 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16")
<utility>
<vector>
)
if(SIMDJSON_HAS_BIT_HEADER)
list(APPEND SIMDJSON_PRECOMPILE_HEADERS <bit>)
endif()
target_precompile_headers(simdjson PRIVATE ${SIMDJSON_PRECOMPILE_HEADERS})
endif()
if(SIMDJSON_BUILD_STATIC_LIB)
@@ -155,13 +202,37 @@ if(MSVC)
endif()
endif()
if(SIMDJSON_STATIC_REFLECTION)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION MATCHES "^21")
execute_process(
COMMAND ${CMAKE_CXX_COMPILER} --version
OUTPUT_VARIABLE CLANG_VERSION_OUTPUT
ERROR_VARIABLE CLANG_VERSION_ERROR
RESULT_VARIABLE CLANG_VERSION_RESULT
)
if(CLANG_VERSION_RESULT EQUAL 0 AND CLANG_VERSION_OUTPUT MATCHES "https://github.com/bloomberg/clang-p2996.git")
set(IS_BLOOMBERG_P2996_CLANG ON)
message(STATUS "Using Bloomberg P2996 Clang fork")
endif()
endif()
# We would like to require C++26, but no compiler supports that!
# This is a hack:
if(IS_BLOOMBERG_P2996_CLANG)
simdjson_add_props(
target_compile_options PUBLIC
-freflection -fexpansion-statements -stdlib=libc++ -std=c++26
)
else()
simdjson_add_props(
target_compile_options PUBLIC
-freflection -std=c++26
)
endif()
else()
simdjson_add_props(target_compile_features PUBLIC cxx_std_11)
endif()
@@ -216,87 +287,89 @@ endif()
# ---- Install rules ----
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
if(SIMDJSON_INSTALL)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
if(SIMDJSON_SINGLEHEADER)
install(
FILES singleheader/simdjson.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT simdjson_Development
)
if(SIMDJSON_SINGLEHEADER)
install(
FILES singleheader/simdjson.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT simdjson_Development
)
endif()
install(
TARGETS simdjson
EXPORT simdjsonTargets
RUNTIME COMPONENT simdjson_Runtime
LIBRARY COMPONENT simdjson_Runtime
NAMELINK_COMPONENT simdjson_Development
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
configure_file(cmake/simdjson-config.cmake.in simdjson-config.cmake @ONLY)
write_basic_package_version_file(
simdjson-config-version.cmake
COMPATIBILITY SameMinorVersion
)
set(
SIMDJSON_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/simdjson"
CACHE STRING "CMake package config location relative to the install prefix"
)
mark_as_advanced(SIMDJSON_INSTALL_CMAKEDIR)
install(
FILES
"${PROJECT_BINARY_DIR}/simdjson-config.cmake"
"${PROJECT_BINARY_DIR}/simdjson-config-version.cmake"
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
install(
EXPORT simdjsonTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
if(SIMDJSON_BUILD_STATIC_LIB)
install(
TARGETS simdjson_static
EXPORT simdjson_staticTargets
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(
EXPORT simdjson_staticTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
endif()
# pkg-config
include(cmake/JoinPaths.cmake)
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
if(SIMDJSON_ENABLE_THREADS)
set(PKGCONFIG_CFLAGS "-DSIMDJSON_THREADS_ENABLED=1")
if(CMAKE_THREAD_LIBS_INIT)
set(PKGCONFIG_LIBS_PRIVATE "Libs.private: ${CMAKE_THREAD_LIBS_INIT}")
endif()
endif()
configure_file("simdjson.pc.in" "simdjson.pc" @ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
)
endif()
install(
TARGETS simdjson
EXPORT simdjsonTargets
RUNTIME COMPONENT simdjson_Runtime
LIBRARY COMPONENT simdjson_Runtime
NAMELINK_COMPONENT simdjson_Development
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
configure_file(cmake/simdjson-config.cmake.in simdjson-config.cmake @ONLY)
write_basic_package_version_file(
simdjson-config-version.cmake
COMPATIBILITY SameMinorVersion
)
set(
SIMDJSON_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/simdjson"
CACHE STRING "CMake package config location relative to the install prefix"
)
mark_as_advanced(SIMDJSON_INSTALL_CMAKEDIR)
install(
FILES
"${PROJECT_BINARY_DIR}/simdjson-config.cmake"
"${PROJECT_BINARY_DIR}/simdjson-config-version.cmake"
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
install(
EXPORT simdjsonTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
if(SIMDJSON_BUILD_STATIC_LIB)
install(
TARGETS simdjson_static
EXPORT simdjson_staticTargets
ARCHIVE COMPONENT simdjson_Development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
install(
EXPORT simdjson_staticTargets
NAMESPACE simdjson::
DESTINATION "${SIMDJSON_INSTALL_CMAKEDIR}"
COMPONENT simdjson_Development
)
endif()
# pkg-config
include(cmake/JoinPaths.cmake)
join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
join_paths(PKGCONFIG_LIBDIR "\${prefix}" "${CMAKE_INSTALL_LIBDIR}")
if(SIMDJSON_ENABLE_THREADS)
set(PKGCONFIG_CFLAGS "-DSIMDJSON_THREADS_ENABLED=1")
if(CMAKE_THREAD_LIBS_INIT)
set(PKGCONFIG_LIBS_PRIVATE "Libs.private: ${CMAKE_THREAD_LIBS_INIT}")
endif()
endif()
configure_file("simdjson.pc.in" "simdjson.pc" @ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/simdjson.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
)
#
# CPack
#
@@ -373,18 +446,25 @@ add_subdirectory(fuzz)
#
# Source files should be just ASCII
#
find_program(FIND find)
find_program(FILE file)
find_program(GREP grep)
if(FIND AND FILE AND GREP)
find_program(FIND_CMD find)
find_program(FILE_CMD file)
find_program(GREP_CMD grep)
if(FIND_CMD AND FILE_CMD AND GREP_CMD)
add_test(
NAME just_ascii
COMMAND sh -c "\
${FIND} include src windows tools singleheader tests examples benchmark \
-path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \
-type f -exec ${FILE} '{}' \; | ${GREP} -qv ASCII || exit 0 && exit 1"
non_ascii=$(${FIND_CMD} include src windows tools singleheader tests examples benchmark \
-path benchmark/checkperf-reference -prune -name '*.h' -o -name '*.cpp' \
-type f -exec ${FILE_CMD} '{}' \; | ${GREP_CMD} -v ASCII); \
if [ -n \"$non_ascii\" ]; then \
echo 'The following files contain non-ASCII characters:'; \
echo \"$non_ascii\"; \
exit 1; \
fi"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
)
else()
message(WARNING "just_ascii test disabled because required tools were not found: find='${FIND_CMD}', file='${FILE_CMD}', grep='${GREP_CMD}'")
endif()
##
+7
View File
@@ -101,3 +101,10 @@ Getting Started Hacking
An overview of simdjson's directory structure, with pointers to architecture and design
considerations and other helpful notes, can be found at [HACKING.md](HACKING.md).
AI Usage Policy
---------------
Please also review our [AI Usage Policy](AI_USAGE_POLICY.md).
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = "4.2.4"
PROJECT_NUMBER = "4.6.1"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+9 -1
View File
@@ -65,6 +65,7 @@ Real-world usage
- [GreptimeDB](https://github.com/GreptimeTeam/greptimedb)
- [mamba](https://github.com/mamba-org/mamba)
- [Ladybird Browser](https://ladybird.org)
- [SereneDB](https://github.com/serenedb/serenedb)
If you are planning to use simdjson in a product, please work from one of our releases.
@@ -188,6 +189,7 @@ We distinguish between "bindings" (which just wrap the C++ code) and a port to a
- [JSON::SIMD](https://metacpan.org/pod/JSON::SIMD): Perl bindings; fully-featured JSON module that uses simdjson for decoding.
- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings.
- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port.
- [mruby-fast-json](https://github.com/Asmod4n/mruby-fast-json): mruby binding with high API coverage.
About simdjson
--------------
@@ -210,10 +212,16 @@ 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, <br />
For the video inclined, we had a talk at QCon San Francisco 2019<br />
[![simdjson at QCon San Francisco 2019](http://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](http://www.youtube.com/watch?v=wlvKAT7SZIQ)<br />
(It was the best voted talk, we're kinda proud of it.)
We also had a CppCon 2025 talk. We show how C++26 reflection allows for one-line serialization (to_json(player)) or deserialization—without invasive macros or manual mapping—using nothing but the C++ standard library. Whether youre a performance junkie or simply interested in the roadmap for the next decade of C++ development, watch our full talk!
[![simdjson at CppCon 2025](http://img.youtube.com/vi/Mcgk3CxHYMs/0.jpg)](http://www.youtube.com/watch?v=Mcgk3CxHYMs)<br />
Citing this work
-----------------
-23
View File
@@ -1,23 +0,0 @@
CXX=clang++ cmake -B buildreflect -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_BRANCH_HINTS=ON
cmake --build buildreflect --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
./buildreflect/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
./buildreflect/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
CXX=clang++ cmake -B buildreflecthints -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_BRANCH_HINTS=ON
cmake --build buildreflecthints --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
./buildreflecthints/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
./buildreflecthints/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
CXX=clang++ cmake -B buildreflectescaping -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_SIMD_ESCAPING=ON
cmake --build buildreflectescaping --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
./buildreflectescaping/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
./buildreflectescaping/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
CXX=clang++ cmake -B buildreflectconsteval -D SIMDJSON_STATIC_REFLECTION=ON -DSIMDJSON_DEVELOPER_MODE=ON -DSIMDJSON_ABLATION_NO_CONSTEVAL=ON
cmake --build buildreflectconsteval --target benchmark_serialization_citm_catalog benchmark_serialization_twitter
./buildreflectconsteval/benchmark/static_reflect/citm_catalog_benchmark/benchmark_serialization_citm_catalog
./buildreflectconsteval/benchmark/static_reflect/twitter_benchmark/benchmark_serialization_twitter
+3 -1
View File
@@ -1,9 +1,10 @@
add_subdirectory(dom)
include_directories( . linux )
include_directories( . )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
link_libraries(counters)
if(SIMDJSON_STATIC_REFLECTION)
add_compile_definitions(SIMDJSON_STATIC_REFLECTION=1)
endif(SIMDJSON_STATIC_REFLECTION)
@@ -15,6 +16,7 @@ if (TARGET benchmark::benchmark)
link_libraries(benchmark::benchmark)
add_executable(bench_parse_call bench_parse_call.cpp)
add_executable(bench_dom_api bench_dom_api.cpp)
add_executable(bench_stream_formats bench_stream_formats.cpp)
if(SIMDJSON_EXCEPTIONS)
add_executable(bench_ondemand bench_ondemand.cpp)
if(TARGET yyjson)
File diff suppressed because it is too large Load Diff
+1
View File
@@ -40,6 +40,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
#include "json2msgpack/boostjson.h"
#include "partial_tweets/simdjson_ondemand.h"
#include "partial_tweets/simdjson_ondemand_key_selector.h"
#include "partial_tweets/simdjson_dom.h"
#include "partial_tweets/yyjson.h"
#if SIMDJSON_COMPETITION_ONDEMAND_SAJSON
+216
View File
@@ -0,0 +1,216 @@
#include <benchmark/benchmark.h>
#include <string>
#include "simdjson.h"
using namespace simdjson;
namespace {
enum class stream_case {
ndjson_small,
ndjson_large,
rfc7464_small,
rfc7464_large,
comma_delimited_small,
comma_delimited_large
};
constexpr size_t TARGET_BYTES = 128 * 1000 * 1000;
constexpr size_t SMALL_PAYLOAD = 16;
constexpr size_t LARGE_PAYLOAD = 4096;
constexpr size_t BATCH_SIZE = 1 << 20;
struct stream_dataset {
padded_string json;
size_t count{};
};
std::string make_document(size_t id, size_t payload_size) {
return std::string{"{\"id\":"} + std::to_string(id) +
",\"name\":\"aaaaaaaa\",\"payload\":\"" +
std::string(payload_size, 'x') + "\",\"flag\":true}";
}
stream_dataset build_dataset(stream_case which) {
const bool small = which == stream_case::ndjson_small ||
which == stream_case::rfc7464_small ||
which == stream_case::comma_delimited_small;
const bool rfc = which == stream_case::rfc7464_small ||
which == stream_case::rfc7464_large;
const bool comma = which == stream_case::comma_delimited_small ||
which == stream_case::comma_delimited_large;
const size_t payload_size = small ? SMALL_PAYLOAD : LARGE_PAYLOAD;
const size_t count = TARGET_BYTES / (payload_size + 48);
std::string out;
out.reserve(count * (payload_size + 64));
for (size_t i = 0; i < count; i++) {
if (rfc) {
out += char(0x1E);
}
if (comma && i > 0) {
out += ',';
}
out += make_document(i, payload_size);
if (!comma) {
out += '\n';
}
}
return {padded_string(out), count};
}
const stream_dataset &get_dataset(stream_case which) {
static const stream_dataset ndjson_small =
build_dataset(stream_case::ndjson_small);
static const stream_dataset ndjson_large =
build_dataset(stream_case::ndjson_large);
static const stream_dataset rfc_small =
build_dataset(stream_case::rfc7464_small);
static const stream_dataset rfc_large =
build_dataset(stream_case::rfc7464_large);
static const stream_dataset comma_small =
build_dataset(stream_case::comma_delimited_small);
static const stream_dataset comma_large =
build_dataset(stream_case::comma_delimited_large);
switch (which) {
case stream_case::ndjson_small:
return ndjson_small;
case stream_case::ndjson_large:
return ndjson_large;
case stream_case::rfc7464_small:
return rfc_small;
case stream_case::rfc7464_large:
return rfc_large;
case stream_case::comma_delimited_small:
return comma_small;
case stream_case::comma_delimited_large:
return comma_large;
}
return ndjson_small;
}
void set_counters(benchmark::State &state, const stream_dataset &dataset) {
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(dataset.json.size()));
state.SetItemsProcessed(int64_t(state.iterations()) * int64_t(dataset.count));
}
template <stream_case which, bool threaded = true>
static void bench_ondemand(benchmark::State &state) {
const auto &dataset = get_dataset(which);
ondemand::parser parser;
parser.threaded = threaded;
stream_format format = stream_format::whitespace_delimited;
if constexpr (which == stream_case::rfc7464_small ||
which == stream_case::rfc7464_large) {
format = stream_format::json_sequence;
} else if constexpr (which == stream_case::comma_delimited_small ||
which == stream_case::comma_delimited_large) {
format = stream_format::comma_delimited;
}
for (const auto _ : state) {
ondemand::document_stream docs;
auto error = parser.iterate_many(dataset.json, BATCH_SIZE, format).get(docs);
if (error) {
state.SkipWithError(error_message(error));
return;
}
uint64_t sum = 0;
for (auto doc : docs) {
ondemand::object obj;
if ((error = doc.get_object().get(obj))) {
state.SkipWithError(error_message(error));
return;
}
uint64_t id;
if ((error = obj["id"].get_uint64().get(id))) {
state.SkipWithError(error_message(error));
return;
}
sum += id;
}
benchmark::DoNotOptimize(sum);
}
set_counters(state, dataset);
}
template <stream_case which>
static void bench_dom(benchmark::State &state) {
const auto &dataset = get_dataset(which);
dom::parser parser;
parser.threaded = true;
stream_format format = stream_format::whitespace_delimited;
if constexpr (which == stream_case::rfc7464_small ||
which == stream_case::rfc7464_large) {
format = stream_format::json_sequence;
} else if constexpr (which == stream_case::comma_delimited_small ||
which == stream_case::comma_delimited_large) {
format = stream_format::comma_delimited;
}
for (const auto _ : state) {
dom::document_stream docs;
auto error = parser.parse_many(dataset.json, BATCH_SIZE, format).get(docs);
if (error) {
state.SkipWithError(error_message(error));
return;
}
uint64_t sum = 0;
for (auto doc : docs) {
uint64_t id;
if ((error = doc["id"].get(id))) {
state.SkipWithError(error_message(error));
return;
}
sum += id;
}
benchmark::DoNotOptimize(sum);
}
set_counters(state, dataset);
}
} // namespace
BENCHMARK(bench_ondemand<stream_case::ndjson_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::ndjson_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::rfc7464_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::rfc7464_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
// Non-threaded comma_delimited for comparison
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small, false>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large, false>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::ndjson_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::ndjson_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::rfc7464_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::rfc7464_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::comma_delimited_small>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK(bench_dom<stream_case::comma_delimited_large>)
->UseRealTime()
->DisplayAggregatesOnly(true);
BENCHMARK_MAIN();
+2 -39
View File
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <cassert>
#include <cctype>
@@ -25,7 +26,6 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
@@ -204,12 +204,8 @@ struct feature_benchmarker {
}
// Rate of 1-7-structural misses per 8-structural flip
double struct1_7_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return struct7_miss[stage].best.branch_misses() - struct7[stage].best.branch_misses() / double(struct7_miss.stats->blocks_with_1_structural_flipped);
#endif
}
// Extra cost of an 8-15 structural block over a 1-7 structural block
double struct8_15_cost(BenchmarkStage stage) const {
@@ -221,12 +217,8 @@ struct feature_benchmarker {
}
// Rate of 8-15-structural misses per 8-structural flip
double struct8_15_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(struct15_miss[stage].best.branch_misses() - struct15[stage].best.branch_misses()) / double(struct15_miss.stats->blocks_with_8_structurals_flipped);
#endif
}
// Extra cost of a 16+-structural block over an 8-15 structural block (actual varies based on # of structurals!)
@@ -239,12 +231,8 @@ struct feature_benchmarker {
}
// Rate of 16-structural misses per 16-structural flip
double struct16_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(struct23_miss[stage].best.branch_misses() - struct23[stage].best.branch_misses()) / double(struct23_miss.stats->blocks_with_16_structurals_flipped);
#endif
}
@@ -258,12 +246,8 @@ struct feature_benchmarker {
}
// Rate of UTF-8 misses per UTF-8 flip
double utf8_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(utf8_miss[stage].best.branch_misses() - utf8[stage].best.branch_misses()) / double(utf8_miss.stats->blocks_with_utf8_flipped);
#endif
}
// Extra cost of having escapes in a block
double escape_cost(BenchmarkStage stage) const {
@@ -275,12 +259,8 @@ struct feature_benchmarker {
}
// Rate of escape misses per escape flip
double escape_miss_rate(BenchmarkStage stage) const {
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
return 1;
#else
if (!has_events()) { return 1; }
return double(escape_miss[stage].best.branch_misses() - escape[stage].best.branch_misses()) / double(escape_miss.stats->blocks_with_escapes_flipped);
#endif
}
@@ -378,22 +358,6 @@ struct feature_benchmarker {
}
};
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
double calc_misses = features.calc_expected_misses(stage, results);
double calc_miss_cost = features.calc_expected_miss_cost(stage, results);
printf(" | %-8s ", benchmark_stage_name(stage));
printf("| %-15s ", filename);
printf("| %8.3g ", features.calc_expected_feature_cost(stage, results));
printf("| %8.3g ", calc_miss_cost);
printf("| %8.3g ", calc);
printf("| %8.3g ", actual);
printf("| %+8.3g ", actual - calc);
printf("| %13llu ", (long long unsigned)(calc_misses));
}
#else
void print_file_effectiveness(BenchmarkStage stage, const char* filename, const benchmarker& results, const feature_benchmarker& features) {
double actual = results[stage].best.elapsed_ns() / double(results.stats->blocks);
double calc = features.calc_expected(stage, results);
@@ -417,7 +381,6 @@ void print_file_effectiveness(BenchmarkStage stage, const char* filename, const
}
printf("|\n");
}
#endif
int main(int argc, char *argv[]) {
// Read options
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef _BENCHMARK_H_
#define _BENCHMARK_H_
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
/*
* Prints the best number of operations per cycle where
+4 -11
View File
@@ -1,7 +1,8 @@
#ifndef __BENCHMARKER_H
#define __BENCHMARKER_H
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include "simdjson.h"
#include <cassert>
@@ -28,11 +29,9 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
#include "simdjson.h"
#include <functional>
@@ -423,18 +422,12 @@ struct benchmarker {
stage.instructions() / static_cast<double>(stats->structurals),
stage.instructions() / static_cast<double>(stage.cycles())
);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
// NOTE: removed cycles/miss because it is a somewhat misleading stat
printf("%s%-13s: %7.0f branch misses (%6.2f%%) - %.0f cache misses (%6.2f%%) - %.2f cache references\n",
printf("%s%-13s: %7.0f branch misses (%6.2f%%)\n",
prefix,
"Misses",
stage.branch_misses(),
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses()),
stage.cache_misses(),
percent(stage.cache_misses(), all_stages_without_allocation.cache_misses()),
stage.cache_references()
percent(stage.branch_misses(), all_stages_without_allocation.branch_misses())
);
#endif
}
}
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <random>
#include <vector>
+3 -2
View File
@@ -1,11 +1,12 @@
include_directories( .. ../linux )
include_directories( .. )
link_libraries(simdjson-windows-headers test-data)
link_libraries(simdjson)
link_libraries(counters)
add_executable(perfdiff perfdiff.cpp)
add_executable(parse parse.cpp)
add_executable(parse_stream parse_stream.cpp)
add_executable(statisticalmodel statisticalmodel.cpp)
add_executable(parse_noutf8validation parse.cpp)
target_compile_definitions(parse_noutf8validation PRIVATE SIMDJSON_SKIPUTF8VALIDATION)
+2 -2
View File
@@ -1,4 +1,5 @@
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <cassert>
#include <cctype>
@@ -24,7 +25,6 @@
#include <string>
#include <vector>
#include "linux-perf-events.h"
#ifdef __linux__
#include <libgen.h>
#endif
-207
View File
@@ -1,207 +0,0 @@
#include <iostream>
#include <unistd.h>
#include "simdjson.h"
#ifdef __linux__
#include "linux-perf-events.h"
#endif
size_t count_nonasciibytes(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += input[i] >> 7;
}
return count;
}
size_t count_backslash(const uint8_t *input, size_t length) {
size_t count = 0;
for (size_t i = 0; i < length; i++) {
count += (input[i] == '\\') ? 1 : 0;
}
return count;
}
struct stat_s {
size_t integer_count;
size_t float_count;
size_t string_count;
size_t backslash_count;
size_t non_ascii_byte_count;
size_t object_count;
size_t array_count;
size_t null_count;
size_t true_count;
size_t false_count;
size_t byte_count;
size_t structural_indexes_count;
bool valid;
};
using stat_t = struct stat_s;
simdjson_inline void simdjson_process_atom(stat_t &s,
simdjson::dom::element element) {
if (element.is<int64_t>()) {
s.integer_count++;
} else if(element.is<std::string_view>()) {
s.string_count++;
} else if(element.is<double>()) {
s.float_count++;
} else if (element.is<bool>()) {
bool v;
simdjson::error_code error;
if ((error = element.get(v))) { std::cerr << error << std::endl; abort(); }
if (v) {
s.true_count++;
} else {
s.false_count++;
}
} else if (element.is_null()) {
s.null_count++;
}
}
void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
simdjson::error_code error;
if (element.is<simdjson::dom::array>()) {
s.array_count++;
simdjson::dom::array array;
if ((error = element.get(array))) { std::cerr << error << std::endl; abort(); }
for (auto child : array) {
if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
simdjson_recurse(s, child);
} else {
simdjson_process_atom(s, child);
}
}
} else if (element.is<simdjson::dom::object>()) {
s.object_count++;
simdjson::dom::object object;
if ((error = element.get(object))) { std::cerr << error << std::endl; abort(); }
for (auto field : object) {
s.string_count++; // for key
if (field.value.is<simdjson::dom::array>() || field.value.is<simdjson::dom::object>()) {
simdjson_recurse(s, field.value);
} else {
simdjson_process_atom(s, field.value);
}
}
} else {
simdjson_process_atom(s, element);
}
}
stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
stat_t answer{};
simdjson::dom::parser parser;
simdjson::dom::element doc;
auto error = parser.parse(p).get(doc);
if (error) {
answer.valid = false;
return answer;
}
answer.valid = true;
answer.backslash_count =
count_backslash(reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.non_ascii_byte_count = count_nonasciibytes(
reinterpret_cast<const uint8_t *>(p.data()), p.size());
answer.byte_count = p.size();
answer.structural_indexes_count = parser.implementation->n_structural_indexes;
simdjson_recurse(answer, doc);
return answer;
}
int main(int argc, char *argv[]) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "")) != -1) {
switch (c) {
default:
abort();
}
}
#else
int optind = 1;
#endif
if (optind >= argc) {
std::cerr << "Reads json, prints stats. " << std::endl;
std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
exit(1);
}
const char *filename = argv[optind];
if (optind + 1 < argc) {
std::cerr << "warning: ignoring everything after " << argv[optind + 1]
<< std::endl;
}
simdjson::padded_string p;
auto error = simdjson::padded_string::load(filename).get(p);
if (error) {
std::cerr << "Could not load the file " << filename << std::endl;
return EXIT_FAILURE;
}
stat_t s = simdjson_compute_stats(p);
if (!s.valid) {
std::cerr << "not a valid JSON" << std::endl;
return EXIT_FAILURE;
}
printf("# integer_count float_count string_count backslash_count "
"non_ascii_byte_count object_count array_count null_count true_count "
"false_count byte_count structural_indexes_count ");
#ifdef __linux__
printf(" stage1_cycle_count stage1_instruction_count stage2_cycle_count "
" stage2_instruction_count stage3_cycle_count "
"stage3_instruction_count ");
#else
printf("(you are not under linux, so perf counters are disaabled)");
#endif
printf("\n");
printf("%zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu %zu ", s.integer_count,
s.float_count, s.string_count, s.backslash_count,
s.non_ascii_byte_count, s.object_count, s.array_count, s.null_count,
s.true_count, s.false_count, s.byte_count, s.structural_indexes_count);
#ifdef __linux__
simdjson::dom::parser parser;
simdjson::error_code alloc_error = parser.allocate(p.size());
if (alloc_error) {
std::cerr << alloc_error << std::endl;
return EXIT_FAILURE;
}
const uint32_t iterations = p.size() < 1 * 1000 * 1000 ? 1000 : 50;
std::vector<int> evts;
evts.push_back(PERF_COUNT_HW_CPU_CYCLES);
evts.push_back(PERF_COUNT_HW_INSTRUCTIONS);
LinuxEvents<PERF_TYPE_HARDWARE> unified(evts);
unsigned long cy1 = 0, cy2 = 0;
unsigned long cl1 = 0, cl2 = 0;
std::vector<unsigned long long> results;
results.resize(evts.size());
for (uint32_t i = 0; i < iterations; i++) {
unified.start();
// The default template is simdjson::architecture::NATIVE.
bool isok = (parser.implementation->stage1((const uint8_t *)p.data(), p.size(), simdjson::stage1_mode::regular) == simdjson::SUCCESS);
unified.end(results);
cy1 += results[0];
cl1 += results[1];
unified.start();
isok = isok && (parser.implementation->stage2(parser.doc) == simdjson::SUCCESS);
unified.end(results);
cy2 += results[0];
cl2 += results[1];
if (!isok) {
std::cerr << "failure?" << std::endl;
}
}
printf("%f %f %f %f ", static_cast<double>(cy1) / static_cast<double>(iterations), static_cast<double>(cl1) / static_cast<double>(iterations),
static_cast<double>(cy2) / static_cast<double>(iterations), static_cast<double>(cl2) / static_cast<double>(iterations));
#endif // __linux__
printf("\n");
return EXIT_SUCCESS;
}
-201
View File
@@ -1,201 +0,0 @@
#ifndef __EVENT_COUNTER_H
#define __EVENT_COUNTER_H
#ifndef SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
#ifdef __aarch64__
// on ARM, we use just cycles and instructions
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 1
#else
// elsewhere, we try to use four counters.
#define SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS 0
#endif
#endif
#include <cassert>
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#ifdef __linux__
#include "linux-perf-events.h"
#include <libgen.h>
#endif
#if __APPLE__ && __aarch64__
#include "apple/apple_arm_events.h"
#endif
#include "simdjson.h"
using std::string;
using std::vector;
using std::chrono::steady_clock;
using std::chrono::time_point;
using std::chrono::duration;
struct event_count {
duration<double> elapsed;
vector<unsigned long long> event_counts;
event_count() : elapsed(0), event_counts{0,0,0,0,0} {}
event_count(const duration<double> _elapsed, const vector<unsigned long long> _event_counts) : elapsed(_elapsed), event_counts(_event_counts) {}
event_count(const event_count& other): elapsed(other.elapsed), event_counts(other.event_counts) { }
// The types of counters (so we can read the getter more easily)
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS
};
#else
enum event_counter_types {
CPU_CYCLES,
INSTRUCTIONS,
BRANCH_MISSES,
CACHE_REFERENCES,
CACHE_MISSES
};
#endif
double elapsed_sec() const { return duration<double>(elapsed).count(); }
double elapsed_ns() const { return duration<double, std::nano>(elapsed).count(); }
double cycles() const { return static_cast<double>(event_counts[CPU_CYCLES]); }
double instructions() const { return static_cast<double>(event_counts[INSTRUCTIONS]); }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return static_cast<double>(event_counts[BRANCH_MISSES]); }
double cache_references() const { return static_cast<double>(event_counts[CACHE_REFERENCES]); }
double cache_misses() const { return static_cast<double>(event_counts[CACHE_MISSES]); }
#endif
event_count& operator=(const event_count& other) {
this->elapsed = other.elapsed;
this->event_counts = other.event_counts;
return *this;
}
event_count operator+(const event_count& other) const {
return event_count(elapsed+other.elapsed, {
event_counts[0]+other.event_counts[0],
event_counts[1]+other.event_counts[1],
event_counts[2]+other.event_counts[2],
event_counts[3]+other.event_counts[3],
event_counts[4]+other.event_counts[4],
});
}
void operator+=(const event_count& other) {
*this = *this + other;
}
};
struct event_aggregate {
int iterations = 0;
event_count total{};
event_count best{};
event_count worst{};
event_aggregate() {}
void operator<<(const event_count& other) {
if (iterations == 0 || other.elapsed < best.elapsed) {
best = other;
}
if (iterations == 0 || other.elapsed > worst.elapsed) {
worst = other;
}
iterations++;
total += other;
}
double elapsed_sec() const { return total.elapsed_sec() / iterations; }
double total_elapsed_ns() const { return total.elapsed_ns(); }
double elapsed_ns() const { return total.elapsed_ns() / iterations; }
double cycles() const { return total.cycles() / iterations; }
double instructions() const { return total.instructions() / iterations; }
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
double branch_misses() const { return total.branch_misses() / iterations; }
double cache_references() const { return total.cache_references() / iterations; }
double cache_misses() const { return total.cache_misses() / iterations; }
#endif
};
struct event_collector {
event_count count{};
time_point<steady_clock> start_clock{};
#if defined(__linux__)
LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
event_collector() : linux_events(vector<int>{
#if SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
#else
PERF_COUNT_HW_CPU_CYCLES,
PERF_COUNT_HW_INSTRUCTIONS,
PERF_COUNT_HW_BRANCH_MISSES,
PERF_COUNT_HW_CACHE_REFERENCES,
PERF_COUNT_HW_CACHE_MISSES
#endif
}) {}
bool has_events() {
return linux_events.is_working();
}
#elif __APPLE__ && __aarch64__
AppleEvents apple_events;
performance_counters diff;
event_collector() : diff(0) {
apple_events.setup_performance_counters();
}
bool has_events() {
return apple_events.setup_performance_counters();
}
#else
event_collector() {}
bool has_events() {
return false;
}
#endif
simdjson_inline void start() {
#if defined(__linux)
linux_events.start();
#elif __APPLE__ && __aarch64__
if(has_events()) { diff = apple_events.get_counters(); }
#endif
start_clock = steady_clock::now();
}
simdjson_inline event_count& end() {
time_point<steady_clock> end_clock = steady_clock::now();
#if defined(__linux)
linux_events.end(count.event_counts);
#elif __APPLE__ && __aarch64__
if(has_events()) {
performance_counters end = apple_events.get_counters();
diff = end - diff;
}
count.event_counts[0] = diff.cycles;
count.event_counts[1] = diff.instructions;
count.event_counts[2] = diff.missed_branches;
count.event_counts[3] = 0;
count.event_counts[4] = 0;
#endif
count.elapsed = end_clock - start_clock;
return count;
}
};
#endif
+2 -1
View File
@@ -1,7 +1,8 @@
#ifndef BENCHMARK_HELPERS_H
#define BENCHMARK_HELPERS_H
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <atomic>
event_collector collector;
+2 -13
View File
@@ -1,7 +1,8 @@
#pragma once
#include "simdjson.h"
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <iostream>
namespace json_benchmark {
@@ -58,11 +59,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) {
state.counters["instructions"] = events.instructions();
state.counters["cycles"] = events.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["branch_miss"] = events.branch_misses();
state.counters["cache_miss"] = events.cache_misses();
state.counters["cache_ref"] = events.cache_references();
#endif
state.counters["instructions_per_byte"] = events.instructions() / double(bench.bytes_per_iteration());
state.counters["instructions_per_cycle"] = events.instructions() / events.cycles();
state.counters["cycles_per_byte"] = events.cycles() / double(bench.bytes_per_iteration());
@@ -70,11 +67,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
state.counters["best_instructions"] = events.best.instructions();
state.counters["best_cycles"] = events.best.cycles();
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
state.counters["best_branch_miss"] = events.best.branch_misses();
state.counters["best_cache_miss"] = events.best.cache_misses();
state.counters["best_cache_ref"] = events.best.cache_references();
#endif
state.counters["best_instructions_per_byte"] = events.best.instructions() / double(bench.bytes_per_iteration());
state.counters["best_instructions_per_cycle"] = events.best.instructions() / events.best.cycles();
@@ -95,11 +88,7 @@ template<typename B, typename R> static void run_json_benchmark(benchmark::State
if (collector.has_events()) {
label << " instructions=" << setw(12) << uint64_t(events.best.instructions()) << setw(0);
label << " cycles=" << setw(12) << uint64_t(events.best.cycles()) << setw(0);
#if !SIMDJSON_SIMPLE_PERFORMANCE_COUNTERS
label << " branch_miss=" << setw(8) << uint64_t(events.best.branch_misses()) << setw(0);
label << " cache_miss=" << setw(8) << uint64_t(events.best.cache_misses()) << setw(0);
label << " cache_ref=" << setw(10) << uint64_t(events.best.cache_references()) << setw(0);
#endif
}
label << " items=" << setw(10) << bench.items_per_iteration() << setw(0);
@@ -1,6 +1,7 @@
#pragma once
#include "json_benchmark/string_runner.h"
#include <fstream>
#include <map>
#include <string>
-105
View File
@@ -1,105 +0,0 @@
#pragma once
#ifdef __linux__
#include <asm/unistd.h> // for __NR_perf_event_open
#include <linux/perf_event.h> // for perf event constants
#include <sys/ioctl.h> // for ioctl
#include <unistd.h> // for syscall
#include <cerrno> // for errno
#include <cstring> // for memset
#include <stdexcept>
#include <iostream>
#include <vector>
template <int TYPE = PERF_TYPE_HARDWARE> class LinuxEvents {
int fd;
bool working;
perf_event_attr attribs{};
size_t num_events{};
std::vector<uint64_t> temp_result_vec{};
std::vector<uint64_t> ids{};
public:
explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
memset(&attribs, 0, sizeof(attribs));
attribs.type = TYPE;
attribs.size = sizeof(attribs);
attribs.disabled = 1;
attribs.exclude_kernel = 1;
attribs.exclude_hv = 1;
attribs.sample_period = 0;
attribs.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
const int pid = 0; // the current process
const int cpu = -1; // all CPUs
const unsigned long flags = 0;
int group = -1; // no group
num_events = config_vec.size();
ids.resize(config_vec.size());
uint32_t i = 0;
for (auto config : config_vec) {
attribs.config = config;
int _fd = static_cast<int>(syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags));
if (_fd == -1) {
report_error("perf_event_open");
}
ioctl(_fd, PERF_EVENT_IOC_ID, &ids[i++]);
if (group == -1) {
group = _fd;
fd = _fd;
}
}
temp_result_vec.resize(num_events * 2 + 1);
}
~LinuxEvents() { if (fd != -1) { close(fd); } }
inline void start() {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_RESET)");
}
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
}
}
}
inline void end(std::vector<unsigned long long> &results) {
if (fd != -1) {
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
}
if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
report_error("read");
}
}
// our actual results are in slots 1,3,5, ... of this structure
for (uint32_t i = 1; i < temp_result_vec.size(); i += 2) {
results[i / 2] = temp_result_vec[i];
}
for (uint32_t i = 2; i < temp_result_vec.size(); i += 2) {
if(ids[i/2-1] != temp_result_vec[i]) {
report_error("event mismatch");
}
}
}
bool is_working() {
return working;
}
private:
void report_error(const std::string &) {
working = false;
}
};
#endif
+1 -1
View File
@@ -10,7 +10,7 @@ namespace partial_tweets {
// {
// "created_at": "Sun Aug 31 00:29:15 +0000 2014",
// "id": 505874924095815700,
// "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
// "text": "@aym0566x ...",
// "in_reply_to_status_id": null,
// "user": {
// "id": 1186275104,
@@ -1,6 +1,7 @@
#ifndef BENCHMARK_HELPER_HPP
#define BENCHMARK_HELPER_HPP
#include "event_counter.h"
#include <counters/event_counter.h>
using namespace counters;
#include <atomic>
inline event_collector &get_collector() {
-30
View File
@@ -240,36 +240,6 @@ else()
add_compile_definitions(SIMDJSON_UTF8VALIDATION=1)
endif()
option(
SIMDJSON_ABLATION_NO_BRANCH_HINTS
"Disable branch hints for ablation testing."
OFF
)
if(SIMDJSON_ABLATION_NO_BRANCH_HINTS)
add_compile_definitions(ABLATION_NO_BRANCH_HINTS=1)
message(STATUS "ABLATION_NO_BRANCH_HINTS enabled")
endif()
option(
SIMDJSON_ABLATION_NO_SIMD_ESCAPING
"Disable SIMD escaping for ablation testing."
OFF
)
if(SIMDJSON_ABLATION_NO_SIMD_ESCAPING)
add_compile_definitions(ABLATION_NO_SIMD_ESCAPING=1)
message(STATUS "ABLATION_NO_SIMD_ESCAPING enabled")
endif()
option(
SIMDJSON_ABLATION_NO_CONSTEVAL
"Disable consteval for ablation testing."
OFF
)
if(SIMDJSON_ABLATION_NO_CONSTEVAL)
add_compile_definitions(ABLATION_NO_CONSTEVAL=1)
message(STATUS "ABLATION_NO_CONSTEVAL enabled")
endif()
include(CheckSymbolExists)
check_symbol_exists(fork unistd.h HAVE_POSIX_FORK)
check_symbol_exists(wait sys/wait.h HAVE_POSIX_WAIT)
+9 -1
View File
@@ -12,7 +12,7 @@ cmake_dependent_option(SIMDJSON_GOOGLE_BENCHMARKS "compile the Google Benchmark
if(SIMDJSON_GOOGLE_BENCHMARKS)
CPMAddPackage(
NAME google_benchmarks
URL https://github.com/google/benchmark/archive/refs/tags/v1.9.4.zip
URL https://github.com/google/benchmark/archive/refs/tags/v1.9.5.zip
OPTIONS
"BENCHMARK_ENABLE_TESTING OFF"
"BENCHMARK_ENABLE_INSTALL OFF"
@@ -20,6 +20,14 @@ if(SIMDJSON_GOOGLE_BENCHMARKS)
)
endif()
CPMAddPackage(
NAME counters
URL https://github.com/lemire/counters/archive/refs/tags/v3.1.0.zip
OPTIONS
"COUNTERS_BUILD_TESTS OFF"
"COUNTERS_INSTALL OFF"
)
CPMAddPackage(
NAME simdjson-data
URL https://github.com/simdjson/simdjson-data/archive/351949906abde446f0314bf79606fb5d884f5be7.zip
+131 -63
View File
@@ -161,54 +161,77 @@ The basics: loading and parsing JSON documents
----------------------------------------------
The simdjson library allows you to navigate and validate JSON documents ([RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html)).
As required by the standard, your JSON document should be in a Unicode (UTF-8) string. The whole
string, from the beginning to the end, needs to be valid: we do not attempt to tolerate bad
inputs before or after a document.
Your JSON document should be a valid Unicode (UTF-8) string.
For efficiency reasons, simdjson requires a string with a few bytes (`simdjson::SIMDJSON_PADDING`)
at the end, these bytes may be read but their content does not affect the parsing. In practice,
it means that the JSON inputs should be stored in a memory region with `simdjson::SIMDJSON_PADDING`
extra bytes at the end. You do not have to set these bytes to specific values though you may
want to if you want to avoid runtime warnings with some sanitizers. We expect the user
of the library to load the data (from disk or from the network) into a padded buffer. To make
this easy, we provide the `padded_string::load` function which loads files from disk in a padded buffer.
[You can similarly fetch a file from a URL to a padded string](https://github.com/simdjson/curltostring) using our `simdjson::padded_string_builder`. Advanced users may want to read the section Free Padding in [our performance notes](performance.md).
The simdjson library offers a tree-like [API](https://en.wikipedia.org/wiki/API), which you can
access by creating a `ondemand::parser` and calling the `iterate()` method. The iterate method
quickly indexes the input string and may detect some errors. The following example illustrates
how to get started with an input JSON file (`"twitter.json"`):
To parse JSON, create a `ondemand::parser` and call its `iterate()` method on a padded input.
The simplest way to load a JSON file is with `padded_string::load`:
```cpp
ondemand::parser parser;
auto json = padded_string::load("twitter.json"); // load JSON file 'twitter.json'.
ondemand::document doc = parser.iterate(json); // position a pointer at the beginning of the JSON data
auto json = padded_string::load("twitter.json");
ondemand::document doc = parser.iterate(json);
```
(Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
filenames: `padded_string::load(L"twitter.json")`.)
For inline JSON strings, use the `_padded` suffix:
If you prefer not to create your own `ondemand::parser` instance, you can access
a thread-local version by calling `ondemand::parser.get_parser()`.
```cpp
ondemand::parser parser;
auto json = "[1,2,3]"_padded;
ondemand::document doc = parser.iterate(json);
```
If you are compiling with C++17 or better, you can use `simdjson::padded_input`
which accepts any string-like input and handles padding automatically:
```cpp
ondemand::parser parser;
std::string_view json = "[1,2,3]";
simdjson::padded_input input(json);
ondemand::document doc = parser.iterate(input);
// Also works with std::string, considering reserved capacity
std::string json_str = "[1,2,3]";
json_str.reserve(100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying
ondemand::document doc2 = parser.iterate(input2);
```
The simdjson library also accepts `std::string` instances directly---if the provided
reference is non-const, it will allocate padding as needed:
```cpp
ondemand::parser parser;
std::string json = "[1,2,3]";
ondemand::document doc = parser.iterate(json);
```
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
### Advanced input options
This section covers additional ways to provide JSON input to simdjson, including
options for fine-grained control over padding and memory.
**Thread-local parser.** If you prefer not to create your own `ondemand::parser` instance, you can access
a thread-local version by calling `ondemand::parser.get_parser()`:
```cpp
ondemand::document doc = ondemand::parser.get_parser().iterate(json);
```
However, you should be careful because a parser instance can only be used for one
document at a time, thus it is only applicable when you are only parsing one
A parser instance can only be used for one document at a time, so
the thread-local parser is only applicable when you parse one
document per thread at any one time.
You can also create a padded string---and call `iterate()`:
**`padded_input` details (C++17+).** The actual padding only occurs when the JSON string ends near the boundary of a memory page, which is
uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind
might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input`
on systems without a page size of at least 4096: virtually all systems qualify except for
some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, a `simdjson::padded_input` instance will not copy the data and will only act
as a view (it does not own the memory).
```cpp
ondemand::parser parser;
auto json = "[1,2,3]"_padded; // The _padded suffix creates a simdjson::padded_string instance
ondemand::document doc = parser.iterate(json); // parse a string
```
If you have a buffer of your own with enough padding already (SIMDJSON_PADDING extra bytes allocated), you can use `padded_string_view` to pass it in:
**User-managed buffers.** If you have a buffer of your own with enough padding already (`SIMDJSON_PADDING` extra bytes allocated), you can use `padded_string_view` to pass it in:
```cpp
ondemand::parser parser;
@@ -217,60 +240,99 @@ strcpy(json, "[1]");
ondemand::document doc = parser.iterate(json, strlen(json), sizeof(json));
```
The simdjson library will also accept `std::string` instances. If the provided
reference is non-const, it will allocate padding as needed.
You can copy your data directly on a `simdjson::padded_string` as follows:
**Copying into a `padded_string`.** You can copy your data directly into a `simdjson::padded_string`:
```cpp
const char * data = "my data"; // 7 bytes
simdjson::padded_string my_padded_data(data, 7); // copies to a padded buffer
```
Or as follows...
Or from a `std::string`:
```cpp
std::string data = "my data";
simdjson::padded_string my_padded_data(data); // copies to a padded buffer
```
You can then parse the JSON data from the `simdjson::padded_string` instance:
```cpp
ondemand::document doc = parser.iterate(my_padded_data);
```
Whenever you pass an `std::string` reference to `parser::iterate`,
the parser will access the bytes beyond the end of
**`std::string` and sanitizer warnings.** Whenever you pass an `std::string` reference to `parser::iterate`,
the parser may access bytes beyond the end of
the string but before the end of the allocated memory (`std::string::capacity()`).
If you are using a sanitizer that checks for reading uninitialized bytes or `std::string`'s
container-overflow checks, you may encounter sanitizer warnings.
You can safely ignore these warnings. Or you can call `simdjson::pad(std::string&)` to pad the
string with `SIMDJSON_PADDING` spaces: this function returns a `simdjson::padding_string_view` which can be be passed to the parser's iterator function:
Sanitizers that check for reading uninitialized bytes may produce warnings.
You can safely ignore these warnings, or call `simdjson::pad(std::string&)` to pad the
string explicitly:
```cpp
std::string json = "[1]";
ondemand::document doc = parser.iterate(simdjson::pad(json));
```
We recommend against creating many `std::string` or many `std::padding_string` instances in your application to store your JSON data.
We recommend against creating many `std::string` or many `std::padded_string` instances in your application to store your JSON data.
Consider reusing the same buffers and limiting memory allocations.
By default, the simdjson library throws exceptions (`simdjson_error`) on errors. We omit `try`-`catch` clauses from our illustrating examples: if you omit `try`-`catch` in your code, an uncaught exception will halt your program. It is also possible to use simdjson without generating exceptions, and you may even build the library without exception support at all. See [Error handling](#error-handling) for details.
**Memory-file mapping.** You can use `simdjson::padded_memory_map` to create a
`simdjson::padded_string_view` from a file on disk. On POSIX systems (Linux,
macOS, BSD, ...) it uses `mmap` for true zero-copy access and is always
available. On Windows it is an **opt-in** feature because it relies on the
`CreateFileMapping2` / `MapViewOfFile3` APIs (Windows 10, version 1803 or
later) which are exported from `onecore.lib` rather than the default
`kernel32.lib`. To enable it, you must satisfy **all** of the following:
Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before including simdjson, in every translation
unit that uses `padded_memory_map`.
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo)
The Windows implementation then uses `CreateFileMapping2` / `MapViewOfFile3`
for true zero-copy access whenever possible, with a transparent
buffered-read fallback for files that end too close to a page boundary.
*Windows-specific*: Windows users who need to read files with
The availability of the class can be tested with the preprocessor macro
`SIMDJSON_HAS_PADDED_MEMORY_MAP`.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view();
ondemand::document doc = parser.iterate(view);
```
**Windows-specific notes.** Windows users compiling with C++17 or better may use `wchar_t` strings to support non-ASCII
filenames: `padded_string::load(L"twitter.json")`. Windows users who need to read files with
non-ANSI characters in the name should set their code page to
UTF-8 (65001). This should be the default with Windows 11 and better.
Further, they may use the AreFileApisANSI function to determine whether
the filename is interpreted using the ANSI or the system default OEM
codepage, and they may call SetFileApisToOEM accordingly.
Some users may want to browse code along with the compiled assembly:
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/98Kx9Kqjn)
* [simdjson examples with errors without exceptions](https://godbolt.org/z/PKG7GdbPo)
**Summary of input types:**
| Input Type / Method | Padding Requirement | How Padding is Handled | Ownership / Copying | Notes / Warnings |
|----------------------------------------------|-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------|----------------------------------------------------------------------------------|
| `padded_string::load("file.json")` | Automatic (SIMDJSON_PADDING extra bytes) | Library allocates padded buffer and loads file into it | Owned by `padded_string` | Recommended for files; safest and simplest. |
| `"...json..."_padded` literal | Automatic (built-in padding) | Creates `padded_string` with padding | Owned by `padded_string` | Convenient for small hardcoded JSON. |
| `simdjson::padded_input` (C++17+) | Automatic when needed | Adds padding **only** if the string ends near a memory page boundary. For `std::string`, considers `capacity()` | Usually a non-owning view (no copy most times) | Safe on standard OS (page size ≥ 4096). May trigger sanitizer/valgrind warnings (harmless). Avoid on niche embedded systems. |
| User buffer with explicit padding | Must have at least `SIMDJSON_PADDING` extra allocated bytes after JSON content | Pass via `iterate(ptr, json_length, total_allocated_size)` or `padded_string_view` | User-owned (no copy) | Use `char buf[len + SIMDJSON_PADDING]`. Library reads (but never writes) into padding. |
| `std::string` (non-const) | Library checks `capacity()` | If insufficient, library may allocate a padded copy | May copy (depends on capacity) | Can trigger sanitizer warnings on uninitialized bytes. Use `simdjson::pad(json)` to avoid. |
| `simdjson::pad(std::string&)` | Adds padding if needed | Returns `padded_string_view` pointing to the (possibly resized) string | References original string | Recommended to silence sanitizers when using `std::string`. |
| `padded_string(data, length)` or `padded_string(std::string)` | Automatic (copies into padded buffer) | Explicit copy into owned padded buffer | Owned by `padded_string` | Safe when you want full ownership and padding guaranteed. |
| `padded_string_view` (manual) | User guarantees `SIMDJSON_PADDING` extra bytes after the viewed length | User provides pointer + length + capacity | Non-owning view | Low-level; requires careful buffer management. |
| Memory-mapped file (`padded_memory_map`) | Automatic via mapping / padded read | Creates view with sufficient padding | Non-owning (tied to map lifetime) | Always available on POSIX (zero-copy `mmap`). On Windows, opt-in via `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON` (requires Windows 10 1803+ and links `onecore.lib`) and `#include <windows.h>` before simdjson; uses `CreateFileMapping2` + `MapViewOfFile3`. |
Documents are iterators
-----------------------
@@ -374,6 +436,10 @@ builds to disable additional runtime testing and get the best performance. We
disable these checks on a best-effort basis but the C++ standard does not provide
a direct way to check for a release build.
Warnign: Mixing debug and release simdjson code is unsafe: you either build all your code
using simdjson in release mode or all of it in debug mode.
Using the parsed JSON
---------------------
@@ -417,7 +483,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
* **Extracting Values:** You can cast a JSON element to a native type:
`double(element)`. This works for `std::string_view`, double, uint64_t, int64_t, bool,
ondemand::object and ondemand::array. We also have explicit methods such as `get_string()`, `get_double()`,
`get_uint64()`, `get_int64()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method,
`get_uint64()`, `get_int64()`, `get_uint32()`, `get_int32()`, `get_bool()`, `get_object()` and `get_array()`. After a cast or an explicit method,
the number, string or boolean will be parsed, or the initial `{` or `[` will be verified for `ondemand::object` and `ondemand::array`. An exception may be thrown if
the cast is not possible: the error code is `simdjson::INCORRECT_TYPE` (see [Error handling](#error-handling)). Importantly, when getting an ondemand::object or ondemand::array instance, its content is
not validated: you are only guaranteed that the corresponding initial character (`{` or `[`) is present. Thus,
@@ -427,8 +493,8 @@ support for users who avoid exceptions. See [the simdjson error handling documen
pass `true` (`get_string(true)`) as a parameter to get replacement characters where errors
occur. If you somehow need to access non-UTF-8 strings in a lossless manner
(e.g., if you strings contain unpaired surrogates), you may use the `get_wobbly_string()` function to get a string in the [WTF-8 format](https://simonsapin.github.io/wtf-8).
When calling `get_uint64()` and `get_int64()`, if the number does not fit in a corresponding
64-bit integer type, it is also considered an error. When parsing numbers or other scalar values, the library checks
When calling `get_uint64()`, `get_int64()`, `get_uint32()` or `get_int32()`, if the number does not fit in the
corresponding integer type, it is also considered an error (`NUMBER_OUT_OF_RANGE`). When parsing numbers or other scalar values, the library checks
that the value is followed by an expected character, thus you *may* get a number parsing error when accessing the digits
as an integer in the following strings: `{"number":12332a`, `{"number":12332\0`, `{"number":12332` (the digits appear at the end). We always abide by the [RFC 8259](https://www.tbray.org/ongoing/When/201x/2017/12/14/rfc8259.html) JSON specification so that, for example, numbers prefixed by the `+` sign are in error.
@@ -1281,10 +1347,6 @@ You can also use the custom `Car` type as part of a template such as `std::vecto
simdjson::ondemand::parser parser;
simdjson::ondemand::document doc = parser.iterate(json);
std::vector<Car> cars(doc);
// visual studio users need an explicit call:
// std::vector<Car> cars = doc.get<std::vector<Car>>();
// because the compiler does not know whether to convert
// doc to an unsigned int or to a vector.
for(Car& c : cars) {
std::cout << c.year << std::endl;
}
@@ -1378,6 +1440,8 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
that are not made by Toyota.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
### 3. Using static reflection (C++26)
@@ -1454,6 +1518,10 @@ void f() {
}
```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
#### Special cases
However, there are instances where the construction cannot
+23
View File
@@ -32,6 +32,21 @@ your code with the `SIMDJSON_STATIC_REFLECTION` macro set:
The `simdjson::compile_time::parse_json` function parses a JSON document at **compile time** and returns a `constexpr` structure reflecting its content. We support the full range of JSON values, which are mapped to C++ types as in
the following table.
For convenience, you can also use the `""_json` user-defined literal operator, which is available in the `simdjson::literals` namespace:
```cpp
using namespace simdjson::literals;
constexpr auto cfg = R"(
{
"port": 8080,
"host": "localhost"
}
)"_json;
```
Alternatively, you can use the qualified name `simdjson::literals::operator""_json`.
| JSON type | C++ type |
|----------------|----------------------------------|
@@ -61,6 +76,8 @@ You can do so, at compile-time, as follows:
```cpp
using namespace simdjson::literals;
constexpr auto cfg = R"(
{
@@ -79,6 +96,8 @@ constexpr auto cfg = R"(
You can nest objects and arrays:
```cpp
using namespace simdjson::literals;
constexpr auto data = R"(
{
@@ -98,6 +117,8 @@ constexpr auto data = R"(
Top-level arrays are allowed:
```cpp
using namespace simdjson::literals;
constexpr auto arr = R"(
[1, 2, 3]
@@ -116,6 +137,8 @@ want to check that it conforms to your expectation. You can do so with concepts.
Let us consider this example:
```cpp
using namespace simdjson::literals;
constexpr auto config = R"(
[
+70 -1
View File
@@ -125,6 +125,42 @@ Further, they may use the AreFileApisANSI function to determine whether
the filename is interpreted using the ANSI or the system default OEM
codepage, and they may call SetFileApisToOEM accordingly.
**Advanced feature:**
You can use `simdjson::padded_memory_map` to create a `simdjson::padded_string_view`
from a file on disk without copying the file contents into your own buffer.
On POSIX systems (Linux, macOS, BSD, ...) it uses `mmap` for true zero-copy
access. On Windows it is available as an **opt-in** feature and requires:
1. Building simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or
defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1` and raising
`NTDDI_VERSION` to at least `NTDDI_WIN10_RS4` (Windows 10, version 1803)
and linking `onecore.lib` manually if you are consuming simdjson as a
pre-built library.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`.
When enabled on Windows, the implementation uses `CreateFileMapping2` and
`MapViewOfFile3` for true zero-copy mapping whenever the file does not end
within `SIMDJSON_PADDING` bytes of a page boundary; otherwise it falls back
to reading the file into a padded heap buffer. If those requirements are
not met, the class is not declared and the code below will fail to compile.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map(TWITTER_JSON);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
```
Using memory-file mapping requires some care. The file should not be modified while you are
accessing it.
Using the Parsed JSON
---------------------
@@ -152,6 +188,23 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to `1` when building simdjson, you can get that `-0` is mapped to `-0.0`
as in JavaScript. You can get the desired effect by building simdjson with cmake setting the
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to on: `cmake -B build -D SIMDJSON_MINUS_ZERO_AS_FLOAT=ON`.
* **Big Integer Support (opt-in):** By default, integers that exceed the 64-bit range cause parsing to fail with `BIGINT_ERROR`. You can opt in to big integer support so that these numbers are stored as raw digit strings on the tape instead:
```cpp
simdjson::dom::parser parser;
parser.number_as_string(true); // opt-in, default false
simdjson::dom::element doc;
auto error = parser.parse("[1, 123456789012345678901]"_padded).get(doc);
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
for (simdjson::dom::element elem : doc) {
if (elem.is_bigint()) {
std::string_view digits;
error = elem.get_bigint().get(digits);
if (error) { std::cerr << error << std::endl; return EXIT_FAILURE; }
std::cout << "big integer: " << digits << std::endl;
}
}
```
When enabled, big integers have type `element_type::BIGINT`. Calling `get_int64()`, `get_uint64()`, or `get_double()` on a big integer returns `INCORRECT_TYPE`. Normal numbers (int64, uint64, double) are unaffected.
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`.
* **Array Iteration:** To iterate through an array, use `for (auto value : array) { ... }`. If you
know the type of the value, you can cast it right there, too! `for (double value : array) { ... }`
@@ -164,7 +217,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
* **Array and Object size** Given an array or an object, you can get its size (number of elements or keys)
with the `size()` method.
* **Checking an Element Type:** You can check an element's type with `element.type()`. It
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::STRING`, `simdjson::dom::element_type::BOOL` or, `simdjson::dom::element_type::NULL_VALUE`.
returns an `element_type` with values such as `simdjson::dom::element_type::ARRAY`, `simdjson::dom::element_type::OBJECT`, `simdjson::dom::element_type::INT64`, `simdjson::dom::element_type::UINT64`,`simdjson::dom::element_type::DOUBLE`, `simdjson::dom::element_type::STRING`, `simdjson::dom::element_type::BOOL`, `simdjson::dom::element_type::NULL_VALUE` or, `simdjson::dom::element_type::BIGINT` (when big integer support is enabled).
* **Output to streams and strings:** Given a document or an element (or node) out of a JSON document, you can output a minified string version using the C++ stream idiom (`out << element`). You can also request the construction of a minified string version (`simdjson::minify(element)`) or a prettified string version (`simdjson::prettify(element)`). Numbers are serialized as 64-bit floating-point numbers (`double`).
### Examples
@@ -737,6 +790,9 @@ void basics_treewalk_1() {
}
```
Notice that we do not include `dom::element_type::BIGINT` in this example
as `dom::element_type::BIGINT` type is only generated if the parser was
set to support big integers (`parser.number_as_string(true)`).
Reusing the parser for maximum efficiency
@@ -849,6 +905,19 @@ simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len,
Setting the `realloc_if_needed` parameter `false` in this manner may lead to better performance since copies are avoided, but it requires that the user takes more responsibilities: the simdjson library cannot verify that the input buffer was padded with SIMDJSON_PADDING extra bytes.
If you are compiling your project with C++17 or better, you can use a `simdjson::padded_input`:
```cpp
simdjson::dom::parser parser;
std::string_view json = "[1,2,3]";
simdjson::padded_input input(json); // Automatically pads if needed
simdjson::dom::element element = parser.parse(input);
```
The actual padding only occurs if the JSON string ends near the boundary of a memory page, which is uncommon. Using a `simdjson::padded_input` is safe although sanitizers and tools like valgrind might report illegal reads (which are safe in our case because they remain in the mapped page). You should avoid `simdjson::padded_input` on systems without a page size of at least 4096: virtually all systems qualify except for some niche embedded systems running custom operating systems. Standard Linux, Windows, macOS, Android, iOS, etc., are all fine. Note that, most times, an `simdjson::padded_input` instance will not copy the data and will only act
as a view (it does not own the memory).
Performance Tips
---------------------
+3
View File
@@ -33,6 +33,9 @@ compiles *all* the implementations into the executable. On Intel, it will includ
(icelake, haswell, westmere and fallback), on 64-bit ARM it will include just one since running dispatching is unnecessary, and on PPC
it will include 2 (ppc64 and fallback).
On Loongson processors, LASX runtime dispatching is only enabled on GCC 15+, not on LLVM or older versions of GCC.
Thus unless you compile specifically for LASX or use GCC 15+, you will not benefit from LASX support.
If you know more about where you're going to run and want to save the space, you can disable any of
these implementations at compile time with `-DSIMDJSON_IMPLEMENTATION_X=0` (where X is ICELAKE, HASWELL,
WESTMERE, ARM64, PPC64, LSX, LASX and FALLBACK).
+361 -29
View File
@@ -22,10 +22,12 @@ Contents
- [Threads](#threads)
- [Support](#support)
- [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams)
- [C++20 features](#c20-features)
- [C++26 features (static reflection)](#c26-features-static-reflection)
Motivation
-----------
@@ -132,7 +134,7 @@ E.g., `[1,2]{"32":1}` is recognized as two documents.
Some official formats **(non-exhaustive list)**:
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/)
- [JSON lines (JSONL)](http://jsonlines.org/)
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson!
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
API
@@ -154,6 +156,86 @@ for (auto doc : docs) {
See [basics.md](basics.md#newline-delimited-json-ndjson-and-json-lines) for an overview of the API.
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most efficient
way to feed `iterate_many` is to use `simdjson::padded_memory_map`. It returns
a `padded_string_view` with the right amount of trailing padding, so you can
hand it straight to `iterate_many` without ever copying the file contents into
your own buffer.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::ondemand::parser parser;
simdjson::ondemand::document_stream stream;
auto error = parser.iterate_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.iterate(...)`:
```cpp
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
ondemand::document doc = parser.iterate(view); // parse the JSON
```
## Use cases
From [jsonlines.org](http://jsonlines.org/examples/):
@@ -264,39 +346,131 @@ Importantly, you should only call `truncated_bytes()` after iterating through al
Comma-separated documents
-----------
We also support comma-separated documents, but with some performance limitations. The `iterate_many` function takes in an option to allow parsing of comma separated documents (which defaults on false). In this mode, the entire buffer is processed in one batch. Therefore, the total size of the document should not exceed the maximal capacity of the parser (4 GB). This mode also effectively disallow multithreading. It is therefore mostly suitable for not "very large" inputs. In this mode, the batch_size parameter
is effectively ignored, as it is set to at least the document size.
Example:
To parse comma-separated documents like `{"a":1},{"b":2},{"c":3}`, use the `stream_format::comma_delimited` parameter:
```cpp
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
ondemand::parser parser;
ondemand::document_stream doc_stream;
// We pass '32' as the batch size, but it is a bogus parameter because, since
// we pass 'true' to the allow_comma parameter, the batch size will be set to at least
// the document size.
auto error = parser.iterate_many(json, 32, true).get(doc_stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : doc_stream) {
std::cout << doc.type() << std::endl;
}
```
This will print:
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
number
number
number
number
string
string
string
object
array
Whitespace around the commas is allowed:
```cpp
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
```
Nested commas inside objects and arrays are preserved:
```cpp
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
// Correctly parses as 2 documents, not 6
```
Mixed document types are supported:
```cpp
auto json = R"(1, 2, 3, 4, "a", "b", "c", {"hello": "world"}, [1, 2, 3])"_padded;
ondemand::parser parser;
ondemand::document_stream doc_stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(doc_stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : doc_stream) {
std::cout << doc.type() << std::endl;
}
// Prints: number number number number string string string object array
```
Extra top-level separators are tolerated for compatibility with the legacy
`allow_comma_separated` behavior. For example, leading commas, trailing commas,
and repeated commas are treated as empty separators rather than documents.
### Legacy `allow_comma_separated` parameter (deprecated)
The `allow_comma_separated` boolean parameter is deprecated. When set to `true`, it now internally maps to `stream_format::comma_delimited`.
The old single-batch limitation no longer applies - comma-delimited parsing now supports multi-batch processing and threading for optimal performance on large files.
JSON Text Sequences (RFC 7464)
------------------------------
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
Example input:
```
<RS>{"name":"doc1"}<LF>
<RS>{"name":"doc2"}<LF>
<RS>{"name":"doc3"}<LF>
```
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
simdjson::padded_string input(input_str);
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
```
The `stream_format` enum has the following values:
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
- `stream_format::comma_delimited`: Comma-separated JSON documents
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
JSON Array As A Document Stream
-------------------------------
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
```cpp
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
```cpp
// All of these work:
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
auto c = R"([])"_padded; // empty array → 0 docs
```
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `iterate_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
C++20 features
--------------------
@@ -404,3 +578,161 @@ Otherwise you may use this longer version for explicit handling of errors:
cars.push_back(c);
}
```
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
C++26 features (static reflection)
-----------------------------------
If you have a C++26 compatible compiler with [P2996](https://wg21.link/P2996)
static reflection support, you can compile the simdjson library with the
`SIMDJSON_STATIC_REFLECTION` macro set to `1`. When this is the case, simdjson
can deserialize a stream of JSON documents directly into your own structures
**without** writing any `tag_invoke` function. The library inspects the
non-static public members of your type at compile time and produces the
parsing code automatically.
```cpp
#define SIMDJSON_STATIC_REFLECTION 1
#include "simdjson.h"
```
Consider the same `Car` structure used in the C++20 example, but **without**
any `tag_invoke` glue:
```cpp
struct Car {
std::string make;
std::string model;
int year;
std::vector<double> tire_pressure;
};
```
With C++26 static reflection enabled, you can iterate a stream of cars and
push them into a `std::vector<Car>` directly:
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] }
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] }
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
This works for every `stream_format` value supported by `iterate_many`. The
following examples each parse the same three cars, but laid out using a
different streaming convention.
### Whitespace-delimited (default, NDJSON / JSON Lines)
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] }
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] }
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::whitespace_delimited).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
cars.push_back((Car)doc); // throws on error
}
```
### Comma-delimited documents
```cpp
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
### A single JSON array as a stream of documents
When the input is a single JSON array, you can stream its elements one at a
time without materializing the entire array as a `std::vector` upfront:
```cpp
auto json = R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
"tire_pressure": [ 40.1, 39.9 ] },
{ "make": "Kia", "model": "Soul", "year": 2012,
"tire_pressure": [ 30.1, 31.0 ] },
{ "make": "Toyota", "model": "Tercel", "year": 1999,
"tire_pressure": [ 29.8, 30.0 ] } ] )"_padded;
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
### JSON Text Sequences (RFC 7464)
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
auto append = [&](std::string_view doc) {
input_str += '\x1e'; input_str += doc; input_str += '\x0a';
};
append(R"({ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9 ] })");
append(R"({ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0 ] })");
append(R"({ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0 ] })");
simdjson::padded_string input(input_str);
ondemand::parser parser;
ondemand::document_stream stream;
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { /* handle error */ }
std::vector<Car> cars;
for (auto doc : stream) {
Car c;
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
cars.push_back(c);
}
```
In every case, the user-defined type (`Car` here) does not need a hand-written
`tag_invoke` overload: the library generates the deserialization code from the
type's public data members at compile time.
**Performance tip**: You will get better performance if you order the attributes (make, model)
in the order they appear in the JSON document.
+194 -1
View File
@@ -18,6 +18,7 @@ Contents
- [How it works](#how-it-works)
- [Support](#support)
- [API](#api)
- [Streaming directly from a memory-mapped file](#streaming-directly-from-a-memory-mapped-file)
- [Use cases](#use-cases)
- [Tracking your position](#tracking-your-position)
- [Incomplete streams](#incomplete-streams)
@@ -132,7 +133,7 @@ Whitespace Characters:
Some official formats **(non-exhaustive list)**:
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec)
- [JSON lines (JSONL)](http://jsonlines.org/)
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson!
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
API
@@ -217,6 +218,86 @@ got full document at 29
```
Streaming directly from a memory-mapped file
--------------------------------------------
When your input is a large NDJSON / JSON-lines file on disk, the most
efficient way to feed `parse_many` is to use `simdjson::padded_memory_map`.
It returns a `padded_string_view` with the right amount of trailing padding,
so you can pass it directly to `parse_many` without copying the file content
into your own buffer first.
`padded_memory_map` is available on POSIX systems (Linux, macOS, BSD, ...) by
default. On Windows it is an **opt-in** feature with the following
requirements:
1. Build simdjson with `-DSIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=ON`, or — if
you consume simdjson as a pre-built library — define
`SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`, raise `NTDDI_VERSION` to at
least `NTDDI_WIN10_RS4` (`0x0A000005`, Windows 10 version 1803), and
add `onecore.lib` to your link line yourself. The Windows
implementation uses the modern memory APIs `CreateFileMapping2` /
`MapViewOfFile3`, which are available starting with that version of
Windows and are exported by `onecore.lib`.
2. `#include <windows.h>` before `#include "simdjson.h"` in every
translation unit where you want to use `padded_memory_map`. simdjson
deliberately does not pull in `<windows.h>` itself, so the class is
only declared when the Win32 types are already visible.
If either requirement is not met on Windows, the `padded_memory_map` class is
not declared at all and any code that references it fails to compile with an
"unknown identifier" error. The availability of the class can be tested with
the macro `SIMDJSON_HAS_PADDED_MEMORY_MAP`.
On POSIX, `padded_memory_map` uses `mmap` to map the file directly into
memory with zero copies. On Windows (when enabled), it uses
`CreateFileMapping2` + `MapViewOfFile3` for true zero-copy mapping
whenever the file does not end within `SIMDJSON_PADDING` bytes of a page
boundary; for those rare cases, it transparently falls back to reading
the file into a heap-allocated padded buffer so that the returned view
always has `SIMDJSON_PADDING` accessible zero bytes after the file content.
```cpp
#ifdef _WIN32
#include <windows.h> // Must come BEFORE <simdjson.h> on Windows
#endif
#include "simdjson.h"
// ...
simdjson::padded_memory_map map("huge_stream.ndjson");
if (!map.is_valid()) { /* file missing, unreadable, too large, ... */ return; }
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(map.view()).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
// process each JSON document in the stream
std::cout << doc << std::endl;
}
```
Important lifetime rule: the `padded_string_view` returned by `map.view()` is
only valid while the `padded_memory_map` instance is alive, so keep `map`
alive for as long as you are iterating the stream.
The file must not be modified while the memory map is in use. If you need a
fully independent copy of the data, use `simdjson::padded_string::load(...)`
instead.
If you prefer single-document parsing on a memory-mapped file, the same
pattern applies to `parser.parse(...)`:
```cpp
simdjson::padded_memory_map map(myfilename);
if (!map.is_valid()) { /* handle error */ }
simdjson::padded_string_view view = map.view(); // view is usable while padded_memory_map is in scope
simdjson::dom::element doc = parser.parse(view); // parse the JSON
```
Incomplete streams
-----------
@@ -239,3 +320,115 @@ Consider the following example where a truncated document (`{"key":"intentionall
Importantly, you should only call `truncated_bytes()` after iterating through all of the documents since the stream cannot tell whether there are truncated documents at the very end when it may not have accessed that part of the data yet.
JSON Text Sequences (RFC 7464)
------------------------------
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
Example input:
```
<RS>{"name":"doc1"}<LF>
<RS>{"name":"doc2"}<LF>
<RS>{"name":"doc3"}<LF>
```
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
```cpp
// Build input with RS (0x1E) and LF (0x0A) delimiters
std::string input_str;
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
simdjson::padded_string input(input_str);
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(input, simdjson::dom::DEFAULT_BATCH_SIZE,
simdjson::stream_format::json_sequence).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
```
The `stream_format` enum has the following values:
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
- `stream_format::comma_delimited`: Comma-separated JSON documents
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
Comma-Separated Documents
-------------------------
Some systems produce JSON documents separated by commas, like `{"a":1},{"b":2},{"c":3}`. This is common when extracting elements from a JSON array or when APIs return comma-separated results.
To parse comma-separated documents, use the `stream_format::comma_delimited` parameter:
```cpp
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
Whitespace around the commas is allowed:
```cpp
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
```
Nested commas inside objects and arrays are preserved:
```cpp
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
// Correctly parses as 2 documents, not 6
```
Extra top-level separators are tolerated for compatibility with the legacy
On-Demand comma-separated mode. Leading commas, trailing commas, and repeated
commas are treated as empty separators rather than documents.
Unlike the legacy `allow_comma_separated` parameter, `stream_format::comma_delimited` supports multi-batch processing and threading for optimal performance on large files.
JSON Array As A Document Stream
-------------------------------
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
```cpp
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
simdjson::dom::parser parser;
simdjson::dom::document_stream stream;
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
simdjson::stream_format::comma_delimited_array).get(stream);
if (error) { std::cerr << error << std::endl; return; }
for (auto doc : stream) {
std::cout << doc << std::endl;
}
// Prints: {"a":1}
// {"b":2}
// {"c":3}
```
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
```cpp
// All of these work:
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
auto c = R"([])"_padded; // empty array → 0 docs
```
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `parse_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
+26 -4
View File
@@ -204,9 +204,31 @@ but can be significantly larger. E.g., Apple systems favour pages spanning 16 ki
In effect, it means that you can almost always read a few bytes beyond your current buffer---without
allocating extra memory. However, tools such as valgrind or memory sanitizers will flag such behavior as unsafe.
Nevertheless, you can still make sure of this capability in your code if you are an expert
programmer and you are willing to silence sanitizer warnings. The following code provides
a portable example.
You can still make sure of this capability in your code if you are an expert
programmer and you are willing to silence sanitizer warnings.
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
Example usage:
```cpp
std::string_view json = get_json_data();
simdjson::padded_input input(json); // Automatically pads if needed
auto result = parser.parse(input);
// Also works with std::string, considering capacity
std::string json_str = get_json_string();
json_str.reserve(json_str.size() + 100); // Reserve extra space
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
auto result2 = parser.parse(input2);
```
This simplifies padding management compared to manually checking and allocating.
More generally, the following code provides a portable example.
The conditional compilation checks for the `_MSC_VER` macro (indicating Microsoft Visual Studio)
@@ -250,7 +272,7 @@ long page_size() {
}
// Returns true if the buffer + len + simdjson::SIMDJSON_PADDING crosses the
// page boundary.
// page boundary. Assumes len != 0.
bool need_allocation(const char *buf, size_t len) {
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size())
+ simdjson::SIMDJSON_PADDING >= static_cast<uintptr_t>(page_size()));
+8
View File
@@ -93,6 +93,14 @@ Float values are represented as two 64-bit tape elements:
Performance consideration: We store numbers of the main tape because we believe that locality of reference is helpful for performance.
## Big Integers
When a JSON integer exceeds the 64-bit range (both signed and unsigned), it is classified as a big integer. By default, parsing returns `BIGINT_ERROR`. When `parser.number_as_string(true)` is set, the raw digits are stored on the string tape using the same format as strings (4-byte little-endian length prefix, followed by the UTF-8 digit bytes, followed by a null terminator).
A big integer is represented on the main tape as the 64-bit tape element `('Z' << 56) + x` where the payload `x` is the location on the string tape of the null-terminated digit string.
For example, the JSON value `99999999999999999999` would be stored as the string `"99999999999999999999"` on the string tape, with a `'Z'` tag on the main tape. Negative big integers include the leading minus sign.
## Root node
Each JSON document will have two special 64-bit tape elements representing a root node, one at the beginning and one at the end.
+3
View File
@@ -54,6 +54,9 @@ static void print_json(std::ostream& os, simdjson::dom::element element) noexcep
case simdjson::dom::element_type::NULL_VALUE:
os << "null" << endl;
break;
case simdjson::dom::element_type::BIGINT:
os << element.get_bigint().value_unsafe() << endl;
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+19
View File
@@ -21,6 +21,10 @@ SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS
/** The maximum document size supported by simdjson. */
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
/** The maximum depth of nested objects and arrays supported by simdjson.
A depth of SIMDJSON_MAXSIZE_BYTES/2 is not reasonable and would be
adversarial, but it serves as an upper bound for validation purposes. */
constexpr size_t SIMDJSON_MAX_DEPTH = SIMDJSON_MAXSIZE_BYTES/2;
/**
* The amount of padding needed in a buffer to parse JSON.
@@ -46,6 +50,21 @@ struct padded_string;
class padded_string_view;
enum class stage1_mode;
/**
* Stream format for parse_many/iterate_many.
*/
enum class stream_format {
whitespace_delimited, ///< Whitespace-delimited JSON documents (default, includes NDJSON/JSONL)
json_sequence, ///< RFC 7464 JSON text sequences (RS-delimited)
comma_delimited, ///< Comma-separated JSON documents (e.g., `{...},{...},{...}`)
comma_delimited_array ///< A single JSON array whose elements are iterated as
///< comma-separated documents (e.g., `[{...},{...},{...}]`).
///< The parser strips the outer `[` / `]` plus any
///< surrounding JSON whitespace (space, tab, LF, CR)
///< and then behaves like `comma_delimited` over the
///< remaining bytes.
};
namespace internal {
template<typename T>
+35 -34
View File
@@ -266,6 +266,12 @@ consteval std::pair<double, size_t> parse_double(const char *src,
}
} // namespace number_parsing
consteval auto make_data_member_options(auto&& name_str) {
std::meta::data_member_options options{};
options.name = std::forward<decltype(name_str)>(name_str);
return options;
}
// JSON string may contain embedded nulls, and C++26 reflection does not yet
// support std::string_view as a data member type. As a workaround, we define
// a custom type that holds a const char* and a size.
@@ -320,7 +326,8 @@ using class_type = type_builder<meta_info...>::constructed_type;
/**
* @brief Variable template for constructing instances with values
*/
template <typename T, auto... Vs> constexpr auto construct_from = T{Vs...};
template <typename T, auto... Vs> constexpr T construct_from = T{Vs...};
// in JSON, there are only a few whitespace characters that are allowed
// outside of objects, arrays, strings, and numbers.
@@ -410,7 +417,7 @@ parse_number(std::string_view json,
std::from_chars(json.data(), json.data() + json.size(), int_value);
if (res.ec == std::errc()) {
out = int_value;
if ((res.ptr - json.data()) != scope) {
if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
simdjson_consteval_error(
"Internal error: cannot agree on the character range of the float");
}
@@ -424,7 +431,7 @@ parse_number(std::string_view json,
std::from_chars(json.data(), json.data() + json.size(), uint_value);
if (res.ec == std::errc()) {
out = uint_value;
if ((res.ptr - json.data()) != scope) {
if (static_cast<std::size_t>(res.ptr - json.data()) != scope) {
simdjson_consteval_error(
"Internal error: cannot agree on the character range of the float");
}
@@ -533,7 +540,7 @@ parse_string(std::string_view json) {
// present, we have an error (isolated high surrogate), which we
// tolerate by substituting the substitution_code_point.
if (end - cursor < 6 || *cursor != '\\' ||
*(cursor + 1) != 'u' > 0xFFFF) {
*(cursor + 1) != 'u') {
code_point = substitution_code_point;
} else { // we have \u following the high surrogate
cursor += 2; // skip \u
@@ -887,10 +894,19 @@ parse_json_array_impl(const std::string_view json) {
std::size_t count = values.size() - 1;
// We assume all elements have the same type as the first element.
// However, if the array is heterogeneous, we should use std::variant.
auto elem_type = std::meta::type_of(values[1]);
// String literals reflected via reflect_constant_string have type const
// char[N], but when passed as template auto parameters they decay to
// const char*. Use const char* as the element type so that
// construct_from can aggregate-initialize the array.
if (std::meta::is_array_type(elem_type) &&
std::meta::remove_all_extents(elem_type) == ^^const char) {
elem_type = ^^const char *;
}
auto array_type = std::meta::substitute(
^^std::array,
{
std::meta::type_of(values[1]), std::meta::reflect_constant(count)});
elem_type, std::meta::reflect_constant(count)});
// Create array instance with values
values[0] = array_type;
@@ -957,8 +973,7 @@ parse_json_object_impl(std::string_view json) {
simdjson_consteval_error("Expected '}'");
}
cursor += object_size;
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed),
{.name = field_name});
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(parsed);
@@ -967,8 +982,7 @@ parse_json_object_impl(std::string_view json) {
case '[': {
std::string_view value(cursor, end);
auto [parsed, array_size] = parse_json_array_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed),
{.name = field_name});
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(parsed);
if (*(cursor + array_size - 1) != ']') {
@@ -989,8 +1003,7 @@ parse_json_object_impl(std::string_view json) {
}
}
auto dms =
std::meta::data_member_spec(^^const char *, {
.name = field_name});
std::meta::data_member_spec(^^const char *, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant_string(value));
break;
@@ -1001,8 +1014,7 @@ parse_json_object_impl(std::string_view json) {
}
cursor += 4;
auto dms = std::meta::data_member_spec(^^bool, {
.name = field_name});
auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(true));
break;
@@ -1013,8 +1025,7 @@ parse_json_object_impl(std::string_view json) {
}
cursor += 5;
auto dms = std::meta::data_member_spec(^^bool, {
.name = field_name});
auto dms = std::meta::data_member_spec(^^bool, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(false));
break;
@@ -1025,9 +1036,7 @@ parse_json_object_impl(std::string_view json) {
}
cursor += 4;
auto dms = std::meta::data_member_spec(^^std::nullptr_t,
{
.name = field_name});
auto dms = std::meta::data_member_spec(^^std::nullptr_t, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(nullptr));
break;
@@ -1051,22 +1060,19 @@ parse_json_object_impl(std::string_view json) {
if (std::holds_alternative<int64_t>(out)) {
int64_t int_value = std::get<int64_t>(out);
auto dms =
std::meta::data_member_spec(^^int64_t, {
.name = field_name});
std::meta::data_member_spec(^^int64_t, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(int_value));
} else if (std::holds_alternative<uint64_t>(out)) {
uint64_t uint_value = std::get<uint64_t>(out);
auto dms =
std::meta::data_member_spec(^^uint64_t, {
.name = field_name});
std::meta::data_member_spec(^^uint64_t, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(uint_value));
} else {
double float_value = std::get<double>(out);
auto dms =
std::meta::data_member_spec(^^double, {
.name = field_name});
std::meta::data_member_spec(^^double, make_data_member_options(field_name));
members.push_back(std::meta::reflect_constant(dms));
values.push_back(std::meta::reflect_constant(float_value));
}
@@ -1111,16 +1117,11 @@ template <constevalutil::fixed_string json_str> consteval auto parse_json() {
"Only JSON objects and arrays are supported at the top level, this "
"limitation will be lifted in the future.");*/
constexpr auto result = json.front() == '['
? parse_json_array_impl(json)
: parse_json_object_impl(json);
return [: result.first :];
/*
if(json.front() == '[') {
return [:parse_json_array_impl(json).first:];
} else if(json.front() == '{') {
// return [:parse_json_object_impl(json).first:];
}*/
if constexpr (json.front() == '[') {
return [: parse_json_array_impl(json).first :];
} else {
return [: parse_json_object_impl(json).first :];
}
}
} // namespace compile_time
+4 -1
View File
@@ -63,13 +63,16 @@ namespace compile_time {
template <constevalutil::fixed_string json_str> consteval auto parse_json();
} // namespace compile_time
} // namespace simdjson
inline namespace literals {
template <simdjson::constevalutil::fixed_string str>
consteval auto operator ""_json() {
return simdjson::compile_time::parse_json<str>();
}
} // namespace literals
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
-1
View File
@@ -119,5 +119,4 @@
#define SIMDJSON_CONSTEVAL 0
#endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
#endif // !defined(SIMDJSON_CONSTEVAL)
#endif // SIMDJSON_COMPILER_CHECK_H
+8
View File
@@ -59,6 +59,14 @@ concept appendable_containers =
details::supports_add<T> || details::supports_append<T> ||
details::supports_insert<T>) && !string_view_keyed_map<T>;
/// Check if T is a key_selector type for efficient JSON field lookup
template <typename T>
concept key_selector_type = requires(T selector) {
{ selector.size() } -> std::same_as<std::size_t>;
{ selector.index_of(std::string_view{}) } -> std::same_as<std::size_t>;
{ selector.get_key(std::size_t{}) } -> std::same_as<std::string_view>;
};
/// Insert into the container however possible
template <appendable_containers T, typename... Args>
constexpr decltype(auto) emplace_one(T &vec, Args &&...args) {
+5
View File
@@ -58,6 +58,11 @@ struct fixed_string {
data[i] = str[i];
}
}
constexpr fixed_string(const unsigned char (&str)[N]) {
for (std::size_t i = 0; i < N; ++i) {
data[i] = static_cast<char>(str[i]);
}
}
char data[N];
constexpr std::string_view view() const { return {data, N - 1}; }
constexpr size_t size() const { return N ; }
+12
View File
@@ -33,6 +33,9 @@ inline error_code document::allocate(size_t capacity) noexcept {
allocated_capacity = 0;
return SUCCESS;
}
if (capacity > SIMDJSON_MAXSIZE_BYTES) {
return CAPACITY;
}
// a pathological input like "[[[[..." would generate capacity tape elements, so
// need a capacity of at least capacity + 1, but it is also possible to do
@@ -141,6 +144,15 @@ inline bool document::dump_raw_tape(std::ostream &os) const noexcept {
case 'r': // we start and end with the root node
// should we be hitting the root node?
return false;
case 'Z': // we have a big integer
os << "bigint ";
std::memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t));
os << std::string_view(
reinterpret_cast<const char *>(string_buf.get() + payload + sizeof(uint32_t)),
string_length
);
os << '\n';
break;
default:
return false;
}
+39 -4
View File
@@ -89,12 +89,14 @@ simdjson_inline document_stream::document_stream(
dom::parser &_parser,
const uint8_t *_buf,
size_t _len,
size_t _batch_size
size_t _batch_size,
stream_format _format
) noexcept
: parser{&_parser},
buf{_buf},
len{_len},
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
format{_format},
error{SUCCESS}
#ifdef SIMDJSON_THREADS_ENABLED
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
@@ -112,6 +114,7 @@ simdjson_inline document_stream::document_stream() noexcept
buf{nullptr},
len{0},
batch_size{0},
format{stream_format::whitespace_delimited},
error{UNINITIALIZED}
#ifdef SIMDJSON_THREADS_ENABLED
, use_thread(false)
@@ -224,7 +227,14 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
} else {
size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index];
size_t svlen = next_doc_index - current_index();
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464 json_sequence
// mode the scanner classifies RS as a scalar character, so an RS-prefixed
// scalar document (number/true/false/null/string) has no closing structural
// index and the slice runs all the way up to the next document's RS. RS
// cannot legally appear in a JSON value at the source level (control
// characters in strings must be escaped as \u001E), so stripping it is
// safe in every stream_format.
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E)) {
svlen--;
}
return std::string_view(start, svlen);
@@ -274,10 +284,35 @@ inline size_t document_stream::next_batch_start() const noexcept {
inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
size_t remaining = len - _batch_start;
stage1_mode mode;
if (remaining <= batch_size) {
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
// Final batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_final;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_final;
break;
default:
mode = stage1_mode::streaming_final;
break;
}
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
} else {
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
// Partial batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_partial;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_partial;
break;
default:
mode = stage1_mode::streaming_partial;
break;
}
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
}
}
+5 -1
View File
@@ -206,12 +206,14 @@ private:
* @param buf is the raw byte buffer we need to process
* @param len is the length of the raw byte buffer in bytes
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
* @param format is the stream format
*/
simdjson_inline document_stream(
dom::parser &parser,
const uint8_t *buf,
size_t len,
size_t batch_size
size_t batch_size,
stream_format format = stream_format::whitespace_delimited
) noexcept;
/**
@@ -261,6 +263,8 @@ private:
const uint8_t *buf;
size_t len;
size_t batch_size;
/** The stream format. */
stream_format format;
/** The error (or lack thereof) from the current document. */
error_code error;
size_t batch_start{0};
+22
View File
@@ -81,6 +81,10 @@ simdjson_inline simdjson_result<bool> simdjson_result<dom::element>::get_bool()
if (error()) { return error(); }
return first.get_bool();
}
simdjson_inline simdjson_result<std::string_view> simdjson_result<dom::element>::get_bigint() const noexcept {
if (error()) { return error(); }
return first.get_bigint();
}
simdjson_inline bool simdjson_result<dom::element>::is_array() const noexcept {
return !error() && first.is_array();
@@ -110,6 +114,9 @@ simdjson_inline bool simdjson_result<dom::element>::is_bool() const noexcept {
simdjson_inline bool simdjson_result<dom::element>::is_null() const noexcept {
return !error() && first.is_null();
}
simdjson_inline bool simdjson_result<dom::element>::is_bigint() const noexcept {
return !error() && first.is_bigint();
}
simdjson_inline simdjson_result<dom::element> simdjson_result<dom::element>::operator[](std::string_view key) const noexcept {
if (error()) { return error(); }
@@ -218,6 +225,15 @@ inline simdjson_result<bool> element::get_bool() const noexcept {
}
return INCORRECT_TYPE;
}
inline simdjson_result<std::string_view> element::get_bigint() const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable());
switch (tape.tape_ref_type()) {
case internal::tape_type::BIGINT:
return tape.get_string_view();
default:
return INCORRECT_TYPE;
}
}
inline simdjson_result<const char *> element::get_c_str() const noexcept {
SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914
switch (tape.tape_ref_type()) {
@@ -360,6 +376,10 @@ inline bool element::is_null() const noexcept {
return tape.is_null_on_tape();
}
inline bool element::is_bigint() const noexcept {
return tape.tape_ref_type() == internal::tape_type::BIGINT;
}
#if SIMDJSON_EXCEPTIONS
inline element::operator bool() const noexcept(false) { return get<bool>(); }
@@ -492,6 +512,8 @@ inline std::ostream& operator<<(std::ostream& out, element_type type) {
return out << "bool";
case element_type::NULL_VALUE:
return out << "null";
case element_type::BIGINT:
return out << "bigint";
default:
return out << "unexpected content!!!"; // abort() usage is forbidden in the library
}
+19 -1
View File
@@ -21,7 +21,10 @@ enum class element_type {
DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double.
STRING = '"', ///< std::string_view
BOOL = 't', ///< bool
NULL_VALUE = 'n' ///< null
NULL_VALUE = 'n', ///< null
/// The BIGINT type is for integers that do not fit in 64 bits. It is only present
// if you set parser.number_as_string(true).
BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string
};
/**
@@ -120,6 +123,14 @@ public:
*/
inline simdjson_result<bool> get_bool() const noexcept;
/**
* Read this element as a big integer (raw digit string).
*
* @returns A string_view of the raw digits, or:
* INCORRECT_TYPE if the JSON element is not a big integer.
*/
inline simdjson_result<std::string_view> get_bigint() const noexcept;
/**
* Whether this element is a json array.
*
@@ -175,6 +186,11 @@ public:
*/
inline bool is_null() const noexcept;
/**
* Whether this element is a big integer (number exceeding 64-bit range).
*/
inline bool is_bigint() const noexcept;
/**
* Tell whether the value can be cast to provided type (T).
*
@@ -533,6 +549,7 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64() const noexcept;
simdjson_inline simdjson_result<double> get_double() const noexcept;
simdjson_inline simdjson_result<bool> get_bool() const noexcept;
simdjson_inline simdjson_result<std::string_view> get_bigint() const noexcept;
simdjson_inline bool is_array() const noexcept;
simdjson_inline bool is_object() const noexcept;
@@ -543,6 +560,7 @@ public:
simdjson_inline bool is_number() const noexcept;
simdjson_inline bool is_bool() const noexcept;
simdjson_inline bool is_null() const noexcept;
simdjson_inline bool is_bigint() const noexcept;
simdjson_inline simdjson_result<dom::element> operator[](std::string_view key) const noexcept;
simdjson_inline simdjson_result<dom::element> operator[](const char *key) const noexcept;
+46 -7
View File
@@ -36,10 +36,11 @@ inline bool parser::dump_raw_tape(std::ostream &os) const noexcept {
}
inline simdjson_result<size_t> parser::read_file(std::string_view path) noexcept {
const std::string path_copy(path);
// Open the file
SIMDJSON_PUSH_DISABLE_WARNINGS
SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe
std::FILE *fp = std::fopen(path.data(), "rb");
std::FILE *fp = std::fopen(path_copy.c_str(), "rb");
SIMDJSON_POP_DISABLE_WARNINGS
if (fp == nullptr) {
@@ -132,6 +133,7 @@ inline simdjson_result<element> parser::parse_into_document(document& provided_d
buf += 3;
len -= 3;
}
implementation->_number_as_string = _number_as_string;
_error = implementation->parse(buf, len, provided_doc);
if (_error) { return _error; }
@@ -168,12 +170,7 @@ simdjson_inline simdjson_result<element> parser::parse(const padded_string_view
}
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
return document_stream(*this, buf, len, batch_size);
return parse_many(buf, len, batch_size, stream_format::whitespace_delimited);
}
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept {
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
@@ -184,6 +181,48 @@ inline simdjson_result<document_stream> parser::parse_many(const std::string &s,
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size) noexcept {
return parse_many(s.data(), s.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size) noexcept {
return parse_many(v.data(), v.length(), batch_size);
}
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
if (format == stream_format::comma_delimited_array) {
// Strip leading JSON whitespace.
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
buf++; len--;
}
// Expect the opening '['.
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
buf++; len--;
// Strip trailing JSON whitespace.
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
len--;
}
// Expect the closing ']'.
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
len--;
// Fall through to comma_delimited over the array contents.
format = stream_format::comma_delimited;
}
return document_stream(*this, buf, len, batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
return parse_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept {
return parse_many(v.data(), v.length(), batch_size, format);
}
simdjson_inline size_t parser::capacity() const noexcept {
return implementation ? implementation->capacity() : 0;
+39
View File
@@ -490,10 +490,39 @@ public:
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
inline simdjson_result<document_stream> parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
*
* Because padded_string_view guarantees SIMDJSON_PADDING trailing bytes, this
* overload is safe to use with buffers that the caller owns elsewhere (for
* example, a padded_memory_map), with no extra copy. Without this overload,
* passing a padded_string_view would silently bind to the padded_string
* overload via an implicit conversion, allocating and copying the input, and
* because that temporary is destroyed at the end of the full-expression
* leaving the returned document_stream pointing at freed memory. */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept;
/** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
/**
* Parse a stream of JSON documents with explicit format specification.
*
* @param buf The concatenated JSON documents.
* @param len The length of the buffer.
* @param batch_size The batch size to use.
* @param format The stream format.
* @return A stream of documents, or an error.
*/
inline simdjson_result<document_stream> parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept;
/**
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
* and `max_depth` depth.
@@ -610,6 +639,13 @@ public:
inline bool dump_raw_tape(std::ostream &os) const noexcept;
/**
* When enabled, big integers (exceeding uint64 range) are stored as strings
* in the tape instead of returning BIGINT_ERROR. Default: false.
*/
inline void number_as_string(bool enabled) noexcept { _number_as_string = enabled; }
inline bool number_as_string() const noexcept { return _number_as_string; }
private:
/**
* The maximum document length this parser will automatically support.
@@ -618,6 +654,9 @@ private:
*/
size_t _max_capacity;
/** Whether to store big integers as strings instead of returning BIGINT_ERROR */
bool _number_as_string{false};
/**
* The loaded buffer (reused each time load() is called)
*/
+6
View File
@@ -460,6 +460,12 @@ inline void string_builder<serializer>::append(simdjson::dom::element value) {
case tape_type::STRING:
format.string(iter.get_string_view());
break;
case tape_type::BIGINT: {
// Big integer stored as string — output raw digits (no quotes)
auto sv = iter.get_string_view();
format.chars(sv.data(), sv.data() + sv.size());
break;
}
case tape_type::INT64:
format.number(iter.next_tape_value<int64_t>());
iter.json_index++; // numbers take up 2 spots, so we need to increment
@@ -41,6 +41,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) {
#endif// _MSC_VER
}
simdjson_inline int trailing_zeroes(uint64_t input_num) {
#ifdef _MSC_VER
unsigned long trailing_zero = 0;
// Search the mask data from least significant bit (LSB)
// to most significant bit (MSB) for a set bit (1).
if (_BitScanForward64(&trailing_zero, input_num))
return (int)trailing_zero;
else return 64;
#else
return __builtin_ctzll(input_num);
#endif// _MSC_VER
}
} // unnamed namespace
} // namespace fallback
} // namespace simdjson
@@ -22,7 +22,7 @@ namespace SIMDJSON_IMPLEMENTATION {
namespace builder {
template <class T>
requires(concepts::container_but_not_string<T> && !require_custom_serialization<T>)
requires(concepts::container_but_not_string<T> && ! concepts::optional_type<T> && !require_custom_serialization<T>)
constexpr void atom(string_builder &b, const T &t) {
auto it = t.begin();
auto end = t.end();
@@ -94,12 +94,8 @@ constexpr void atom(string_builder &b, const T &t) {
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if (i != 0)
b.append(',');
#if ABLATION_NO_CONSTEVAL
b.escape_and_append_with_quotes(std::meta::identifier_of(dm));
#else
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
b.append_raw(key);
#endif
b.append(':');
atom(b, t.[:dm:]);
i++;
@@ -134,15 +130,11 @@ template <typename T>
requires(std::is_enum_v<T> && !require_custom_serialization<T>)
void atom(string_builder &b, const T &e) {
#if SIMDJSON_STATIC_REFLECTION
constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
template for (constexpr auto enum_val : enumerators) {
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
if (e == [:enum_val:]) {
#if ABLATION_NO_CONSTEVAL
b.escape_and_append_with_quotes(std::meta::identifier_of(enum_val));
#else
constexpr auto enum_str = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(enum_val)));
b.append_raw(enum_str);
#endif
return;
}
};
@@ -237,12 +229,8 @@ void append(string_builder &b, const Z &z) {
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^Z, std::meta::access_context::unchecked()))) {
if (i != 0)
b.append(',');
#if ABLATION_NO_CONSTEVAL
b.escape_and_append_with_quotes(std::meta::identifier_of(dm));
#else
constexpr auto key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)));
b.append_raw(key);
#endif
b.append(':');
atom(b, z.[:dm:]);
i++;
@@ -306,35 +294,24 @@ string_builder& operator<<(string_builder& b, const Z& z) {
template<constevalutil::fixed_string... FieldNames, typename T>
requires(std::is_class_v<T> && (sizeof...(FieldNames) > 0))
void extract_from(string_builder &b, const T &obj) {
// Helper to check if a field name matches any of the requested fields
auto should_extract = [](std::string_view field_name) constexpr -> bool {
return ((FieldNames.view() == field_name) || ...);
};
b.append('{');
bool first = true;
// Iterate through all members of T using reflection
template for (constexpr auto mem : std::define_static_array(
std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()));
template for (constexpr auto mem : members) {
if constexpr (std::meta::is_public(mem)) {
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
static constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
// Only serialize this field if it's in our list of requested fields
if constexpr (should_extract(key)) {
if constexpr (((FieldNames.view() == key) || ...)) {
if (!first) {
b.append(',');
}
first = false;
// Serialize the key
#if ABLATION_NO_CONSTEVAL
b.escape_and_append_with_quotes(std::meta::identifier_of(mem));
#else
constexpr auto quoted_key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)));
static constexpr auto quoted_key = std::define_static_string(constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(mem)));
b.append_raw(quoted_key);
#endif
b.append(':');
// Serialize the value
@@ -27,6 +27,22 @@
#define SIMDJSON_EXPERIMENTAL_HAS_NEON 1
#endif
#endif
#if defined(__loongarch_sx)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX
#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1
#endif
#endif
#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \
defined(__riscv_vector)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV
#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1
#endif
#endif
#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__)
#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64
#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1
#endif
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
#include <arm_neon.h>
#ifdef _MSC_VER
@@ -39,6 +55,22 @@
#include <intrin.h>
#endif
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_LSX
#include <lsxintrin.h>
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_RVV
#include <riscv_vector.h>
#endif
#if SIMDJSON_EXPERIMENTAL_HAS_PPC64
#include <altivec.h>
#ifdef bool
#undef bool
#endif
#ifdef vector
#undef vector
#endif
#endif
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
@@ -139,6 +171,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) {
}
return _mm_movemask_epi8(running) != 0;
}
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
simdjson_inline bool fast_needs_escaping(std::string_view view) {
if (view.size() < 16) {
return simple_needs_escaping(view);
}
size_t i = 0;
__vector unsigned char running = vec_splats((unsigned char)0);
__vector unsigned char v34 = vec_splats((unsigned char)34);
__vector unsigned char v92 = vec_splats((unsigned char)92);
__vector unsigned char v32 = vec_splats((unsigned char)32);
for (; i + 15 < view.size(); i += 16) {
__vector unsigned char word =
vec_vsx_ld(0, reinterpret_cast<const unsigned char *>(view.data() + i));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92));
running = vec_or(running,
(__vector unsigned char)vec_cmplt(word, v32));
}
if (i < view.size()) {
__vector unsigned char word = vec_vsx_ld(
0, reinterpret_cast<const unsigned char *>(view.data() + view.length() - 16));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34));
running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92));
running = vec_or(running,
(__vector unsigned char)vec_cmplt(word, v32));
}
return !vec_all_eq(running, vec_splats((unsigned char)0));
}
#else
simdjson_inline bool fast_needs_escaping(std::string_view view) {
return simple_needs_escaping(view);
@@ -182,28 +243,12 @@ find_next_json_quotable_character(const std::string_view view,
needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92));
needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32));
if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) {
// Found quotable character - extract exact byte position using ctz
uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape);
uint64_t lo = vgetq_lane_u64(as64, 0);
uint64_t hi = vgetq_lane_u64(as64, 1);
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4);
const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
if(mask != 0) {
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
#ifdef _MSC_VER
unsigned long trailing_zero = 0;
if (lo != 0) {
_BitScanForward64(&trailing_zero, lo);
return offset + trailing_zero / 8;
} else {
_BitScanForward64(&trailing_zero, hi);
return offset + 8 + trailing_zero / 8;
}
#else
if (lo != 0) {
return offset + __builtin_ctzll(lo) / 8;
} else {
return offset + 8 + __builtin_ctzll(hi) / 8;
}
#endif
auto trailing_zero = trailing_zeroes(mask);
return offset + (trailing_zero >> 2);
}
ptr += 16;
remaining -= 16;
@@ -241,13 +286,128 @@ find_next_json_quotable_character(const std::string_view view,
if (mask != 0) {
// Found quotable character - use trailing zero count to find position
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
#ifdef _MSC_VER
unsigned long trailing_zero = 0;
_BitScanForward(&trailing_zero, mask);
return offset + trailing_zero;
return offset + trailing_zeroes(mask);
}
ptr += 16;
remaining -= 16;
}
// Scalar fallback for remaining bytes
size_t current = len - remaining;
return find_next_json_quotable_character_scalar(view, current);
}
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
//SIMD constants for characters requiring escape
__m128i v34 = __lsx_vreplgr2vr_b(34); // '"'
__m128i v92 = __lsx_vreplgr2vr_b(92); // '\\'
__m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold
while (remaining >= 16){
__m128i word = __lsx_vld(ptr, 0);
//Check for the quotable characters: '"', '\\', or control char (<32)
__m128i needs_escape = __lsx_vseq_b(word, v34);
needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92));
needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32));
if (!__lsx_bz_v(needs_escape)){
//Found quotable character - extract exact byte position
uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0);
uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1);
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
if ( lo != 0) {
return offset + trailing_zeroes(lo) / 8;
} else {
return offset + 8 + trailing_zeroes(hi) / 8;
}
}
ptr += 16;
remaining -= 16;
}
size_t current = len - remaining;
return find_next_json_quotable_character_scalar(view, current);
}
#elif SIMDJSON_EXPERIMENTAL_HAS_RVV
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
while (remaining > 0) {
size_t vl = __riscv_vsetvl_e8m1(remaining);
vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl);
// Check for quotable characters: '"', '\\', or control chars (< 32)
vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl);
needs_escape = __riscv_vmor(needs_escape,
__riscv_vmseq(word, (uint8_t)92, vl), vl);
needs_escape = __riscv_vmor(needs_escape,
__riscv_vmsltu(word, (uint8_t)32, vl), vl);
long first = __riscv_vfirst(needs_escape, vl);
if (first >= 0) {
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
return offset + first;
}
ptr += vl;
remaining -= vl;
}
return len;
}
#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64
simdjson_inline size_t
find_next_json_quotable_character(const std::string_view view,
size_t location) noexcept {
const size_t len = view.size();
const uint8_t *ptr =
reinterpret_cast<const uint8_t *>(view.data()) + location;
size_t remaining = len - location;
// SIMD constants for characters requiring escape
__vector unsigned char v34 = vec_splats((unsigned char)34); // '"'
__vector unsigned char v92 = vec_splats((unsigned char)92); // '\\'
__vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold
// Bitmask for vec_vbpermq to extract one bit per byte
const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50,
0x48, 0x40, 0x38, 0x30, 0x28, 0x20,
0x18, 0x10, 0x08, 0x00};
while (remaining >= 16) {
__vector unsigned char word =
vec_vsx_ld(0, reinterpret_cast<const unsigned char *>(ptr));
// Check for quotable characters: '"', '\\', or control chars (< 32)
__vector unsigned char needs_escape =
(__vector unsigned char)vec_cmpeq(word, v34);
needs_escape = vec_or(needs_escape,
(__vector unsigned char)vec_cmpeq(word, v92));
needs_escape = vec_or(needs_escape,
(__vector unsigned char)vec_cmplt(word, v32));
__vector unsigned long long result =
(__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask);
#ifdef __LITTLE_ENDIAN__
unsigned int mask = static_cast<unsigned int>(result[1]);
#else
return offset + __builtin_ctz(mask);
unsigned int mask = static_cast<unsigned int>(result[0]);
#endif
if (mask != 0) {
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
return offset + __builtin_ctz(mask);
}
ptr += 16;
remaining -= 16;
@@ -295,22 +455,9 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o
// written. Uses SIMD position finding to locate quotable characters efficiently.
inline size_t write_string_escaped(const std::string_view input, char *out) {
size_t mysize = input.size();
#if ABLATION_NO_SIMD_ESCAPING
auto naive_find_next_json_quotable_character = [](std::string_view input, size_t start) -> size_t {
for (size_t i = start; i < input.size(); ++i) {
auto c = static_cast<unsigned char>(input[i]);
if (c == '"' || c == '\\' || c < 32) return i;
}
return input.size();
};
#endif
// Use SIMD position finder directly - it returns mysize if no escape needed
#if ABLATION_NO_SIMD_ESCAPING
size_t location = naive_find_next_json_quotable_character(input, 0);
#else
size_t location = find_next_json_quotable_character(input, 0);
#endif
if (location == mysize) {
// Fast path: no escaping needed
memcpy(out, input.data(), input.size());
@@ -323,11 +470,7 @@ inline size_t write_string_escaped(const std::string_view input, char *out) {
escape_json_char(input[location], out);
location += 1;
while (location < mysize) {
#if ABLATION_NO_SIMD_ESCAPING
size_t newlocation = naive_find_next_json_quotable_character(input, location);
#else
size_t newlocation = find_next_json_quotable_character(input, location);
#endif
memcpy(out, input.data() + location, newlocation - location);
out += newlocation - location;
location = newlocation;
@@ -349,15 +492,6 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
// We use the convention that when is_valid is false, then the capacity and
// the position are 0.
// Most of the time, this function will return true.
#if ABLATION_NO_BRANCH_HINTS
if (upcoming_bytes <= capacity - position) {
return true;
}
// check for overflow, most of the time there is no overflow
if (position + upcoming_bytes < position) {
return false;
}
#else
if (simdjson_likely(upcoming_bytes <= capacity - position)) {
return true;
}
@@ -365,14 +499,13 @@ simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
if (simdjson_unlikely(position + upcoming_bytes < position)) {
return false;
}
#endif
// We will rarely get here.
grow_buffer((std::max)(capacity * 2, position + upcoming_bytes));
// If the buffer allocation failed, we set is_valid to false.
return is_valid;
}
simdjson_inline void string_builder::grow_buffer(size_t desired_capacity) {
inline void string_builder::grow_buffer(size_t desired_capacity) {
if (!is_valid) {
return;
}
@@ -709,7 +842,7 @@ simdjson_inline void string_builder::append(const T &value) {
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R>
requires(!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>)
requires(!std::is_convertible<R, std::string_view>::value && !concepts::optional_type<R> && !require_custom_serialization<R>)
simdjson_inline void string_builder::append(const R &range) noexcept {
auto it = std::ranges::begin(range);
auto end = std::ranges::end(range);
@@ -171,7 +171,7 @@ public:
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
// Support for range-based appending (std::ranges::view, etc.)
template <std::ranges::range R>
requires (!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>)
requires (!std::is_convertible<R, std::string_view>::value && !concepts::optional_type<R> && !require_custom_serialization<R>)
simdjson_inline void append(const R &range) noexcept;
#endif
/**
@@ -249,7 +249,7 @@ private:
* If the allocation fails, is_valid is set to false. We expect
* that this function would not be repeatedly called.
*/
simdjson_inline void grow_buffer(size_t desired_capacity);
inline void grow_buffer(size_t desired_capacity);
/**
* We use this helper function to make sure that is_valid is kept consistent.
@@ -74,6 +74,7 @@ inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(s
}
inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept {
if(max_depth > SIMDJSON_MAX_DEPTH) { return CAPACITY; }
// Stage 2 stacks
open_containers.reset(new (std::nothrow) open_container[max_depth]);
is_array.reset(new (std::nothrow) bool[max_depth]);
@@ -20,6 +20,7 @@
#include "simdjson/generic/ondemand/document.h"
#include "simdjson/generic/ondemand/document_stream.h"
#include "simdjson/generic/ondemand/field.h"
#include "simdjson/generic/ondemand/key_selector.h"
#include "simdjson/generic/ondemand/object.h"
#include "simdjson/generic/ondemand/object_iterator.h"
#include "simdjson/generic/ondemand/serialization.h"
@@ -16,6 +16,8 @@
#include "simdjson/generic/ondemand/deserialize.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <limits>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
@@ -131,6 +133,18 @@ simdjson_inline simdjson_result<int64_t> document::get_int64() noexcept {
simdjson_inline simdjson_result<int64_t> document::get_int64_in_string() noexcept {
return get_root_value_iterator().get_root_int64_in_string(true);
}
simdjson_inline simdjson_result<uint32_t> document::get_uint32() noexcept {
uint64_t result;
SIMDJSON_TRY(get_uint64().get(result));
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<uint32_t>(result);
}
simdjson_inline simdjson_result<int32_t> document::get_int32() noexcept {
int64_t result;
SIMDJSON_TRY(get_int64().get(result));
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<int32_t>(result);
}
simdjson_inline simdjson_result<double> document::get_double() noexcept {
return get_root_value_iterator().get_root_double(true);
}
@@ -164,6 +178,8 @@ template<> simdjson_inline simdjson_result<std::string_view> document::get() & n
template<> simdjson_inline simdjson_result<double> document::get() & noexcept { return get_double(); }
template<> simdjson_inline simdjson_result<uint64_t> document::get() & noexcept { return get_uint64(); }
template<> simdjson_inline simdjson_result<int64_t> document::get() & noexcept { return get_int64(); }
template<> simdjson_inline simdjson_result<uint32_t> document::get() & noexcept { return get_uint32(); }
template<> simdjson_inline simdjson_result<int32_t> document::get() & noexcept { return get_int32(); }
template<> simdjson_inline simdjson_result<bool> document::get() & noexcept { return get_bool(); }
template<> simdjson_inline simdjson_result<value> document::get() & noexcept { return get_value(); }
@@ -174,6 +190,8 @@ template<> simdjson_warn_unused simdjson_inline error_code document::get(std::st
template<> simdjson_warn_unused simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(uint32_t& out) & noexcept { return get_uint32().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(int32_t& out) & noexcept { return get_int32().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); }
@@ -504,6 +522,14 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
if (error()) { return error(); }
return first.get_int64_in_string();
}
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_uint32() noexcept {
if (error()) { return error(); }
return first.get_uint32();
}
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_int32() noexcept {
if (error()) { return error(); }
return first.get_int32();
}
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::get_double() noexcept {
if (error()) { return error(); }
return first.get_double();
@@ -627,27 +653,27 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::op
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator uint64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_uint64();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator int64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_int64();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator double() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_double();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator std::string_view() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_string();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_raw_json_string();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator bool() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_bool();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
@@ -731,6 +757,18 @@ simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64() noexc
simdjson_inline simdjson_result<uint64_t> document_reference::get_uint64_in_string() noexcept { return doc->get_root_value_iterator().get_root_uint64_in_string(false); }
simdjson_inline simdjson_result<int64_t> document_reference::get_int64() noexcept { return doc->get_root_value_iterator().get_root_int64(false); }
simdjson_inline simdjson_result<int64_t> document_reference::get_int64_in_string() noexcept { return doc->get_root_value_iterator().get_root_int64_in_string(false); }
simdjson_inline simdjson_result<uint32_t> document_reference::get_uint32() noexcept {
uint64_t result;
SIMDJSON_TRY(doc->get_root_value_iterator().get_root_uint64(false).get(result));
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<uint32_t>(result);
}
simdjson_inline simdjson_result<int32_t> document_reference::get_int32() noexcept {
int64_t result;
SIMDJSON_TRY(doc->get_root_value_iterator().get_root_int64(false).get(result));
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<int32_t>(result);
}
simdjson_inline simdjson_result<double> document_reference::get_double() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
simdjson_inline simdjson_result<double> document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); }
simdjson_inline simdjson_result<std::string_view> document_reference::get_string(bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(false, allow_replacement); }
@@ -748,6 +786,8 @@ template<> simdjson_inline simdjson_result<std::string_view> document_reference:
template<> simdjson_inline simdjson_result<double> document_reference::get() & noexcept { return get_double(); }
template<> simdjson_inline simdjson_result<uint64_t> document_reference::get() & noexcept { return get_uint64(); }
template<> simdjson_inline simdjson_result<int64_t> document_reference::get() & noexcept { return get_int64(); }
template<> simdjson_inline simdjson_result<uint32_t> document_reference::get() & noexcept { return get_uint32(); }
template<> simdjson_inline simdjson_result<int32_t> document_reference::get() & noexcept { return get_int32(); }
template<> simdjson_inline simdjson_result<bool> document_reference::get() & noexcept { return get_bool(); }
template<> simdjson_inline simdjson_result<value> document_reference::get() & noexcept { return get_value(); }
#if SIMDJSON_EXCEPTIONS
@@ -879,6 +919,14 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
if (error()) { return error(); }
return first.get_int64_in_string();
}
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_uint32() noexcept {
if (error()) { return error(); }
return first.get_uint32();
}
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_int32() noexcept {
if (error()) { return error(); }
return first.get_int32();
}
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::get_double() noexcept {
if (error()) { return error(); }
return first.get_double();
@@ -994,27 +1042,27 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_refe
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator uint64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_uint64();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator int64_t() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_int64();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator double() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_double();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator std::string_view() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_string();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_raw_json_string();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator bool() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
return first;
return first.get_bool();
}
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_reference>::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) {
if (error()) { throw simdjson_error(error()); }
+50 -24
View File
@@ -76,6 +76,26 @@ public:
* @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer.
*/
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
/**
* Cast this JSON value to a 32-bit unsigned integer.
*
* Calls get_uint64() and checks that the result fits in a uint32_t.
*
* @returns A 32-bit unsigned integer.
* @returns INCORRECT_TYPE If the JSON value is not an unsigned integer.
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in a uint32_t.
*/
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
/**
* Cast this JSON value to a 32-bit signed integer.
*
* Calls get_int64() and checks that the result fits in an int32_t.
*
* @returns A 32-bit signed integer.
* @returns INCORRECT_TYPE If the JSON value is not an integer.
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in an int32_t.
*/
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
/**
* Cast this JSON value to a double.
*
@@ -299,21 +319,21 @@ public:
* @returns A signed 64-bit integer.
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer.
*/
simdjson_inline operator uint64_t() noexcept(false);
explicit simdjson_inline operator uint64_t() noexcept(false);
/**
* Cast this JSON value to a signed integer.
*
* @returns A signed 64-bit integer.
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer.
*/
simdjson_inline operator int64_t() noexcept(false);
explicit simdjson_inline operator int64_t() noexcept(false);
/**
* Cast this JSON value to a double.
*
* @returns A double.
* @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number.
*/
simdjson_inline operator double() noexcept(false);
explicit simdjson_inline operator double() noexcept(false);
/**
* Cast this JSON value to a string.
*
@@ -323,7 +343,7 @@ public:
* time it parses a document or when it is destroyed.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
*/
simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound;
explicit simdjson_inline operator std::string_view() noexcept(false) simdjson_lifetime_bound;
/**
* Cast this JSON value to a raw_json_string.
*
@@ -332,14 +352,14 @@ public:
* @returns A pointer to the raw JSON for the given string.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string.
*/
simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound;
explicit simdjson_inline operator raw_json_string() noexcept(false) simdjson_lifetime_bound;
/**
* Cast this JSON value to a bool.
*
* @returns A bool value.
* @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false.
*/
simdjson_inline operator bool() noexcept(false);
explicit simdjson_inline operator bool() noexcept(false);
/**
* Cast this JSON value to a value when the document is an object or an array.
*
@@ -829,6 +849,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -923,12 +945,12 @@ public:
explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator array() & noexcept(false);
simdjson_inline operator object() & noexcept(false);
simdjson_inline operator uint64_t() noexcept(false);
simdjson_inline operator int64_t() noexcept(false);
simdjson_inline operator double() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator raw_json_string() noexcept(false);
simdjson_inline operator bool() noexcept(false);
explicit simdjson_inline operator uint64_t() noexcept(false);
explicit simdjson_inline operator int64_t() noexcept(false);
explicit simdjson_inline operator double() noexcept(false);
explicit simdjson_inline operator std::string_view() noexcept(false);
explicit simdjson_inline operator raw_json_string() noexcept(false);
explicit simdjson_inline operator bool() noexcept(false);
simdjson_inline operator value() noexcept(false);
#endif
simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
@@ -982,6 +1004,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -1006,12 +1030,12 @@ public:
explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false);
simdjson_inline operator uint64_t() noexcept(false);
simdjson_inline operator int64_t() noexcept(false);
simdjson_inline operator double() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
simdjson_inline operator bool() noexcept(false);
explicit simdjson_inline operator uint64_t() noexcept(false);
explicit simdjson_inline operator int64_t() noexcept(false);
explicit simdjson_inline operator double() noexcept(false);
explicit simdjson_inline operator std::string_view() noexcept(false);
explicit simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
explicit simdjson_inline operator bool() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false);
#endif
simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
@@ -1069,6 +1093,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -1090,12 +1116,12 @@ public:
explicit simdjson_inline operator T() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false);
simdjson_inline operator uint64_t() noexcept(false);
simdjson_inline operator int64_t() noexcept(false);
simdjson_inline operator double() noexcept(false);
simdjson_inline operator std::string_view() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
simdjson_inline operator bool() noexcept(false);
explicit simdjson_inline operator uint64_t() noexcept(false);
explicit simdjson_inline operator int64_t() noexcept(false);
explicit simdjson_inline operator double() noexcept(false);
explicit simdjson_inline operator std::string_view() noexcept(false);
explicit simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false);
explicit simdjson_inline operator bool() noexcept(false);
simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false);
#endif
simdjson_inline simdjson_result<size_t> count_elements() & noexcept;
@@ -95,13 +95,15 @@ simdjson_inline document_stream::document_stream(
const uint8_t *_buf,
size_t _len,
size_t _batch_size,
bool _allow_comma_separated
bool _allow_comma_separated,
stream_format _format
) noexcept
: parser{&_parser},
buf{_buf},
len{_len},
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
allow_comma_separated{_allow_comma_separated},
format{_format},
error{SUCCESS}
#ifdef SIMDJSON_THREADS_ENABLED
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
@@ -120,6 +122,7 @@ simdjson_inline document_stream::document_stream() noexcept
len{0},
batch_size{0},
allow_comma_separated{false},
format{stream_format::whitespace_delimited},
error{UNINITIALIZED}
#ifdef SIMDJSON_THREADS_ENABLED
, use_thread(false)
@@ -219,7 +222,10 @@ inline void document_stream::start() noexcept {
error = run_stage1(*parser, batch_start);
}
if (error) { return; }
doc_index = batch_start;
// For json_sequence mode, structural_indexes[0] points to the actual JSON value
// after the RS delimiter and any following whitespace. For regular mode, it is
// the offset from batch_start to the first document in the batch.
doc_index = batch_start + parser->implementation->structural_indexes[0];
doc = document(json_iterator(&buf[batch_start], parser));
doc.iter._streaming = true;
@@ -300,7 +306,7 @@ inline void document_stream::next() noexcept {
*/
if (error) { continue; } // If the error was EMPTY, we may want to load another batch.
doc_index = batch_start;
doc_index = batch_start + parser->implementation->structural_indexes[0];
}
}
}
@@ -329,10 +335,35 @@ inline error_code document_stream::run_stage1(ondemand::parser &p, size_t _batch
// This code only updates the structural index in the parser, it does not update any json_iterator
// instance.
size_t remaining = len - _batch_start;
stage1_mode mode;
if (remaining <= batch_size) {
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
// Final batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_final;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_final;
break;
default:
mode = stage1_mode::streaming_final;
break;
}
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
} else {
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
// Partial batch
switch (format) {
case stream_format::json_sequence:
mode = stage1_mode::json_sequence_partial;
break;
case stream_format::comma_delimited:
mode = stage1_mode::comma_delimited_partial;
break;
default:
mode = stage1_mode::streaming_partial;
break;
}
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
}
}
@@ -353,14 +384,21 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
depth--;
break;
default: // Scalar value document
// TODO: We could remove trailing whitespaces
// This returns a string spanning from start of value to the beginning of the next document (excluded)
{
auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index];
// normally the length would be next_index - current_index() - 1, except for the last document
size_t svlen = next_index - current_index();
const char *start = reinterpret_cast<const char*>(stream->buf) + current_index();
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464
// json_sequence mode the scanner classifies RS as a scalar
// character, so an RS-prefixed scalar document (number / true /
// false / null / string) has no closing structural index and the
// slice runs all the way up to the next document's RS. RS cannot
// legally appear in a JSON value at the source level (control
// characters in strings must be escaped as \u001E), so stripping
// it is safe in every stream_format.
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E)) {
svlen--;
}
return std::string_view(start, svlen);
@@ -441,4 +479,4 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::document_stre
}
#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H
#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H
@@ -229,13 +229,16 @@ private:
* @param buf is the raw byte buffer we need to process
* @param len is the length of the raw byte buffer in bytes
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
* @param allow_comma_separated whether to allow comma-separated documents
* @param format the stream format
*/
simdjson_inline document_stream(
ondemand::parser &parser,
const uint8_t *buf,
size_t len,
size_t batch_size,
bool allow_comma_separated
bool allow_comma_separated,
stream_format format = stream_format::whitespace_delimited
) noexcept;
/**
@@ -284,6 +287,7 @@ private:
size_t len;
size_t batch_size;
bool allow_comma_separated;
stream_format format;
/**
* We are going to use just one document instance. The document owns
* the json_iterator. It implies that we only ever pass a reference
@@ -0,0 +1,339 @@
#ifndef SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
#define SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
#include "simdjson/base.h"
#include "simdjson/common_defs.h"
#include <array>
#include <string_view>
#include <cstddef>
#include <cstdint>
#include <cstring>
#if SIMDJSON_SUPPORTS_CONCEPTS
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
// Forward declaration
class object;
/**
* A compile-time key selector for efficient JSON object field lookup.
* Uses perfect hashing (gperf-style) to map keys to identifiers.
*/
template <std::size_t N>
class key_selector {
static_assert(N > 0, "key_selector requires at least one key");
static_assert(N <= 100, "key_selector supports at most 100 keys");
// Perfect hash table data (gperf-style)
static constexpr std::size_t MAX_POSITIONS = 16;
static constexpr std::size_t POS_LAST_CHAR = std::size_t(-1);
static constexpr std::size_t MAX_TABLE_SIZE = 256; // Power of 2, fits in uint8_t
std::array<std::array<std::uint8_t, 256>, MAX_POSITIONS> asso_values_{};
std::uint8_t num_positions_{};
std::array<std::size_t, MAX_POSITIONS> positions_{};
std::array<std::uint8_t, MAX_TABLE_SIZE> slot_to_key_{};
std::array<std::uint8_t, N> key_to_slot_{};
std::array<std::array<char, 64>, N> key_data_{};
std::array<std::uint8_t, N> key_lengths_{};
std::size_t table_size_{};
public:
// Validate keys at compile time
constexpr void validate_keys(const std::array<std::string_view, N>& keys) {
for (std::size_t i = 0; i < N; ++i) {
auto key = keys[i];
if (key.empty()) {
throw "Empty keys are not allowed in key_selector";
}
if (key.size() > SIMDJSON_PADDING) {
throw "Key length exceeds SIMDJSON_PADDING (64 bytes)";
}
for (char c : key) {
if (c == '\\') {
throw "Escape characters (\\) are not allowed in key_selector keys";
}
if (c == '\0') {
throw "Null characters are not allowed in key_selector keys";
}
}
}
}
// Gperf-style perfect hash generation using partition-based algorithm
constexpr void generate_hash_table(const std::array<std::string_view, N>& keys) {
// Try power-of-two table sizes starting from next_power_of_2(N)
constexpr std::size_t START_M = next_power_of_2(N);
if constexpr (START_M <= MAX_TABLE_SIZE) {
if (try_compute_phf<START_M>(keys)) return;
if constexpr (START_M * 2 <= MAX_TABLE_SIZE) {
if (try_compute_phf<START_M * 2>(keys)) return;
if constexpr (START_M * 4 <= MAX_TABLE_SIZE) {
if (try_compute_phf<START_M * 4>(keys)) return;
if constexpr (START_M * 8 <= MAX_TABLE_SIZE) {
if (try_compute_phf<START_M * 8>(keys)) return;
}
}
}
}
// Fallback: linear table
table_size_ = N;
num_positions_ = 0;
std::fill(slot_to_key_.begin(), slot_to_key_.begin() + MAX_TABLE_SIZE, static_cast<std::uint8_t>(N));
for (std::size_t i = 0; i < N; ++i) {
slot_to_key_[i] = static_cast<std::uint8_t>(i);
key_to_slot_[i] = static_cast<std::uint8_t>(i);
}
}
private:
// Helper functions for gperf algorithm
static constexpr std::size_t next_power_of_2(std::size_t n) {
if (n == 0) return 1;
std::size_t p = 1;
while (p < n) p <<= 1;
return p;
}
static constexpr std::size_t char_at(std::string_view key, std::size_t pos) {
if (pos == POS_LAST_CHAR) {
return key.empty() ? 256 : static_cast<unsigned char>(key.back());
}
return (pos < key.size()) ? static_cast<unsigned char>(key[pos]) : 256;
}
template <std::size_t M>
constexpr bool try_compute_phf(const std::array<std::string_view, N>& keys) {
// Initialize
std::array<std::array<std::size_t, 256>, MAX_POSITIONS> asso{};
std::size_t npos = 0;
std::array<std::size_t, MAX_POSITIONS> pos{};
std::array<std::size_t, M> s2k{};
// Try to generate gperf
if (try_generate_gperf<M>(keys, asso, npos, pos, s2k)) {
table_size_ = M;
num_positions_ = static_cast<std::uint8_t>(npos);
for (std::size_t p = 0; p < MAX_POSITIONS; ++p) {
positions_[p] = pos[p];
for (std::size_t c = 0; c < 256; ++c) {
asso_values_[p][c] = static_cast<std::uint8_t>(asso[p][c]);
}
}
for (std::size_t i = 0; i < M; ++i) {
slot_to_key_[i] = static_cast<std::uint8_t>(s2k[i]);
}
// Fill remaining slots with sentinel
for (std::size_t i = M; i < MAX_TABLE_SIZE; ++i) {
slot_to_key_[i] = static_cast<std::uint8_t>(N);
}
// Build key_to_slot mapping
for (std::size_t i = 0; i < N; ++i) {
key_to_slot_[i] = static_cast<std::uint8_t>(N); // Initialize
}
for (std::size_t slot = 0; slot < M; ++slot) {
std::size_t key_idx = s2k[slot];
if (key_idx < N) {
key_to_slot_[key_idx] = static_cast<std::uint8_t>(slot);
}
}
return true;
}
return false;
}
template <std::size_t M>
static constexpr bool try_generate_gperf(
const std::array<std::string_view, N>& keys,
std::array<std::array<std::size_t, 256>, MAX_POSITIONS>& asso_values,
std::size_t& num_positions,
std::array<std::size_t, MAX_POSITIONS>& positions,
std::array<std::size_t, M>& slot_to_key)
{
// Initialize
for (std::size_t p = 0; p < MAX_POSITIONS; ++p) {
for (std::size_t c = 0; c < 256; ++c) {
asso_values[p][c] = 0;
}
}
for (std::size_t i = 0; i < M; ++i) {
slot_to_key[i] = N;
}
// Try length-only hashing first
bool success = true;
for (std::size_t i = 0; i < N && success; ++i) {
std::size_t slot = keys[i].size() % M;
if (slot_to_key[slot] != N) {
success = false;
} else {
slot_to_key[slot] = i;
}
}
if (success) {
num_positions = 0;
return true;
}
// Try with position 0
positions[0] = 0;
num_positions = 1;
// Find a working assignment of asso_values for position 0
// Use a simple approach: try different offsets
for (std::size_t offset = 0; offset < M; ++offset) {
// Reset
for (std::size_t i = 0; i < M; ++i) {
slot_to_key[i] = N;
}
// Assign asso_values based on offset
for (std::size_t c = 0; c < 256; ++c) {
asso_values[0][c] = (c + offset) % M;
}
success = true;
for (std::size_t i = 0; i < N && success; ++i) {
std::size_t h = keys[i].size();
std::size_t ch = char_at(keys[i], 0);
if (ch < 256) h += asso_values[0][ch];
std::size_t slot = h % M;
if (slot_to_key[slot] != N) {
success = false;
} else {
slot_to_key[slot] = i;
}
}
if (success) {
return true;
}
}
// Try with positions {0, last_char}
if (N <= 50) { // Only for smaller N to avoid complexity
positions[0] = 0;
positions[1] = POS_LAST_CHAR;
num_positions = 2;
for (std::size_t offset1 = 0; offset1 < 4 && !success; ++offset1) {
for (std::size_t offset2 = 0; offset2 < 4 && !success; ++offset2) {
// Reset
for (std::size_t i = 0; i < M; ++i) {
slot_to_key[i] = N;
}
// Assign asso_values
for (std::size_t c = 0; c < 256; ++c) {
asso_values[0][c] = (c + offset1) % M;
asso_values[1][c] = (c + offset2) % M;
}
success = true;
for (std::size_t i = 0; i < N && success; ++i) {
std::size_t h = keys[i].size();
std::size_t ch1 = char_at(keys[i], 0);
if (ch1 < 256) h += asso_values[0][ch1];
std::size_t ch2 = char_at(keys[i], POS_LAST_CHAR);
if (ch2 < 256) h += asso_values[1][ch2];
std::size_t slot = h % M;
if (slot_to_key[slot] != N) {
success = false;
} else {
slot_to_key[slot] = i;
}
}
if (success) {
return true;
}
}
}
}
return false;
}
public:
constexpr key_selector(const std::array<std::string_view, N>& keys) {
validate_keys(keys);
// Store key data
for (std::size_t i = 0; i < N; ++i) {
key_lengths_[i] = static_cast<std::uint8_t>(keys[i].size());
std::copy(keys[i].begin(), keys[i].end(), key_data_[i].begin());
}
generate_hash_table(keys);
}
[[nodiscard]] constexpr std::size_t size() const noexcept { return N; }
[[nodiscard]] constexpr simdjson_really_inline std::size_t compute_hash(std::string_view key) const noexcept {
std::size_t h = key.size();
const char* kp = key.data();
for (std::uint8_t i = 0; i < num_positions_; ++i) {
std::size_t pos = positions_[i];
std::size_t ch;
if (pos == POS_LAST_CHAR) {
ch = static_cast<unsigned char>(key.back());
} else {
ch = static_cast<unsigned char>(kp[pos]);
}
h += asso_values_[i][ch];
}
return h & (table_size_ - 1);
}
[[nodiscard]] constexpr simdjson_really_inline bool contains(std::string_view key) const noexcept {
std::size_t slot = compute_hash(key);
if (slot >= table_size_) return false;
std::uint8_t key_idx = slot_to_key_[slot];
if (key_idx >= N) return false;
// Compare key
if (key_lengths_[key_idx] != key.size()) return false;
return std::equal(key.begin(), key.end(), key_data_[key_idx].begin());
}
[[nodiscard]] constexpr simdjson_really_inline std::size_t index_of(std::string_view key) const noexcept {
std::size_t slot = compute_hash(key);
if (slot >= table_size_) return N; // Invalid index
std::uint8_t key_idx = slot_to_key_[slot];
if (key_idx >= N) return N;
// Compare key
if (key_lengths_[key_idx] != key.size()) return N;
if (!std::equal(key.begin(), key.end(), key_data_[key_idx].begin())) return N;
return key_idx;
}
// Accessors for key data (used by object::find_field)
[[nodiscard]] constexpr std::string_view get_key(std::size_t index) const noexcept {
if (index >= N) return {};
return std::string_view(key_data_[index].data(), key_lengths_[index]);
}
};
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SUPPORTS_CONCEPTS
#endif // SIMDJSON_GENERIC_ONDEMAND_KEY_SELECTOR_H
@@ -63,6 +63,42 @@ simdjson_inline simdjson_result<value> object::find_field(const std::string_view
return value(iter.child());
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<value>> object::find_field(const Selector& selector) & noexcept {
// Try to find any of the keys in the selector
for (std::size_t i = 0; i < selector.size(); ++i) {
std::string_view key = selector.get_key(i);
auto result = iter.find_field_unordered_raw(key);
if (result.error()) {
return {selector.size(), result.error()};
}
bool has_value = result.value();
if (has_value) {
return {i, value(iter.child())};
}
}
return {selector.size(), NO_SUCH_FIELD}; // Return size() as invalid index
}
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<value>> object::find_field(const Selector& selector) && noexcept {
// Try to find any of the keys in the selector
for (std::size_t i = 0; i < selector.size(); ++i) {
std::string_view key = selector.get_key(i);
auto result = iter.find_field_unordered_raw(key);
if (result.error()) {
return {selector.size(), result.error()};
}
bool has_value = result.value();
if (has_value) {
return {i, value(iter.child())};
}
}
return {selector.size(), NO_SUCH_FIELD}; // Return size() as invalid index
}
#endif
simdjson_inline simdjson_result<object> object::start(value_iterator &iter) noexcept {
SIMDJSON_TRY( iter.start_object().error() );
return object(iter);
@@ -334,6 +370,20 @@ simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjs
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(key);
}
#if SIMDJSON_SUPPORTS_CONCEPTS
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::find_field(const Selector& selector) & noexcept {
if (error()) { return {0, error()}; }
return first.find_field(selector);
}
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::find_field(const Selector& selector) && noexcept {
if (error()) { return {0, error()}; }
return std::forward<SIMDJSON_IMPLEMENTATION::ondemand::object>(first).find_field(selector);
}
#endif
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::object>::at_pointer(std::string_view json_pointer) noexcept {
if (error()) { return error(); }
return first.at_pointer(json_pointer);
@@ -5,6 +5,7 @@
#include "simdjson/generic/ondemand/base.h"
#include "simdjson/generic/implementation_simdjson_result_base.h"
#include "simdjson/generic/ondemand/value_iterator.h"
#include "simdjson/generic/ondemand/key_selector.h"
#include <vector>
#if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS
#include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string
@@ -122,6 +123,23 @@ public:
/** @overload simdjson_inline simdjson_result<value> find_field_unordered(std::string_view key) & noexcept; */
simdjson_inline simdjson_result<value> operator[](std::string_view key) && noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
/**
* Look up a field by name using a key_selector. This method is similar to find_field_unordered()
* but uses a compile-time generated perfect hash table for efficient lookup.
*
* @tparam Selector The key_selector type
* @param selector The key selector instance
* @returns A pair containing the key identifier (index in the selector) and the value,
* or NO_SUCH_FIELD if the field is not in the object.
*/
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<value>> find_field(const Selector& selector) & noexcept;
/** @overload template <concepts::key_selector_type Selector> simdjson_inline std::pair<std::size_t, simdjson_result<value>> find_field(const Selector& selector) & noexcept; */
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<value>> find_field(const Selector& selector) && noexcept;
#endif
/**
* Get the value associated with the given JSON pointer. We use the RFC 6901
* https://tools.ietf.org/html/rfc6901 standard, interpreting the current node
@@ -324,6 +342,12 @@ public:
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field(std::string_view key) && noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) & noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> find_field_unordered(std::string_view key) && noexcept;
#if SIMDJSON_SUPPORTS_CONCEPTS
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>> find_field(const Selector& selector) & noexcept;
template <concepts::key_selector_type Selector>
simdjson_inline std::pair<std::size_t, simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>> find_field(const Selector& selector) && noexcept;
#endif
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) & noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> operator[](std::string_view key) && noexcept;
simdjson_inline simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value> at_pointer(std::string_view json_pointer) noexcept;
+76 -3
View File
@@ -133,6 +133,34 @@ simdjson_warn_unused simdjson_inline simdjson_result<json_iterator> parser::iter
return json_iterator(reinterpret_cast<const uint8_t *>(json.data()), this);
}
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
return document_stream(*this, buf, len, batch_size, false, stream_format::whitespace_delimited);
}
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size) noexcept {
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
}
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size) noexcept {
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
return iterate_many(s.data(), s.length(), batch_size);
}
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size) noexcept {
return iterate_many(padded_string_view(s), batch_size);
}
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size) noexcept {
return iterate_many(padded_string_view(s), batch_size);
}
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size) noexcept {
return iterate_many(pad(s), batch_size);
}
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
SIMDJSON_PUSH_DISABLE_WARNINGS
SIMDJSON_DISABLE_DEPRECATED_WARNING
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
// Warning: no check is done on the buffer padding. We trust the user.
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
@@ -140,8 +168,11 @@ inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf,
buf += 3;
len -= 3;
}
if(allow_comma_separated && batch_size < len) { batch_size = len; }
return document_stream(*this, buf, len, batch_size, allow_comma_separated);
// Map allow_comma_separated to stream_format::comma_delimited
if (allow_comma_separated) {
return document_stream(*this, buf, len, batch_size, false, stream_format::comma_delimited);
}
return document_stream(*this, buf, len, batch_size, false, stream_format::whitespace_delimited);
}
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
@@ -161,6 +192,48 @@ inline simdjson_result<document_stream> parser::iterate_many(const std::string &
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
return iterate_many(pad(s), batch_size, allow_comma_separated);
}
SIMDJSON_POP_DISABLE_WARNINGS
#endif // SIMDJSON_DISABLE_DEPRECATED_API
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
buf += 3;
len -= 3;
}
if (format == stream_format::comma_delimited_array) {
// Strip leading JSON whitespace.
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
buf++; len--;
}
// Expect the opening '['.
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
buf++; len--;
// Strip trailing JSON whitespace.
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
len--;
}
// Expect the closing ']'.
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
len--;
// Fall through to comma_delimited over the array contents.
format = stream_format::comma_delimited;
}
return document_stream(*this, buf, len, batch_size, false, format);
}
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
}
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept {
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
return iterate_many(s.data(), s.length(), batch_size, format);
}
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
return iterate_many(padded_string_view(s), batch_size, format);
}
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
return iterate_many(padded_string_view(s), batch_size, format);
}
simdjson_pure simdjson_inline size_t parser::capacity() const noexcept {
return _capacity;
}
@@ -172,7 +245,7 @@ simdjson_pure simdjson_inline size_t parser::max_depth() const noexcept {
}
simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept {
if(max_capacity < dom::MINIMAL_DOCUMENT_CAPACITY) {
if(max_capacity > dom::MINIMAL_DOCUMENT_CAPACITY) {
_max_capacity = max_capacity;
} else {
_max_capacity = dom::MINIMAL_DOCUMENT_CAPACITY;
+51 -17
View File
@@ -244,32 +244,66 @@ public:
* spot is cache-related: small enough to fit in cache, yet big enough to
* parse as many documents as possible in one tight loop.
* Defaults to 10MB, which has been a reasonable sweet spot in our tests.
* @param allow_comma_separated (defaults on false) This allows a mode where the documents are
* separated by commas instead of whitespace. It comes with a performance
* penalty because the entire document is indexed at once (and the document must be
* less than 4 GB), and there is no multithreading. In this mode, the batch_size parameter
* is effectively ignored, as it is set to at least the document size.
* @param allow_comma_separated @deprecated Use stream_format::comma_delimited instead.
* When true, maps internally to stream_format::comma_delimited.
* Defaults to false.
* @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors:
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails
* - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity.
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
*/
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size)
the string might be automatically padded with up to SIMDJSON_PADDING whitespace characters */
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
/** @private We do not want to allow implicit conversion from C string to std::string. */
simdjson_result<document_stream> iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete;
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
/**
* @deprecated Use iterate_many with stream_format::comma_delimited instead.
*/
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size, bool allow_comma_separated) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size, bool allow_comma_separated) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept;
#endif // SIMDJSON_DISABLE_DEPRECATED_API
/**
* Parse a stream of JSON documents with explicit format specification.
*
* @param buf The concatenated JSON documents.
* @param len The length of the buffer.
* @param batch_size The batch size to use.
* @param format The stream format (whitespace_delimited, json_sequence, or comma_delimited).
* @return A stream of documents, or an error.
*/
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
/** The capacity of this parser (the largest document it can process). */
simdjson_pure simdjson_inline size_t capacity() const noexcept;
/** The maximum capacity of this parser (the largest document it is allowed to process). */
@@ -425,4 +459,4 @@ public:
} // namespace simdjson
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_H
#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_H
@@ -305,7 +305,7 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
#if SIMDJSON_STATIC_REFLECTION
std::string_view str;
SIMDJSON_TRY(val.get_string().get(str));
constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
static constexpr auto enumerators = std::define_static_array(std::meta::enumerators_of(^^T));
template for (constexpr auto enum_val : enumerators) {
if (str == std::meta::identifier_of(enum_val)) {
out = [:enum_val:];
@@ -12,6 +12,8 @@
#include "simdjson/generic/ondemand/value.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <limits>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace ondemand {
@@ -72,6 +74,18 @@ simdjson_inline simdjson_result<int64_t> value::get_int64() noexcept {
simdjson_inline simdjson_result<int64_t> value::get_int64_in_string() noexcept {
return iter.get_int64_in_string();
}
simdjson_inline simdjson_result<uint32_t> value::get_uint32() noexcept {
uint64_t result;
SIMDJSON_TRY(get_uint64().get(result));
if (result > (std::numeric_limits<uint32_t>::max)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<uint32_t>(result);
}
simdjson_inline simdjson_result<int32_t> value::get_int32() noexcept {
int64_t result;
SIMDJSON_TRY(get_int64().get(result));
if (result > (std::numeric_limits<int32_t>::max)() || result < (std::numeric_limits<int32_t>::min)()) { return NUMBER_OUT_OF_RANGE; }
return static_cast<int32_t>(result);
}
simdjson_inline simdjson_result<bool> value::get_bool() noexcept {
return iter.get_bool();
}
@@ -87,6 +101,8 @@ template<> simdjson_inline simdjson_result<number> value::get() noexcept { retur
template<> simdjson_inline simdjson_result<double> value::get() noexcept { return get_double(); }
template<> simdjson_inline simdjson_result<uint64_t> value::get() noexcept { return get_uint64(); }
template<> simdjson_inline simdjson_result<int64_t> value::get() noexcept { return get_int64(); }
template<> simdjson_inline simdjson_result<uint32_t> value::get() noexcept { return get_uint32(); }
template<> simdjson_inline simdjson_result<int32_t> value::get() noexcept { return get_int32(); }
template<> simdjson_inline simdjson_result<bool> value::get() noexcept { return get_bool(); }
@@ -98,6 +114,8 @@ template<> simdjson_warn_unused simdjson_inline error_code value::get(number& ou
template<> simdjson_warn_unused simdjson_inline error_code value::get(double& out) noexcept { return get_double().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code value::get(uint64_t& out) noexcept { return get_uint64().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code value::get(int64_t& out) noexcept { return get_int64().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code value::get(uint32_t& out) noexcept { return get_uint32().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code value::get(int32_t& out) noexcept { return get_int32().get(out); }
template<> simdjson_warn_unused simdjson_inline error_code value::get(bool& out) noexcept { return get_bool().get(out); }
#if SIMDJSON_EXCEPTIONS
@@ -401,6 +419,14 @@ simdjson_inline simdjson_result<int64_t> simdjson_result<SIMDJSON_IMPLEMENTATION
if (error()) { return error(); }
return first.get_int64_in_string();
}
simdjson_inline simdjson_result<uint32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get_uint32() noexcept {
if (error()) { return error(); }
return first.get_uint32();
}
simdjson_inline simdjson_result<int32_t> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get_int32() noexcept {
if (error()) { return error(); }
return first.get_int32();
}
simdjson_inline simdjson_result<double> simdjson_result<SIMDJSON_IMPLEMENTATION::ondemand::value>::get_double() noexcept {
if (error()) { return error(); }
return first.get_double();
+24
View File
@@ -161,6 +161,28 @@ public:
*/
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
/**
* Cast this JSON value to a 32-bit unsigned integer.
*
* Calls get_uint64() and checks that the result fits in a uint32_t.
*
* @returns A 32-bit unsigned integer.
* @returns INCORRECT_TYPE If the JSON value is not an unsigned integer.
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in a uint32_t.
*/
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
/**
* Cast this JSON value to a 32-bit signed integer.
*
* Calls get_int64() and checks that the result fits in an int32_t.
*
* @returns A 32-bit signed integer.
* @returns INCORRECT_TYPE If the JSON value is not an integer.
* @returns NUMBER_OUT_OF_RANGE If the value does not fit in an int32_t.
*/
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
/**
* Cast this JSON value to a double.
*
@@ -746,6 +768,8 @@ public:
simdjson_inline simdjson_result<uint64_t> get_uint64_in_string() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64() noexcept;
simdjson_inline simdjson_result<int64_t> get_int64_in_string() noexcept;
simdjson_inline simdjson_result<uint32_t> get_uint32() noexcept;
simdjson_inline simdjson_result<int32_t> get_int32() noexcept;
simdjson_inline simdjson_result<double> get_double() noexcept;
simdjson_inline simdjson_result<double> get_double_in_string() noexcept;
simdjson_inline simdjson_result<std::string_view> get_string(bool allow_replacement = false) noexcept;
@@ -19,7 +19,12 @@ class document;
* 3) The stream_final mode allows us to truncate final
* unterminated strings. It is useful in conjunction with streaming_partial.
*/
enum class stage1_mode { regular, streaming_partial, streaming_final};
enum class stage1_mode {
regular,
streaming_partial, streaming_final,
json_sequence_partial, json_sequence_final,
comma_delimited_partial, comma_delimited_final
};
/**
* Returns true if mode == streaming_partial or mode == streaming_final
@@ -31,7 +36,6 @@ inline bool is_streaming(stage1_mode mode) {
// return (mode == stage1_mode::streaming_partial || mode == stage1_mode::streaming_final);
}
namespace internal {
@@ -212,6 +216,12 @@ protected:
*/
size_t _max_depth{0};
public:
/** Whether to store big integers as strings instead of returning BIGINT_ERROR */
bool _number_as_string{false};
protected:
// Declaring these so that subclasses can use them to implement their constructors.
simdjson_inline dom_parser_implementation() noexcept;
simdjson_inline dom_parser_implementation(dom_parser_implementation &&other) noexcept;
+2 -1
View File
@@ -19,7 +19,8 @@ enum class tape_type {
DOUBLE = 'd',
TRUE_VALUE = 't',
FALSE_VALUE = 'f',
NULL_VALUE = 'n'
NULL_VALUE = 'n',
BIGINT = 'Z' // Big integer stored as string in string buffer
}; // enum class tape_type
} // namespace internal
+207 -4
View File
@@ -10,6 +10,19 @@
#include <climits>
#include <cwchar>
#if SIMDJSON_HAS_UNISTD_H
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
// On Windows, `padded_memory_map` (when it is enabled) depends on types and
// functions declared in <windows.h>. We deliberately do NOT include that
// header here: users of simdjson who want `padded_memory_map` on Windows
// must include <windows.h> themselves *before* including this header. See
// padded_string.h for the detection logic.
namespace simdjson {
namespace internal {
@@ -160,10 +173,13 @@ inline padded_string::operator padded_string_view() const noexcept simdjson_life
}
inline simdjson_result<padded_string> padded_string::load(std::string_view filename) noexcept {
// std::string_view is not guaranteed to be null-terminated, but std::fopen requires
// a null-terminated C string. Construct a temporary std::string to ensure null-termination.
const std::string null_terminated_filename(filename);
// Open the file
SIMDJSON_PUSH_DISABLE_WARNINGS
SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe
std::FILE *fp = std::fopen(filename.data(), "rb");
std::FILE *fp = std::fopen(null_terminated_filename.c_str(), "rb");
SIMDJSON_POP_DISABLE_WARNINGS
if (fp == nullptr) {
@@ -215,10 +231,13 @@ inline simdjson_result<padded_string> padded_string::load(std::string_view filen
#if defined(_WIN32) && SIMDJSON_CPLUSPLUS17
inline simdjson_result<padded_string> padded_string::load(std::wstring_view filename) noexcept {
// std::wstring_view is not guaranteed to be null-terminated, but _wfopen requires
// a null-terminated wide C string. Construct a temporary std::wstring to ensure null-termination.
const std::wstring null_terminated_filename(filename);
// Open the file using the wide characters
SIMDJSON_PUSH_DISABLE_WARNINGS
SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe
std::FILE *fp = _wfopen(filename.data(), L"rb");
std::FILE *fp = _wfopen(null_terminated_filename.c_str(), L"rb");
SIMDJSON_POP_DISABLE_WARNINGS
if (fp == nullptr) {
@@ -341,6 +360,9 @@ inline padded_string padded_string_builder::convert() noexcept {
}
inline bool padded_string_builder::reserve(size_t additional) noexcept {
if (simdjson_unlikely(additional + size < size)) {
return false; // overflow: cannot satisfy request
}
size_t needed = size + additional;
if (needed <= capacity) {
return true;
@@ -350,8 +372,9 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
// repeated allocations.
if (new_capacity < 4096) {
new_capacity *= 2;
} else {
new_capacity += new_capacity/2; // grow by 1.5x
// overflow guard: ensure new_capacity + new_capacity/2 does not overflow
} else if (new_capacity + new_capacity / 2 > new_capacity) {
new_capacity += new_capacity / 2; // grow by 1.5x
}
char *new_data = internal::allocate_padded_buffer(new_capacity);
if (new_data == nullptr) {
@@ -366,6 +389,185 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept {
return true;
}
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
#if SIMDJSON_HAS_UNISTD_H
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
int fd = open(filename, O_RDONLY);
if (fd == -1) {
return; // file not found or cannot be opened, data will be nullptr
}
struct stat st;
if (fstat(fd, &st) == -1) {
close(fd);
return; // failed to get file size, data will be nullptr
}
size = static_cast<size_t>(st.st_size);
size_t total_size = size + simdjson::SIMDJSON_PADDING;
void *anon_map =
mmap(NULL, total_size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (anon_map == MAP_FAILED) {
close(fd);
return; // failed to create anonymous mapping, data will be nullptr
}
void *file_map =
mmap(anon_map, size, PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0);
if (file_map == MAP_FAILED) {
munmap(anon_map, total_size);
close(fd);
return; // failed to mmap file, data will be nullptr
}
data = static_cast<const char *>(file_map);
close(fd); // no longer needed after mapping
}
simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
if (data != nullptr) {
munmap(const_cast<char *>(data), size + simdjson::SIMDJSON_PADDING);
}
}
#elif defined(_WIN32)
// Windows zero-copy implementation using placeholder virtual memory.
//
// We use the modern Windows memory APIs (VirtualAlloc2, CreateFileMapping2,
// MapViewOfFile3 — available since Windows 10 1803) to map the file into a
// contiguous virtual address range that includes at least SIMDJSON_PADDING
// zero bytes after the file content, with no data copies.
//
// Strategy:
// 1. If rounding the file size up to the allocation granularity already
// exceeds file_size + SIMDJSON_PADDING, the OS page zero-fill provides
// the padding and we use a simple MapViewOfFile3 call.
// 2. Otherwise we reserve a contiguous placeholder region via VirtualAlloc2,
// split it at the granularity-aligned file boundary, map the file into
// the first part, and commit zero pages for the second part (padding).
simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept {
HANDLE file_handle = ::CreateFileA(
filename, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return;
}
LARGE_INTEGER file_size_li;
if (!::GetFileSizeEx(file_handle, &file_size_li) || file_size_li.QuadPart < 0) {
::CloseHandle(file_handle);
return;
}
#if SIMDJSON_IS_32BITS
if (static_cast<unsigned long long>(file_size_li.QuadPart) >
static_cast<unsigned long long>(SIZE_MAX - simdjson::SIMDJSON_PADDING)) {
::CloseHandle(file_handle);
return;
}
#endif
size = static_cast<size_t>(file_size_li.QuadPart);
if (size == 0) {
::CloseHandle(file_handle);
return;
}
HANDLE section = ::CreateFileMapping2(
file_handle, NULL, FILE_MAP_READ, PAGE_READONLY,
0, 0, NULL, NULL, 0);
::CloseHandle(file_handle);
if (section == NULL) {
return;
}
SYSTEM_INFO si;
::GetSystemInfo(&si);
const size_t granularity = static_cast<size_t>(si.dwAllocationGranularity);
const size_t file_region = (size + granularity - 1) & ~(granularity - 1);
const size_t total_needed = size + simdjson::SIMDJSON_PADDING;
if (file_region >= total_needed) {
// The zero-fill in the last page already covers the padding.
PVOID view = ::MapViewOfFile3(
section, ::GetCurrentProcess(), NULL, 0, 0,
0, PAGE_READONLY, NULL, 0);
::CloseHandle(section);
if (view != NULL) {
data = static_cast<const char *>(view);
}
return;
}
// We need extra zero pages beyond the file region. Use the placeholder API
// to get a contiguous virtual address range spanning both the file mapping
// and the zero-filled padding.
const size_t padding_region =
((total_needed - file_region) + granularity - 1) & ~(granularity - 1);
const size_t reserve_size = file_region + padding_region;
// Reserve a contiguous placeholder.
PVOID placeholder = ::VirtualAlloc2(
::GetCurrentProcess(), NULL, reserve_size,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, NULL, 0);
if (placeholder == NULL) {
::CloseHandle(section);
return;
}
// Split into two placeholders at the file_region boundary.
if (!::VirtualFree(placeholder, file_region,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
::VirtualFree(placeholder, 0, MEM_RELEASE);
::CloseHandle(section);
return;
}
// Map the file into the first placeholder.
PVOID file_view = ::MapViewOfFile3(
section, ::GetCurrentProcess(), placeholder, 0, file_region,
MEM_REPLACE_PLACEHOLDER, PAGE_READONLY, NULL, 0);
::CloseHandle(section);
if (file_view == NULL) {
::VirtualFree(placeholder, 0, MEM_RELEASE);
::VirtualFree(static_cast<char *>(placeholder) + file_region,
0, MEM_RELEASE);
return;
}
// Commit zero pages in the second placeholder (the padding).
void *pad = static_cast<char *>(placeholder) + file_region;
PVOID padding_ptr = ::VirtualAlloc2(
::GetCurrentProcess(), pad, padding_region,
MEM_REPLACE_PLACEHOLDER | MEM_COMMIT, PAGE_READONLY, NULL, 0);
if (padding_ptr == NULL) {
::UnmapViewOfFile(file_view);
::VirtualFree(pad, 0, MEM_RELEASE);
return;
}
data = static_cast<const char *>(file_view);
padding_view_ = padding_ptr;
}
simdjson_inline padded_memory_map::~padded_memory_map() noexcept {
if (data == nullptr) { return; }
::UnmapViewOfFile(data);
if (padding_view_ != nullptr) {
::VirtualFree(padding_view_, 0, MEM_RELEASE);
}
}
#endif // POSIX or _WIN32
simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound {
if(!is_valid()) {
return simdjson::padded_string_view(); // return an empty view if mapping failed
}
return simdjson::padded_string_view(data, size, size + simdjson::SIMDJSON_PADDING);
}
simdjson_inline bool padded_memory_map::is_valid() const noexcept {
return data != nullptr;
}
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
} // namespace simdjson
inline simdjson::padded_string operator ""_padded(const char *str, size_t len) {
@@ -376,4 +578,5 @@ inline simdjson::padded_string operator ""_padded(const char8_t *str, size_t len
return simdjson::padded_string(reinterpret_cast<const char *>(str), len);
}
#endif
#endif // SIMDJSON_PADDED_STRING_INL_H
+79
View File
@@ -277,6 +277,85 @@ inline std::ostream& operator<<(std::ostream& out, const padded_string& s) { ret
inline std::ostream& operator<<(std::ostream& out, simdjson_result<padded_string> &s) noexcept(false) { return out << s.value(); }
#endif
#if SIMDJSON_HAS_PADDED_MEMORY_MAP
/**
* A class representing a memory-mapped file with padding.
*
* On POSIX systems (Linux, macOS, BSD, ...), this uses `mmap` to map the file
* contents directly into memory, which is efficient for large files (no copy).
*
* On Windows, this class is disabled by default and must be opted into at
* build time by defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`. When
* enabled, `<windows.h>` must also be included before `<simdjson.h>` and
* the compilation must target Windows 10, version 1803 or later. The
* Windows implementation uses the modern memory APIs (`VirtualAlloc2`,
* `CreateFileMapping2`, `MapViewOfFile3`) with the placeholder virtual
* memory mechanism to always achieve true zero-copy mapping with
* contiguous zero-filled padding.
*
* Either way, the resulting `padded_string_view` carries at least
* `SIMDJSON_PADDING` bytes of accessible zero-filled padding after the file
* content, so it can be consumed directly by the simdjson parsers (including
* `parse_many` / `iterate_many`).
*/
class padded_memory_map {
public:
/**
* Create a new padded memory map for the given file.
* After creating the memory map, you can call view() to get a padded_string_view of the file content.
* The memory map will be automatically released when the padded_memory_map instance is destroyed.
* On POSIX systems, the file content is not copied, so this is efficient for large files.
* On Windows, the file is mapped into memory via `MapViewOfFile3` (zero-copy).
* In all cases, the file must remain unchanged while the memory map is in use.
* In case of error (e.g., file not found, permission denied, etc.), the memory map will be
* invalid and view() will return an empty view.
* You can check if the memory map is valid by calling is_valid() before using view().
*
* @param filename the path to the file to memory-map.
*/
simdjson_inline padded_memory_map(const char *filename) noexcept;
/**
* Destroy the padded memory map and release any resources.
*/
simdjson_inline ~padded_memory_map() noexcept;
// lifetime of the view is tied to the memory map, so we can return a view
// directly
/**
* Get a view of the memory-mapped file. It always succeeds, but the view may be empty
* if the memory map is invalid (e.g., due to file not found, permission denied, etc.).
* You can check if the memory map is valid by calling is_valid() before using the view.
*
* Lifetime of the view is tied to the memory map, so the view should not be used after the
* padded_memory_map instance is destroyed.
*
* @return a padded_string_view representing the memory-mapped file, or an empty view if the memory map is invalid.
*/
simdjson_inline simdjson::padded_string_view view() const noexcept simdjson_lifetime_bound;
/**
* Check if the memory map is valid.
*
* @return true if the memory map is valid, false otherwise.
*/
simdjson_inline bool is_valid() const noexcept;
private:
padded_memory_map() = delete;
padded_memory_map(const padded_memory_map &) = delete;
padded_memory_map &operator=(const padded_memory_map &) = delete;
const char *data{nullptr};
size_t size{0};
#ifdef _WIN32
// When the file ends near an allocation-granularity boundary, we use the
// placeholder API to append zero-filled padding pages. This pointer tracks
// that region so the destructor can release it with VirtualFree.
void *padding_view_{nullptr};
#endif
};
#endif // SIMDJSON_HAS_PADDED_MEMORY_MAP
} // namespace simdjson
// This is deliberately outside of simdjson so that people get it without having to use the namespace
+85
View File
@@ -6,6 +6,15 @@
#include <cstring> /* memcmp */
// for page size computation.
#if SIMDJSON_HAS_UNISTD_H
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/sysctl.h>
#endif
#endif
namespace simdjson {
inline padded_string_view::padded_string_view(const char* s, size_t len, size_t capacity) noexcept
@@ -95,7 +104,83 @@ inline padded_string_view pad_with_reserve(std::string& s) noexcept {
return padded_string_view(s.data(), s.size(), s.capacity());
}
inline uint32_t get_page_size() noexcept {
#if defined(_WINDOWS_) // if and only if someone loaded Windows.h, we can get the page size from there.
// Otherwise, we assume 4096.
static const uint32_t cached = []() -> uint32_t {
SYSTEM_INFO si;
GetSystemInfo(&si);
return static_cast<std::uint32_t>(si.dwPageSize);
}();
return cached;
#elif SIMDJSON_HAS_UNISTD_H
static const uint32_t cached = []() -> uint32_t {
long page_size = sysconf(_SC_PAGESIZE);
if (page_size > 0) {
return static_cast<uint32_t>(page_size);
}
return 4096; // fallback
}();
return cached;
#else
return 4096; // fallback
#endif
}
#if SIMDJSON_CPLUSPLUS17
inline padded_input::padded_input(std::string_view sv)
: storage(simdjson::padded_string_view{}) {
if (needs_allocation(sv.data(), sv.size())) {
storage = simdjson::padded_string(sv);
} else {
storage = simdjson::padded_string_view(
sv.data(), sv.size(), sv.size() + simdjson::SIMDJSON_PADDING);
}
}
inline padded_input::padded_input(const char *data, size_t length)
: storage(simdjson::padded_string_view{}) {
if (needs_allocation(data, length)) {
storage = simdjson::padded_string(data, length);
} else {
storage = simdjson::padded_string_view(
data, length, length + simdjson::SIMDJSON_PADDING);
}
}
inline padded_input::padded_input(const std::string &s)
: storage(simdjson::padded_string_view{}) {
const size_t len = s.size();
const size_t cap = s.capacity();
// Here we have the string content from data() to data() + size(),
// but the memory is accessible from data() to data() + capacity().
const size_t needed_padding = (cap - len) < simdjson::SIMDJSON_PADDING
? simdjson::SIMDJSON_PADDING - (cap - len) : 0;
if (needed_padding > 0 && needs_allocation(s.data(), cap, needed_padding)) {
storage = simdjson::padded_string(s);
} else {
storage = simdjson::padded_string_view(
s.data(), len, len + simdjson::SIMDJSON_PADDING);
}
}
inline bool padded_input::is_view() const noexcept {
return std::holds_alternative<simdjson::padded_string_view>(storage);
}
inline padded_input::operator simdjson::padded_string_view() const noexcept {
return std::visit([](const auto& p) -> simdjson::padded_string_view {
return p;
}, storage);
}
inline bool padded_input::needs_allocation(const char* buf, size_t len, size_t padding) noexcept {
if(len == 0) { return false; }
const auto page_size = get_page_size();
return ((reinterpret_cast<uintptr_t>(buf + len - 1) % page_size)
+ padding >= static_cast<uintptr_t>(page_size));
}
#endif // SIMDJSON_CPLUSPLUS17
} // namespace simdjson
+67 -1
View File
@@ -9,6 +9,9 @@
#include <memory>
#include <string>
#include <ostream>
#if SIMDJSON_CPLUSPLUS17
#include <variant>
#endif
namespace simdjson {
@@ -17,7 +20,7 @@ namespace simdjson {
*/
class padded_string_view : public std::string_view {
private:
size_t _capacity;
size_t _capacity{0};
public:
/** Create an empty padded_string_view. */
@@ -73,6 +76,68 @@ public:
}; // padded_string_view
/**
* Get the system's memory page size. By default, we return
* 4096 bytes, which is the most common page size. On systems
* where the page size is not a multiple of 4096 bytes, and not
* a unix-like system, nor Windows, this function may return an
* incorrect value.
*
* @return The page size in bytes.
*/
inline uint32_t get_page_size() noexcept;
#if SIMDJSON_CPLUSPLUS17
/**
* A padded_input is a wrapper around either a padded_string_view or a padded_string.
* It will automatically pad a string_view if it does not have sufficient padding
* up to the end of the memory page. Note that a requirement for this method to
* make sense is to be on a system with a page size of at least 4096 (which is
* universal except on some embedded systems).
*/
struct padded_input {
/**
* Construct a padded_input from a string_view. If the string_view does not have sufficient padding,
* the data will be copied into a padded_string and the padded_string_view will point to the
* padded_string's data. Otherwise, the padded_string_view will point to the original string_view's data.
*/
inline explicit padded_input(std::string_view sv);
/**
* Construct a padded_input from a C-style string (length specified). If the string does not have sufficient padding,
* the data will be copied into a padded_string and the padded_string_view will point to the
* padded_string's data. Otherwise, the padded_string_view will point to the original string's data.
*/
inline explicit padded_input(const char *data, size_t length);
/**
* Construct a padded_input from a std::string. If the string does not have sufficient padding
* (considering its capacity), the data will be copied into a padded_string and the padded_string_view
* will point to the padded_string's data. Otherwise, the padded_string_view will point to the
* original string's data.
*/
inline explicit padded_input(const std::string &s);
/**
* Check if the padded_input is a view.
*
* @return true if the padded_input is a view, false otherwise.
*/
inline bool is_view() const noexcept;
/**
* Convert the padded_input to a padded_string_view.
*
* @return The padded_string_view.
*/
inline operator simdjson::padded_string_view() const noexcept;
private:
std::variant<simdjson::padded_string_view, simdjson::padded_string> storage;
// whether we cross a page boundary and need to allocate a new padded string.
static inline bool needs_allocation(const char* buf, size_t len, size_t padding = SIMDJSON_PADDING) noexcept;
};
#endif // SIMDJSON_CPLUSPLUS17
#if SIMDJSON_EXCEPTIONS
/**
* Send padded_string instance to an output stream.
@@ -105,6 +170,7 @@ inline padded_string_view pad(std::string& s) noexcept;
* @return The padded string.
*/
inline padded_string_view pad_with_reserve(std::string& s) noexcept;
} // namespace simdjson
#endif // SIMDJSON_PADDED_STRING_VIEW_H
+67
View File
@@ -66,6 +66,25 @@ using std::size_t;
#define SIMDJSON_IS_LASX 1 // We can always run both
#elif defined(__loongarch_sx)
#define SIMDJSON_IS_LSX 1
// Adjust for runtime dispatching support.
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__NVCOMPILER)
#if __GNUC__ > 15 || (__GNUC__ == 15 && __GNUC_MINOR__ >= 0)
// We are ok, we will support runtime dispatch for LASX.
#else
// We disable runtime dispatch for LASX, which means that we will not be able to use LASX
// even if it is supported by the hardware.
// Loongson users should update to GCC 15 or better.
#define SIMDJSON_IMPLEMENTATION_LASX 0
#endif
#else
// We are not using GCC, so we assume that we can support runtime dispatch for LASX.
// https://godbolt.org/z/jcMnrjYhs
#define SIMDJSON_IMPLEMENTATION_LASX 0
#endif
#endif
#elif defined(__PPC64__) || defined(_M_PPC64)
#define SIMDJSON_IS_PPC64 1
@@ -266,5 +285,53 @@ using std::size_t;
#endif
#endif
#ifndef SIMDJSON_HAS_UNISTD_H
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#define SIMDJSON_HAS_UNISTD_H 1
#else
#define SIMDJSON_HAS_UNISTD_H 0
#endif
#endif
// padded_memory_map availability.
//
// On POSIX platforms the class is always available: the implementation uses
// `mmap` (and a trailing anonymous page for padding) from <sys/mman.h>.
//
// On Windows the class is disabled by default and must be explicitly
// opted into by defining `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS=1`. Enabling
// it requires:
// 1. `<windows.h>` has been included *before* `<simdjson.h>` (so that
// this header can see the Win32 types and the `_WINDOWS_` include
// guard),
// 2. the compilation targets Windows 10, version 1803 or later
// (i.e. `NTDDI_VERSION >= NTDDI_WIN10_RS4`, `0x0A000005`). This is
// required because the implementation relies on the modern memory
// APIs introduced with that version (`CreateFileMapping2` /
// `MapViewOfFile3`),
// 3. the link step pulls in an import library that exports those APIs,
// typically `onecore.lib` (or `mincore.lib`).
//
// The `SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS` CMake option arranges (1)-(3)
// automatically when building simdjson with its own CMake. Consumers using
// simdjson as a pre-built library are responsible for setting the macro,
// the Windows version macros, and the link library themselves.
//
// If the opt-in conditions are not met on Windows, `padded_memory_map`
// simply does not exist — any attempt to use it fails at compile time
// with an "unknown identifier" diagnostic rather than silently degrading.
//
// The SIMDJSON_HAS_PADDED_MEMORY_MAP macro reflects whether the class is
// available in the current translation unit. Users may test this macro to
// conditionally compile code that depends on padded_memory_map.
#ifndef SIMDJSON_HAS_PADDED_MEMORY_MAP
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#elif defined(_WINDOWS_) && defined(SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS) && SIMDJSON_ENABLE_MEMORY_FILE_MAPPING_ON_WINDOWS
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 1
#else
#define SIMDJSON_HAS_PADDED_MEMORY_MAP 0
#endif
#endif
#endif // SIMDJSON_PORTABILITY_H
+3 -3
View File
@@ -4,7 +4,7 @@
#define SIMDJSON_SIMDJSON_VERSION_H
/** The version of simdjson being used (major.minor.revision) */
#define SIMDJSON_VERSION "4.2.4"
#define SIMDJSON_VERSION "4.6.1"
namespace simdjson {
enum {
@@ -15,11 +15,11 @@ enum {
/**
* The minor version (major.MINOR.revision) of simdjson being used.
*/
SIMDJSON_VERSION_MINOR = 2,
SIMDJSON_VERSION_MINOR = 6,
/**
* The revision (major.minor.REVISION) of simdjson being used.
*/
SIMDJSON_VERSION_REVISION = 4
SIMDJSON_VERSION_REVISION = 1
};
} // namespace simdjson
+11
View File
@@ -0,0 +1,11 @@
{
"app_name": "MyApp",
"version": "1.0.0",
"port": 8080,
"debug": true,
"features": ["logging", "caching"],
"database": {
"host": "localhost",
"port": 5432
}
}
+54 -3
View File
@@ -268,7 +268,21 @@ class SimdjsonFile:
print(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.
assert include.is_conditional_include, f"Error: Amalgamated file '{self}' is trying to include '{include}', but '{include}' is not an amalgamated file. Amalgamated files can only include other amalgamated files to maintain conditional inclusion structure. Check the inclusion rules in the script's 'rules' variable. {rules}"
if not include.is_conditional_include:
dep = self.dependency_file
dep_hint = f" and add it to the dependency file '{dep}'" if dep else ""
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}"
)
# 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."
@@ -425,8 +439,10 @@ class Amalgamator:
self.implementation = "SIMDJSON_BUILTIN_IMPLEMENTATION"
assert not self.editor_only_region, f"Error: Already in an editor-only region when starting to write '{file}'. Ensure proper nesting of conditional blocks."
editor_only_start_line = None
bare_endif_lines = []
with open(file.absolute_path, 'r') as fid2:
for line in fid2:
for line_number, line in enumerate(fid2, 1):
line = line.rstrip('\n')
# Ignore #pragma once, it causes warnings if it ends up in a .cpp file
@@ -439,6 +455,7 @@ class Amalgamator:
assert self.in_conditional_include_block, f"Error: File '{file}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' without a prior '#define SIMDJSON_CONDITIONAL_INCLUDE'. Ensure the define comes first. Stack: {self.include_stack}. {rules}"
assert not self.editor_only_region, f"Error: File '{file}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' twice in a row. Ensure conditional blocks are properly nested and closed. {rules}"
self.editor_only_region = True
editor_only_start_line = line_number
# Handle ignored lines (and ending ignore blocks)
end_ignore = endif_conditional_re.search(line)
@@ -453,6 +470,12 @@ class Amalgamator:
file.add_editor_only_include(included_file)
if end_ignore:
self.editor_only_region = False
editor_only_start_line = None
else:
# Track bare #endif lines that might be the intended closer
stripped = line.strip()
if stripped == '#endif' or (stripped.startswith('#endif') and 'SIMDJSON_CONDITIONAL_INCLUDE' not in stripped):
bare_endif_lines.append((line_number, line.strip()))
continue
assert not end_ignore, f"Error: File '{file}' has '#endif // SIMDJSON_CONDITIONAL_INCLUDE' without a matching '#ifndef'. Ensure proper conditional block structure. {rules}"
@@ -501,7 +524,35 @@ class Amalgamator:
self.write(line)
assert not self.editor_only_region, f"Error: File '{file}' ended without closing the '#endif // SIMDJSON_CONDITIONAL_INCLUDE'. Ensure all conditional blocks are properly closed. {rules}"
if self.editor_only_region:
msg = (
f"Error: File '{file}' ended without closing the "
f"'#endif // SIMDJSON_CONDITIONAL_INCLUDE' block "
f"(opened at line {editor_only_start_line}).\n\n"
)
if bare_endif_lines:
msg += (
f"HINT: Found #endif line(s) inside the block that are missing "
f"the required comment. The amalgamation script looks for exactly:\n\n"
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
f"but found:\n"
)
for ln, text in bare_endif_lines:
msg += f" line {ln}: {text}\n"
msg += (
f"\nFIX: Change the #endif to:\n\n"
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
f"The '// SIMDJSON_CONDITIONAL_INCLUDE' comment is required "
f"for the amalgamation script to recognize it as the closing "
f"of the conditional block.\n"
)
else:
msg += (
f"FIX: Add '#endif // SIMDJSON_CONDITIONAL_INCLUDE' to close "
f"the '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block.\n"
)
msg += f"\n{rules}"
raise AssertionError(msg)
self.write(f"/* end file {self.file_to_str(file)} */")
+7070 -95
View File
File diff suppressed because it is too large Load Diff
+26205 -593
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+58 -2
View File
@@ -226,9 +226,15 @@ simdjson_warn_unused simdjson_inline error_code scan() {
add_structural();
// Primitive or invalid character (invalid characters will be checked in stage 2)
} else {
// Anything else, add the structural and go until we find the next one
// Anything else, add the structural and go until we find the next one.
// We also stop on RS (0x1E) so that RFC 7464 json_sequence inputs
// like `\x1e"a"\x1e"b"` produce a separate structural for each RS
// rather than being absorbed into a single primitive run. RS is a
// control character that is invalid in normal JSON, so breaking
// the run here has no effect on well-formed non-json_sequence
// inputs.
add_structural();
while (idx+1<len && !char_is_space_or_operator(buf[idx+1])) {
while (idx+1<len && !char_is_space_or_operator(buf[idx+1]) && buf[idx+1] != 0x1e) {
idx++;
};
}
@@ -283,6 +289,56 @@ simdjson_warn_unused simdjson_inline error_code scan() {
// doing.
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
if (parser.n_structural_indexes == 0) { return EMPTY; }
} else if (partial == stage1_mode::json_sequence_partial) {
// RFC 7464: use RS positions for batch boundaries
if(unclosed_string) {
parser.n_structural_indexes--;
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return CAPACITY; }
}
uint32_t next_batch_start = uint32_t(len);
auto new_structural_indexes = find_next_document_index_json_sequence(parser, len, false, next_batch_start);
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
return CAPACITY;
}
if (new_structural_indexes == 0) {
parser.n_structural_indexes = 0;
return EMPTY;
}
parser.n_structural_indexes = new_structural_indexes;
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
} else if (partial == stage1_mode::json_sequence_final) {
// RFC 7464: final batch, last document extends to EOF
if(unclosed_string) { parser.n_structural_indexes--; }
uint32_t next_batch_start = uint32_t(len);
parser.n_structural_indexes = find_next_document_index_json_sequence(parser, len, true, next_batch_start);
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return EMPTY; }
} else if (partial == stage1_mode::comma_delimited_partial) {
// Comma-delimited: filter root-level commas, use comma positions for batch boundaries
if(unclosed_string) {
parser.n_structural_indexes--;
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return CAPACITY; }
}
uint32_t next_batch_start = uint32_t(len);
auto new_structural_indexes = filter_comma_delimited(parser, len, false, next_batch_start);
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
return CAPACITY;
}
if (new_structural_indexes == 0) {
parser.n_structural_indexes = 0;
return EMPTY;
}
parser.n_structural_indexes = new_structural_indexes;
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
} else if (partial == stage1_mode::comma_delimited_final) {
// Comma-delimited: final batch, last document extends to EOF
if(unclosed_string) { parser.n_structural_indexes--; }
uint32_t next_batch_start = uint32_t(len);
parser.n_structural_indexes = filter_comma_delimited(parser, len, true, next_batch_start);
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return EMPTY; }
} else if(unclosed_string) { error = UNCLOSED_STRING; }
return error;
}
+272 -1
View File
@@ -97,9 +97,280 @@ simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &par
return 0;
}
/**
* Sentinel value returned to indicate a document started but didn't fit
* (CAPACITY error), as opposed to 0 which means no document content found
* (EMPTY).
*/
constexpr uint32_t DOCUMENT_TOO_LARGE = UINT32_MAX;
/**
* For RFC 7464 JSON text sequences, filter RS from structural indexes and
* find batch boundaries.
*
* In JSON sequence mode, RS (0x1E) marks the start of each JSON text.
* RS bytes appear in structural_indexes as they are classified as scalars.
* This function:
* 1. Scans structural_indexes to find and count RS positions
* 2. Filters RS out of structural_indexes in-place
* 3. Determines batch boundaries based on RS positions
*
* @param parser The parser with structural_indexes and buf.
* @param len The length of the current batch buffer.
* @param is_final True if this is the final batch (no more data coming).
* @param next_batch_start Output: offset where the next batch should start.
* @return The number of structural indexes to keep (after RS filtering),
* 0 if no document content found (EMPTY),
* or DOCUMENT_TOO_LARGE if a document started but didn't fit (CAPACITY).
*/
simdjson_inline uint32_t find_next_document_index_json_sequence(
dom_parser_implementation &parser,
size_t len,
bool is_final,
uint32_t &next_batch_start) {
// Default: next batch starts at end of buffer
next_batch_start = uint32_t(len);
if (parser.n_structural_indexes == 0) { return 0; }
// Phase 1: Scan structural_indexes to find RS positions and handle them.
// RS marks the start of a JSON text. For objects/arrays, the '{' or '[' after RS
// is already in structural_indexes (it's an operator). For scalars like numbers,
// the digit following RS is NOT in structural_indexes because the scanner sees
// RS as a scalar, making the digit a scalar continuation, not a start.
// We must: (1) remove RS from structural_indexes, and (2) for scalars, add the
// actual value start position.
uint32_t write_idx = 0;
uint32_t last_rs_pos = 0;
uint32_t rs_count = 0;
for (uint32_t read_idx = 0; read_idx < parser.n_structural_indexes; read_idx++) {
const uint32_t pos = parser.structural_indexes[read_idx];
if (parser.buf[pos] == 0x1E) {
// This is an RS character - find the actual JSON value start.
last_rs_pos = pos;
rs_count++;
// Skip past this RS and any whitespace *and any additional RSes*
// to locate the real value. Consecutive RSes are degenerate
// "empty records" per RFC 7464; we collapse them here. They do
// not always appear as separate entries in structural_indexes
// because the scanner groups runs of adjacent non-whitespace
// scalar bytes (including RS) into a single scalar start.
uint32_t value_start = pos + 1;
while (value_start < len) {
const uint8_t c = parser.buf[value_start];
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
value_start++;
} else if (c == 0x1E) {
// Collapsed empty record. Still count it so rs_count reflects
// the true number of record markers and last_rs_pos tracks
// the final one.
last_rs_pos = value_start;
rs_count++;
value_start++;
} else {
break;
}
}
// If the scanner emitted additional structurals inside the
// whitespace+RS run we just walked over (i.e., isolated RSes
// separated by whitespace), skip past them so we do not
// double-count or double-emit.
while (read_idx + 1 < parser.n_structural_indexes &&
parser.structural_indexes[read_idx + 1] < value_start) {
read_idx++;
}
// Check if the value start is an operator (always present in
// scanner structural_indexes) or a scalar-like start (which may
// be missing from structural_indexes and must be added here).
// Note: '"' is NOT always in structural_indexes. The scanner
// classifies '"' as a scalar character and emits it as a
// structural only when it is a *scalar start* (preceded by
// whitespace or an operator). When '"' immediately follows an
// RS (which the scanner also classifies as scalar), it is
// treated as a scalar continuation and not emitted - so we
// must add it here just like any other scalar value.
if (value_start < len) {
const uint8_t c = parser.buf[value_start];
const bool is_operator =
(c == '{' || c == '}' || c == '[' || c == ']' ||
c == ':' || c == ',');
// If the next scanner structural is exactly at value_start,
// the scanner already emitted it (it followed whitespace) and
// we must not add a duplicate - a subsequent iteration will
// copy it into write_idx.
const bool already_emitted =
(read_idx + 1 < parser.n_structural_indexes &&
parser.structural_indexes[read_idx + 1] == value_start);
if (!is_operator && !already_emitted) {
// Scalar value (number/true/false/null/string) - add its
// position since scanner missed it.
parser.structural_indexes[write_idx++] = value_start;
}
}
} else {
// Not RS, copy to output
parser.structural_indexes[write_idx++] = pos;
}
}
// Update structural index count
parser.n_structural_indexes = write_idx;
if (parser.n_structural_indexes == 0) { return 0; }
if (rs_count == 0) {
// No RS found; for final batch, try generic boundary detection
return is_final ? find_next_document_index(parser) : 0;
}
// Phase 2: Determine batch boundaries based on RS positions
if (is_final) {
// Final batch: all documents are complete (last one ends at EOF).
// In json_sequence mode, RS markers define document boundaries, so all
// remaining structurals form complete documents. Return them all directly.
// (Calling find_next_document_index() would fail for scalar documents.)
return parser.n_structural_indexes;
}
// Partial batch: need to find complete documents only.
// A document starting at an RS is complete if there is another RS after it.
next_batch_start = last_rs_pos;
if (rs_count < 2) {
// Only one RS, so we have at most one document that may be incomplete.
// We cannot confirm it is complete without another RS.
// Return DOCUMENT_TOO_LARGE if content was found (write_idx > 0), 0 if only separators.
return (parser.n_structural_indexes > 0) ? DOCUMENT_TOO_LARGE : 0;
}
// We have at least 2 RS markers. The last complete document ends before last_rs_pos.
// Find the structural index cutoff: keep only structurals < last_rs_pos.
// Since we already filtered RS, all remaining structurals are valid.
// We iterate backward to find the last structural before last_rs_pos.
uint32_t keep_count = 0;
for (uint32_t i = parser.n_structural_indexes; i > 0; i--) {
if (parser.structural_indexes[i - 1] < last_rs_pos) {
keep_count = i;
break;
}
}
// No structurals before the last RS - no complete documents
if (keep_count == 0) { return 0; }
// All documents before the last RS are complete by definition (the next RS
// confirms their end). No need to call find_next_document_index() which
// would fail for scalar documents like `1` or `"hello"`.
return keep_count;
}
/**
* Filter comma-delimited documents by removing root-level commas from
* structural indexes.
*
* For comma-delimited format like `{...},{...},{...}`, we need to remove
* the commas that separate documents (depth 0) while preserving commas
* inside arrays and objects (depth > 0).
*
* After filtering, the structural indexes look like whitespace-delimited
* documents, so find_next_document_index() works unchanged.
*
* @param parser The parser with structural_indexes and buf.
* @param len The length of the current batch buffer.
* @param is_final True if this is the final batch (no more data coming).
* @param next_batch_start Output: offset where the next batch should start.
* @return The number of structural indexes to keep,
* 0 if no document content found (EMPTY),
* or DOCUMENT_TOO_LARGE if a document started but didn't fit (CAPACITY).
*/
simdjson_inline uint32_t filter_comma_delimited(
dom_parser_implementation &parser,
size_t len,
bool is_final,
uint32_t &next_batch_start) {
// Default: next batch starts at end of buffer
next_batch_start = uint32_t(len);
if (parser.n_structural_indexes == 0) { return 0; }
// Track depth to identify root-level commas (depth 0)
int depth = 0;
uint32_t write_idx = 0;
uint32_t last_root_comma_pos = 0;
uint32_t root_comma_count = 0;
for (uint32_t i = 0; i < parser.n_structural_indexes; i++) {
uint32_t idx = parser.structural_indexes[i];
uint8_t c = parser.buf[idx];
switch (c) {
case '{': case '[':
depth++;
parser.structural_indexes[write_idx++] = idx;
break;
case '}': case ']':
depth--;
parser.structural_indexes[write_idx++] = idx;
break;
case ',':
if (depth == 0) {
// Root-level comma = document boundary, skip it
last_root_comma_pos = idx;
root_comma_count++;
continue;
}
parser.structural_indexes[write_idx++] = idx;
break;
default:
// Colons, scalars, etc.
parser.structural_indexes[write_idx++] = idx;
break;
}
}
// Update structural index count
parser.n_structural_indexes = write_idx;
if (parser.n_structural_indexes == 0) { return 0; }
if (is_final) {
// Final batch: use standard boundary detection on filtered indexes
return find_next_document_index(parser);
}
// Partial batch: need to find complete documents only.
// A document ending with a root comma is complete.
if (root_comma_count == 0) {
// No root commas found; we cannot confirm any document is complete.
// The whole batch might be one incomplete document.
// Return DOCUMENT_TOO_LARGE if content was found (write_idx > 0), 0 if only commas.
return (parser.n_structural_indexes > 0) ? DOCUMENT_TOO_LARGE : 0;
}
// We have at least one root comma. Documents before the last comma are complete.
next_batch_start = last_root_comma_pos + 1;
// Find the structural index cutoff: keep only structurals < last_root_comma_pos
uint32_t keep_count = 0;
for (uint32_t i = parser.n_structural_indexes; i > 0; i--) {
if (parser.structural_indexes[i - 1] < last_root_comma_pos) {
keep_count = i;
break;
}
}
if (keep_count == 0) { return 0; }
// Use standard boundary detection on the complete portion
parser.n_structural_indexes = keep_count;
return find_next_document_index(parser);
}
} // namespace stage1
} // unnamed namespace
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SRC_GENERIC_STAGE1_FIND_NEXT_DOCUMENT_INDEX_H
#endif // SIMDJSON_SRC_GENERIC_STAGE1_FIND_NEXT_DOCUMENT_INDEX_H
+50 -1
View File
@@ -314,7 +314,6 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa
return EMPTY;
}
}
parser.n_structural_indexes = new_structural_indexes;
} else if (partial == stage1_mode::streaming_final) {
if(have_unclosed_string) { parser.n_structural_indexes--; }
@@ -342,6 +341,56 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa
// the trailing garbage.
return EMPTY;
}
} else if (partial == stage1_mode::json_sequence_partial) {
// RFC 7464: use RS positions for batch boundaries
if(have_unclosed_string) {
parser.n_structural_indexes--;
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; }
}
uint32_t next_batch_start = uint32_t(len);
auto new_structural_indexes = find_next_document_index_json_sequence(parser, len, false, next_batch_start);
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
return CAPACITY;
}
if (new_structural_indexes == 0) {
parser.n_structural_indexes = 0;
return EMPTY;
}
parser.n_structural_indexes = new_structural_indexes;
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
} else if (partial == stage1_mode::json_sequence_final) {
// RFC 7464: final batch, last document extends to EOF
if(have_unclosed_string) { parser.n_structural_indexes--; }
uint32_t next_batch_start = uint32_t(len);
parser.n_structural_indexes = find_next_document_index_json_sequence(parser, len, true, next_batch_start);
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return EMPTY; }
} else if (partial == stage1_mode::comma_delimited_partial) {
// Comma-delimited: filter root-level commas, use comma positions for batch boundaries
if(have_unclosed_string) {
parser.n_structural_indexes--;
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; }
}
uint32_t next_batch_start = uint32_t(len);
auto new_structural_indexes = filter_comma_delimited(parser, len, false, next_batch_start);
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
return CAPACITY;
}
if (new_structural_indexes == 0) {
parser.n_structural_indexes = 0;
return EMPTY;
}
parser.n_structural_indexes = new_structural_indexes;
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
} else if (partial == stage1_mode::comma_delimited_final) {
// Comma-delimited: final batch, last document extends to EOF
if(have_unclosed_string) { parser.n_structural_indexes--; }
uint32_t next_batch_start = uint32_t(len);
parser.n_structural_indexes = filter_comma_delimited(parser, len, true, next_batch_start);
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return EMPTY; }
}
checker.check_eof();
return checker.errors();
+17 -1
View File
@@ -172,7 +172,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept {
iter.log_value("number");
return numberparsing::parse_number(value, tape);
error_code err = numberparsing::parse_number(value, tape);
if (simdjson_unlikely(err == BIGINT_ERROR &&
iter.dom_parser._number_as_string)) {
// Write big integer to string buffer using the same format as strings.
// Scan digits the same way parse_number does (skip optional '-', then digits).
const uint8_t *p = value;
if (*p == '-') p++;
while (numberparsing::is_digit(*p)) p++;
size_t len = size_t(p - value);
tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT);
uint8_t *dst = current_string_buf_loc + sizeof(uint32_t);
memcpy(dst, value, len);
dst += len;
on_end_string(dst);
return SUCCESS;
}
return err;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept {
+15
View File
@@ -26,6 +26,9 @@ struct tape_writer {
/** Write a double value to tape. */
simdjson_inline void append_double(double value) noexcept;
/** Write a big integer (as string) to tape. src points to first digit, len is byte count. */
simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept;
/**
* Append a tape entry (an 8-bit type,and 56 bits worth of value).
*/
@@ -109,6 +112,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern
tape_loc = val | ((uint64_t(char(t))) << 56);
}
simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept {
// Write to string buffer: [4-byte LE length][digits][null]
uint32_t str_len = uint32_t(len);
memcpy(string_buf, &str_len, sizeof(uint32_t));
memcpy(string_buf + sizeof(uint32_t), src, len);
string_buf[sizeof(uint32_t) + len] = 0;
// Tape entry: offset into string buffer
// The caller must set the offset relative to doc.string_buf base
append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches
string_buf += sizeof(uint32_t) + len + 1;
}
} // namespace stage2
} // unnamed namespace
} // namespace SIMDJSON_IMPLEMENTATION
+5 -1
View File
@@ -4,6 +4,10 @@ include(${PROJECT_SOURCE_DIR}/cmake/add_cpp_test.cmake)
add_subdirectory(dom)
add_subdirectory(ondemand)
# compilation_failure_tests is added before the global link_libraries(simdjson) so that
# multiple_include/myexe does not receive a duplicate simdjson entry alongside the one
# already propagated by mylib PUBLIC simdjson::simdjson.
add_subdirectory(compilation_failure_tests)
# All remaining tests link with simdjson proper
link_libraries(simdjson)
@@ -11,6 +15,7 @@ link_libraries(simdjson)
add_cpp_test(unicode_tests LABELS dom acceptance per_implementation)
add_cpp_test(minify_tests LABELS other acceptance per_implementation)
add_cpp_test(padded_string_tests LABELS other acceptance )
add_cpp_test(memory_map_tests LABELS other acceptance )
add_cpp_test(prettify_tests LABELS other acceptance per_implementation)
add_cpp_test(fractured_json_tests LABELS other acceptance per_implementation)
@@ -26,6 +31,5 @@ endif()
# SIMDJSON_FORCE_IMPLEMENTATION, so we know we're testing what we think we're testing
add_cpp_test(checkimplementation LABELS other per_implementation)
add_subdirectory(compilation_failure_tests)
add_subdirectory(builder)
add_subdirectory(compile_time)
@@ -6,40 +6,40 @@
using namespace simdjson;
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<double> tire_pressure;
std::string make{};
std::string model{};
int64_t year{};
std::vector<double> tire_pressure{};
};
struct kid {
int age;
std::string name;
std::vector<std::string> toys;
int age{};
std::string name{};
std::vector<std::string> toys{};
bool operator<=> (const kid&) const = default;
};
struct Z {
int x;
int x{};
bool operator<=> (const Z&) const = default;
};
struct Y {
int g;
std::string h;
std::vector<int> i;
Z z;
int g{};
std::string h{};
std::vector<int> i{};
Z z{};
bool operator<=> (const Y&) const = default;
};
struct X {
char a;
int b;
int c;
std::string d;
std::vector<int> e;
std::vector<std::string> f;
Y y;
char a{};
int b{};
int c{};
std::string d{};
std::vector<int> e{};
std::vector<std::string> f{};
Y y{};
bool operator<=> (const X&) const = default;
};
@@ -16,8 +16,8 @@ namespace builder_tests {
#if SIMDJSON_STATIC_REFLECTION
// Custom type for testing tag_invoke with extract_into
struct Price {
double amount;
std::string currency;
double amount{};
std::string currency{};
// Custom deserializer that applies currency conversion
friend error_code tag_invoke(deserialize_tag,
@@ -46,17 +46,37 @@ namespace builder_tests {
return SUCCESS;
}
};
struct Car {
std::string make{};
std::string model{};
int year{};
double price{};
bool electric{};
};
struct CarOpt {
std::string make;
std::string model;
int year;
double price;
std::optional<std::string> color;
};
struct CarColor {
std::string make;
std::string model;
int year;
double price;
std::string color;
};
struct Product {
std::string name;
Price price; // Custom deserializable type
int stock;
std::string name{};
Price price{}; // Custom deserializable type
int stock{};
};
// Another custom type for testing
struct Dimensions {
double value;
std::string unit;
double value{};
std::string unit{};
// Custom deserializer that converts to metric
friend error_code tag_invoke(deserialize_tag,
@@ -89,6 +109,13 @@ namespace builder_tests {
Dimensions weight;
Dimensions length;
};
struct Person {
std::string name;
int age;
std::optional<std::string> email;
std::optional<std::string> phone;
};
#endif
bool test_primitive_types() {
@@ -132,8 +159,8 @@ namespace builder_tests {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct StringTypes {
std::string string_val;
std::string_view string_view_val;
std::string string_val{};
std::string_view string_view_val{};
};
StringTypes test{"hello world", "test_view"};
@@ -159,10 +186,10 @@ namespace builder_tests {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct OptionalTypes {
std::optional<int> opt_int_with_value;
std::optional<std::string> opt_string_with_value;
std::optional<int> opt_int_null;
std::optional<std::string> opt_string_null;
std::optional<int> opt_int_with_value{};
std::optional<std::string> opt_string_with_value{};
std::optional<int> opt_int_null{};
std::optional<std::string> opt_string_null{};
};
OptionalTypes test;
@@ -200,11 +227,11 @@ namespace builder_tests {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct SmartPointerTypes {
std::unique_ptr<int> unique_int_with_value;
std::shared_ptr<std::string> shared_string_with_value;
std::unique_ptr<bool> unique_bool_with_value;
std::unique_ptr<int> unique_int_null;
std::shared_ptr<std::string> shared_string_null;
std::unique_ptr<int> unique_int_with_value{};
std::shared_ptr<std::string> shared_string_with_value{};
std::unique_ptr<bool> unique_bool_with_value{};
std::unique_ptr<int> unique_int_null{};
std::shared_ptr<std::string> shared_string_null{};
};
SmartPointerTypes test;
@@ -245,9 +272,9 @@ namespace builder_tests {
#if SIMDJSON_STATIC_REFLECTION
// Test basic container types
struct ContainerTypes {
std::vector<int> int_vector;
std::set<std::string> string_set;
std::map<std::string, int> string_map;
std::vector<int> int_vector{};
std::set<std::string> string_set{};
std::map<std::string, int> string_map{};
};
ContainerTypes test;
@@ -278,8 +305,8 @@ namespace builder_tests {
// Test std::list with iterator-based serialization
struct ListContainer {
std::list<int> int_list;
std::list<std::string> string_list;
std::list<int> int_list{};
std::list<std::string> string_list{};
};
ListContainer list_test;
@@ -319,13 +346,7 @@ namespace builder_tests {
bool test_extract_into() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct Car {
std::string make;
std::string model;
int year;
double price;
std::optional<std::string> color;
};
ondemand::parser parser;
@@ -341,7 +362,7 @@ namespace builder_tests {
"transmission": "Automatic"
})"_padded;
Car car{};
CarOpt car{};
ondemand::document doc;
ASSERT_SUCCESS( parser.iterate(padded).get(doc) );
@@ -377,7 +398,7 @@ namespace builder_tests {
"color": "Red"
})"_padded;
Car car{};
CarOpt car{};
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(padded).get(doc));
@@ -402,7 +423,7 @@ namespace builder_tests {
"price": 35999.99
})"_padded;
Car car{};
CarOpt car{};
ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(padded).get(doc));
@@ -495,13 +516,6 @@ namespace builder_tests {
#if SIMDJSON_STATIC_REFLECTION
// Test 1: Extract specific fields from Car struct
{
struct Car {
std::string make;
std::string model;
int year;
double price;
bool electric;
};
Car car{"Tesla", "Model 3", 2023, 42000.0, true};
@@ -533,13 +547,7 @@ namespace builder_tests {
// Test 2: Extract different field combination
{
struct Car {
std::string make;
std::string model;
int year;
double price;
bool electric;
};
Car car{"Ford", "F-150", 2024, 55000.0, false};
@@ -566,13 +574,6 @@ namespace builder_tests {
// Test 3: Extract from struct with optional fields
{
struct Person {
std::string name;
int age;
std::optional<std::string> email;
std::optional<std::string> phone;
};
Person person{"John Doe", 30, "john@example.com", std::nullopt};
// Extract name and email
@@ -594,13 +595,6 @@ namespace builder_tests {
// Test 4: Extract with optional that has value
{
struct Person {
std::string name;
int age;
std::optional<std::string> email;
std::optional<std::string> phone;
};
Person person{"Jane Smith", 25, "jane@example.com", "555-1234"};
// Extract name, age, and phone
@@ -629,21 +623,21 @@ namespace builder_tests {
// Test 5: Round-trip test - serialize with extract_from, deserialize with extract_into
{
struct Product {
std::string id;
std::string name;
double price;
int stock;
struct MyProduct {
std::string id{};
std::string name{};
double price{};
int stock{};
};
Product original{"P123", "Widget", 19.99, 100};
MyProduct original{"P123", "Widget", 19.99, 100};
// Extract specific fields to JSON
std::string json;
ASSERT_SUCCESS((extract_from<"id", "name", "price">(original).get(json)));
// Parse and extract back
Product restored{"", "", 0.0, 0};
MyProduct restored{"", "", 0.0, 0};
auto padded = pad(json);
ondemand::parser parser;
auto doc = parser.iterate(padded);
@@ -11,10 +11,10 @@ using namespace simdjson;
// Suppose that we want to serialize/deserialize Car using
// strings for the year
struct Car {
std::string make;
std::string model;
int64_t year;
std::vector<float> tire_pressure;
std::string make{};
std::string model{};
int64_t year{};
std::vector<float> tire_pressure{};
};
@@ -14,11 +14,11 @@ namespace builder_tests {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct EmptyValues {
std::string empty_string;
std::vector<int> empty_vector;
std::optional<int> null_optional;
std::unique_ptr<int> null_unique_ptr;
std::shared_ptr<std::string> null_shared_ptr;
std::string empty_string{};
std::vector<int> empty_vector{};
std::optional<int> null_optional{};
std::unique_ptr<int> null_unique_ptr{};
std::shared_ptr<std::string> null_shared_ptr{};
};
EmptyValues test;
@@ -56,18 +56,18 @@ namespace builder_tests {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct SpecialChars {
std::string quotes;
std::string backslashes;
std::string newlines;
std::string unicode;
char null_char;
std::string quotes{};
std::string backslashes{};
std::string newlines{};
std::string unicode{};
char null_char{};
};
SpecialChars test;
test.quotes = "He said \"Hello\"";
test.backslashes = "Path\\to\\file";
test.newlines = "Line1\nLine2\tTabbed";
test.unicode = "Café résumé";
test.unicode = "Caf\xc3\xa9 r\xc3\xa9sum\xc3\xa9";
test.null_char = '\0';
std::string json;
@@ -82,10 +82,10 @@ namespace builder_tests {
// Test round-trip (excluding null char which has special handling)
struct SpecialCharsNoNull {
std::string quotes;
std::string backslashes;
std::string newlines;
std::string unicode;
std::string quotes{};
std::string backslashes{};
std::string newlines{};
std::string unicode{};
};
SpecialCharsNoNull test_no_null;
@@ -157,15 +157,15 @@ namespace builder_tests {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct Inner {
int value;
std::string name;
int value{};
std::string name{};
};
struct Outer {
Inner inner_obj;
std::vector<Inner> inner_vector;
std::optional<Inner> optional_inner;
std::unique_ptr<Inner> unique_inner;
Inner inner_obj{};
std::vector<Inner> inner_vector{};
std::optional<Inner> optional_inner{};
std::unique_ptr<Inner> unique_inner{};
};
Outer test;
@@ -213,4 +213,4 @@ namespace builder_tests {
int main(int argc, char *argv[]) {
return test_main(argc, argv, builder_tests::run);
}
}
@@ -51,8 +51,8 @@ namespace builder_tests {
};
struct StatusStruct {
Status status;
std::string name;
Status status{};
std::string name{};
};
// Test deserialization of different enum values with string representation
@@ -104,9 +104,9 @@ namespace builder_tests {
};
struct Task {
Priority priority;
std::string description;
int id;
Priority priority{};
std::string description{};
int id{};
};
Task original{Priority::High, "Important task", 123};
@@ -144,8 +144,8 @@ namespace builder_tests {
};
struct Response {
ErrorCode error;
std::string message;
ErrorCode error{};
std::string message{};
};
Response test{ErrorCode::NotFound, "Resource not found"};
@@ -8,26 +8,26 @@ using namespace simdjson;
// Test structures for FracturedJson builder integration
struct SimpleUser {
int id;
std::string name;
bool active;
int id{};
std::string name{};
bool active{};
};
struct UserWithEmail {
int id;
std::string name;
std::string email;
bool active;
int id{};
std::string name{};
std::string email{};
bool active{};
};
struct NestedData {
std::string title;
std::vector<SimpleUser> users;
int count;
std::string title{};
std::vector<SimpleUser> users{};
int count{};
};
struct TableTestData {
std::vector<SimpleUser> records;
std::vector<SimpleUser> records{};
};
namespace fractured_json_builder_tests {

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