Compare commits
134
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
209e822121 | ||
|
|
f889ec4dcc | ||
|
|
961c9c604f | ||
|
|
168d74f6eb | ||
|
|
7d774a7352 | ||
|
|
1bb6e69470 | ||
|
|
08ba1d9567 | ||
|
|
6f70f7e9ac | ||
|
|
e101ce560a | ||
|
|
f7398914e1 | ||
|
|
1e0f30e97c | ||
|
|
c16e6cbde2 | ||
|
|
66cc256498 | ||
|
|
5867cfb64f | ||
|
|
226700373a | ||
|
|
acf4ace49e | ||
|
|
133283560f | ||
|
|
9e40ddc3ea | ||
|
|
5170bf7be9 | ||
|
|
6a785afaad | ||
|
|
0f30daf63e | ||
|
|
b5c18ddc3a | ||
|
|
894b51aff1 | ||
|
|
ba674e04c3 | ||
|
|
fa25d1b9a6 | ||
|
|
099522f84f | ||
|
|
2e3f9ab33a | ||
|
|
00fe86e762 | ||
|
|
3d79688ce6 | ||
|
|
282db9452b | ||
|
|
0a0bb57db5 | ||
|
|
45566375ee | ||
|
|
14a45389fb | ||
|
|
58ba8e061f | ||
|
|
1b8d714a60 | ||
|
|
f80ebdaae5 | ||
|
|
bf4893313d | ||
|
|
940b5f9235 | ||
|
|
4ac39d3bec | ||
|
|
7e5b0fda26 | ||
|
|
ac3ab28e29 | ||
|
|
bd30ac1927 | ||
|
|
d1cd64fbc9 | ||
|
|
367258b8a2 | ||
|
|
7b5d5568a9 | ||
|
|
1efb1c1ca7 | ||
|
|
6c3de264bc | ||
|
|
a071cbd819 | ||
|
|
ee408a1ce4 | ||
|
|
04af28bd8e | ||
|
|
a263596f1b | ||
|
|
adaa1273f4 | ||
|
|
4939f8d667 | ||
|
|
4cdc02de4b | ||
|
|
a23d11d50a | ||
|
|
bac7daaed7 | ||
|
|
c17f985fe0 | ||
|
|
86c9dce9e8 | ||
|
|
0c1b54e1d0 | ||
|
|
20aba28d3b | ||
|
|
02feaec64d | ||
|
|
2bfa54a4b9 | ||
|
|
a45c421cc5 | ||
|
|
9cd968698c | ||
|
|
c1e2638b6d | ||
|
|
17e92520c6 | ||
|
|
b86a47cf3d | ||
|
|
01dc1e7807 | ||
|
|
ba177f3cb2 | ||
|
|
ef20a69c3a | ||
|
|
4683158d4b | ||
|
|
8c642402fa | ||
|
|
1f3bae087f | ||
|
|
109cab33c1 | ||
|
|
32a4b0feb3 | ||
|
|
7b66cdb9d8 | ||
|
|
be59fb2a57 | ||
|
|
321965e0ad | ||
|
|
a8cc0a1379 | ||
|
|
e7eceb7869 | ||
|
|
b2fb4dfcbd | ||
|
|
32d759564b | ||
|
|
40503139ea | ||
|
|
5fc53651cb | ||
|
|
4c31665346 | ||
|
|
ee19cddf3b | ||
|
|
809063e9f2 | ||
|
|
e3ddc9869e | ||
|
|
1b3cb9df75 | ||
|
|
7b755737b4 | ||
|
|
f2582daa62 | ||
|
|
01f45b844d | ||
|
|
412ef7419a | ||
|
|
a183f99d21 | ||
|
|
7c4bf1eb0d | ||
|
|
2a6d1f376f | ||
|
|
e62fc0ba93 | ||
|
|
9cc0ff977e | ||
|
|
a1d74c55a7 | ||
|
|
f60b822476 | ||
|
|
760352de24 | ||
|
|
5948b89182 | ||
|
|
383b27eefc | ||
|
|
f8895c72a9 | ||
|
|
badbe685a5 | ||
|
|
a43bad747a | ||
|
|
e7fc853e2d | ||
|
|
4cc906e567 | ||
|
|
99250c8275 | ||
|
|
6d3f0dae96 | ||
|
|
bdb6966416 | ||
|
|
fbc0cb1ce6 | ||
|
|
efe00e5b30 | ||
|
|
d14408a527 | ||
|
|
b701e94ec3 | ||
|
|
6f48fe3f9e | ||
|
|
7ed8cd593a | ||
|
|
26d51dbb64 | ||
|
|
188971008b | ||
|
|
8ea9131d76 | ||
|
|
4ba9da2b1d | ||
|
|
c8fb14213d | ||
|
|
a64cb5e006 | ||
|
|
76d8513668 | ||
|
|
037d245df8 | ||
|
|
2b5f4fb96e | ||
|
|
8c28ef250e | ||
|
|
19c845aa82 | ||
|
|
a80897e895 | ||
|
|
ab1cc82bd1 | ||
|
|
302ffe5c13 | ||
|
|
32d3083585 | ||
|
|
efbabe66fa | ||
|
|
481a9a216f |
No files matched your search
+3
-10
@@ -108,16 +108,9 @@ The tests are located in [`tests/src/unit-*.cpp`](https://github.com/nlohmann/js
|
||||
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
||||
context which existing file needs to be extended, and only very few cases require creating new test files.
|
||||
|
||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue. Tests that need C++17 or
|
||||
C++20 go into `unit-regression3-cpp17.cpp` or `unit-regression3-cpp20.cpp` instead, so the large file is not rebuilt
|
||||
for every C++ standard. `unit-regression2.cpp` holds the older tests; the two files exist because a single one grew
|
||||
large enough for the MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing
|
||||
the larger one.
|
||||
|
||||
To keep compile times down, several test files are compiled together as one translation unit. This sets a few rules
|
||||
for test files, such as giving file-scope helpers file-specific names. See the README in the
|
||||
[`tests`](https://github.com/nlohmann/json/tree/develop/tests) folder for how the tests are built and what to keep in
|
||||
mind when adding them.
|
||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||
|
||||
#### Exceptions
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
path: pr/
|
||||
|
||||
check:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MAIN_DIR: ${{ github.workspace }}/main
|
||||
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
|
||||
@@ -77,12 +77,13 @@ jobs:
|
||||
cd $MAIN_DIR
|
||||
|
||||
# Built from this PR's own tools/macro_builder/main.cpp, not a
|
||||
# develop checkout: unlike amalgamate.py, this tool has no other
|
||||
# source of truth to check against (its own README documents that
|
||||
# it must reproduce macro_scope.hpp byte for byte), so there is
|
||||
# nothing to gain from checking out a separate copy, and doing so
|
||||
# would make this step fail on a PR that adds support for a new
|
||||
# dispatch table until that PR itself merges to develop.
|
||||
# develop checkout: unlike amalgamate.py and generate_natvis.py,
|
||||
# this tool has no other source of truth to check against (its
|
||||
# own README documents that it must reproduce macro_scope.hpp
|
||||
# byte for byte), so there is nothing to gain from checking out a
|
||||
# separate copy, and doing so would make this step fail on a PR
|
||||
# that adds support for a new dispatch table until that PR itself
|
||||
# merges to develop.
|
||||
TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX)
|
||||
c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder"
|
||||
"$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp"
|
||||
@@ -145,14 +146,14 @@ jobs:
|
||||
- name: Regenerate nlohmann_json.natvis
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
# The generator and its template are what this check verifies the
|
||||
# committed natvis file against (which namespaces it lists, and
|
||||
# what each entry looks like), so run this PR's copy rather than
|
||||
# the develop checkout's: with develop's, a PR that changes either
|
||||
# could never pass. generate_natvis.py loads the template from its
|
||||
# own directory. Like the macro_builder step above, this only runs
|
||||
# PR content in a job without credentials.
|
||||
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
||||
# The template is what this check verifies the committed natvis
|
||||
# file against, so take it from this PR rather than from the
|
||||
# develop checkout: generate_natvis.py loads the template from its
|
||||
# own directory, and with develop's copy a PR that changes the
|
||||
# template could never pass. Like the macro_builder step above,
|
||||
# this only renders PR content in a job without credentials.
|
||||
cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/
|
||||
python3 $NATVIS_TOOL_DIR/generate_natvis.py $MAIN_DIR
|
||||
|
||||
- name: Build patch and check for differences
|
||||
id: diff
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
|
||||
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
name: Scan
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_gcc
|
||||
|
||||
ci_meson_install:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
cmake --build build-import-split
|
||||
|
||||
ci_infer:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
|
||||
ci_static_analysis_ubuntu:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_test_coverage:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -430,7 +430,7 @@ jobs:
|
||||
run: cmake --build build --target ci_nvhpc
|
||||
|
||||
ci_emscripten:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -450,7 +450,7 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
|
||||
+6
-6
@@ -361,10 +361,10 @@ add_custom_target(ci_test_coverage
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage json.info.filtered.noexcept
|
||||
|
||||
COMMENT "Compile and test with coverage"
|
||||
)
|
||||
@@ -890,11 +890,11 @@ add_custom_target(ci_icpx
|
||||
# The following tests are excluded as they trigger known nvc++ 25.5 defects (not
|
||||
# library bugs); see https://github.com/nlohmann/json for tracking. Only the
|
||||
# affected language-standard variants are excluded so coverage is otherwise kept:
|
||||
# - test-comparison-cpp20_cpp20, test-comparison_legacy-cpp20_cpp20
|
||||
# - test-comparison_cpp20, test-comparison_legacy_cpp20
|
||||
# miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
|
||||
# - test-constructor1_cpp11
|
||||
# std::initializer_list lifetime bug -> SIGSEGV
|
||||
# - test-deserialization-cpp20_cpp20
|
||||
# - test-deserialization_cpp20
|
||||
# mangles the UTF-8 u8"" string literal in the char8_t (C++20) section
|
||||
add_custom_target(ci_nvhpc
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
@@ -906,7 +906,7 @@ add_custom_target(ci_nvhpc
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
||||
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
||||
# instead of treating them as shell pipe operators
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison-cpp20_cpp20\\|test-comparison_legacy-cpp20_cpp20\\|test-constructor1_cpp11\\|test-deserialization-cpp20_cpp20" --output-on-failure
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
||||
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
||||
)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
cppcheck==1.5.3
|
||||
cppcheck==1.5.2
|
||||
+39
-366
@@ -109,55 +109,6 @@ function(_json_test_apply_test_properties test_target properties_target)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# for internal use by _json_test_add_test() and _json_test_add_unity_batch():
|
||||
# registers the CTest test <test_name>_cpp<cxx_standard> (plus its Valgrind
|
||||
# variant), which runs the executable target <test_target> with the arguments
|
||||
# in ARGN, and applies the test properties of the test- and standard-specific
|
||||
# interface targets
|
||||
function(_json_test_register_test test_name test_target cxx_standard)
|
||||
set(ctest_name ${test_name}_cpp${cxx_standard})
|
||||
|
||||
if (JSON_FastTests)
|
||||
add_test(NAME ${ctest_name}
|
||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} ${ARGN}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
else()
|
||||
add_test(NAME ${ctest_name}
|
||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} ${ARGN} --no-skip
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
endif()
|
||||
set_tests_properties(${ctest_name} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
|
||||
|
||||
# apply standard-specific test properties
|
||||
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
||||
_json_test_apply_test_properties(${ctest_name} _json_test_interface__cpp_${cxx_standard})
|
||||
endif()
|
||||
|
||||
# apply test-specific test properties
|
||||
if(TARGET _json_test_interface_${test_name})
|
||||
_json_test_apply_test_properties(${ctest_name} _json_test_interface_${test_name})
|
||||
endif()
|
||||
|
||||
# apply test- and standard-specific test properties
|
||||
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
||||
_json_test_apply_test_properties(${ctest_name}
|
||||
_json_test_interface_${test_name}_cpp_${cxx_standard}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(JSON_Valgrind)
|
||||
add_test(NAME ${ctest_name}_valgrind
|
||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER} ${ARGN}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
set_tests_properties(${ctest_name}_valgrind PROPERTIES
|
||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# for internal use by json_test_add_test_for()
|
||||
function(_json_test_add_test test_name file main cxx_standard)
|
||||
set(test_target ${test_name}_cpp${cxx_standard})
|
||||
@@ -192,7 +143,45 @@ function(_json_test_add_test test_name file main cxx_standard)
|
||||
)
|
||||
endif()
|
||||
|
||||
_json_test_register_test(${test_name} ${test_target} ${cxx_standard})
|
||||
if (JSON_FastTests)
|
||||
add_test(NAME ${test_target}
|
||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
else()
|
||||
add_test(NAME ${test_target}
|
||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} --no-skip
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
endif()
|
||||
set_tests_properties(${test_target} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
|
||||
|
||||
# apply standard-specific test properties
|
||||
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
||||
_json_test_apply_test_properties(${test_target} _json_test_interface__cpp_${cxx_standard})
|
||||
endif()
|
||||
|
||||
# apply test-specific test properties
|
||||
if(TARGET _json_test_interface_${test_name})
|
||||
_json_test_apply_test_properties(${test_target} _json_test_interface_${test_name})
|
||||
endif()
|
||||
|
||||
# apply test- and standard-specific test properties
|
||||
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
||||
_json_test_apply_test_properties(${test_target}
|
||||
_json_test_interface_${test_name}_cpp_${cxx_standard}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(JSON_Valgrind)
|
||||
add_test(NAME ${test_target}_valgrind
|
||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
set_tests_properties(${test_target}_valgrind PROPERTIES
|
||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#############################################################################
|
||||
@@ -211,14 +200,6 @@ endfunction()
|
||||
# Use NAME <name> to override the filename-derived test name.
|
||||
# Use FORCE to create the test regardless of the file containing
|
||||
# JSON_HAS_CPP_<version_number>.
|
||||
#
|
||||
# Tests that depend on the C++ standard (e.g., because they use the macros
|
||||
# JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
# should not make the whole of a large unit-foo.cpp be rebuilt for every
|
||||
# standard. Put them into a separate file unit-foo-cpp<NN>.cpp (see, e.g.,
|
||||
# unit-items-cpp17.cpp) which wraps its content in #ifdef JSON_HAS_CPP_<NN>.
|
||||
# Then, unit-foo.cpp itself contains no JSON_HAS_CPP_<NN> and is only built for
|
||||
# C++11.
|
||||
# Test targets are linked against <main>.
|
||||
# CXX_STANDARDS defaults to "11".
|
||||
#############################################################################
|
||||
@@ -262,314 +243,6 @@ function(json_test_add_test_for file)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# for internal use by json_test_add_unity_tests(): sets <result> to whether the
|
||||
# (absolute) <file> is built for <cxx_standard>; same rule as in
|
||||
# json_test_add_test_for(): C++11 always, others only if the file contains
|
||||
# JSON_HAS_CPP_<cxx_standard> or <force> is set
|
||||
function(_json_test_unity_applies file cxx_standard force result)
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
if(NOT ("${cxx_standard}" STREQUAL 11 OR force))
|
||||
file(READ ${file} file_content)
|
||||
string(FIND "${file_content}" JSON_HAS_CPP_${cxx_standard} has_cpp_found)
|
||||
if(${has_cpp_found} EQUAL -1)
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# for internal use by json_test_add_unity_tests(): creates the executable
|
||||
# test-unity-<batch_name>_cpp<cxx_standard> from the (absolute) source files
|
||||
# in ARGN, which are #include-d by a generated source file, and registers one
|
||||
# CTest test per source file that runs only the test cases of that file; if
|
||||
# <private> is true, the generated file defines JSON_TESTS_PRIVATE first
|
||||
function(_json_test_add_unity_batch batch_name cxx_standard main private)
|
||||
set(batch_target test-unity-${batch_name}_cpp${cxx_standard})
|
||||
set(batch_source ${PROJECT_BINARY_DIR}/tests/unity/${batch_target}.cpp)
|
||||
|
||||
set(batch_content "// generated by cmake/test.cmake; do not edit\n")
|
||||
if(private)
|
||||
string(APPEND batch_content "// at least one file of this batch needs access to private members of the library\n")
|
||||
string(APPEND batch_content "#define JSON_TESTS_PRIVATE\n")
|
||||
# the files define the macro again; mark this definition as used, as
|
||||
# -Wunused-macros reports an unused definition when it is redefined
|
||||
string(APPEND batch_content "#ifdef JSON_TESTS_PRIVATE\n#endif\n")
|
||||
endif()
|
||||
foreach(file ${ARGN})
|
||||
string(APPEND batch_content "#include \"${file}\" // NOLINT(bugprone-suspicious-include)\n")
|
||||
endforeach()
|
||||
|
||||
# only touch the generated file if it changed to keep incremental builds incremental
|
||||
set(old_content "")
|
||||
if(EXISTS ${batch_source})
|
||||
file(READ ${batch_source} old_content)
|
||||
endif()
|
||||
if(NOT "${old_content}" STREQUAL "${batch_content}")
|
||||
file(WRITE ${batch_source} "${batch_content}")
|
||||
endif()
|
||||
|
||||
add_executable(${batch_target} ${batch_source})
|
||||
target_link_libraries(${batch_target} PRIVATE ${main})
|
||||
set_target_properties(${batch_target} PROPERTIES
|
||||
CXX_STANDARD ${cxx_standard}
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
||||
target_link_libraries(${batch_target} PRIVATE _json_test_interface__cpp_${cxx_standard})
|
||||
endif()
|
||||
|
||||
# rebuild the batch when one of its files changes, and show the files in IDEs;
|
||||
# files that are also built standalone (VARIANT_FILES) are left out, as
|
||||
# HEADER_FILE_ONLY is a per-file property and would also affect those targets
|
||||
set_source_files_properties(${batch_source} PROPERTIES OBJECT_DEPENDS "${ARGN}")
|
||||
foreach(file ${ARGN})
|
||||
if(NOT file IN_LIST _json_test_unity_variant_files)
|
||||
set_source_files_properties(${file} PROPERTIES HEADER_FILE_ONLY ON)
|
||||
target_sources(${batch_target} PRIVATE ${file})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(file ${ARGN})
|
||||
get_filename_component(file_basename ${file} NAME_WE)
|
||||
string(REGEX REPLACE "unit-(.+)" "test-\\1" test_name ${file_basename})
|
||||
|
||||
# run only the test cases defined in this file (and in the shared
|
||||
# make_test_data_available.hpp if the file uses it)
|
||||
file(READ ${file} file_content)
|
||||
set(source_filter "--source-file=*${file_basename}.cpp")
|
||||
string(FIND "${file_content}" make_test_data_available.hpp uses_test_data)
|
||||
if(NOT ${uses_test_data} EQUAL -1)
|
||||
string(APPEND source_filter ",*make_test_data_available.hpp")
|
||||
endif()
|
||||
|
||||
_json_test_register_test(${test_name} ${batch_target} ${cxx_standard} "${source_filter}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
#############################################################################
|
||||
# json_test_add_unity_tests(
|
||||
# FILES <files>...
|
||||
# MAIN <main>
|
||||
# [CXX_STANDARDS <version_number>...] [FORCE]
|
||||
# [BATCH_SIZE <size>]
|
||||
# [GROUPS <group>...]
|
||||
# [VARIANT_FILES <files>...])
|
||||
#
|
||||
# Like calling json_test_add_test_for(<file> MAIN <main> ...) for each of the
|
||||
# <files>, but compiles several files together to speed up the build: for each
|
||||
# C++ standard, the files are split into batches of <size> files (default: 8)
|
||||
# and each batch is built as a single executable
|
||||
#
|
||||
# test-unity-<pool><index>_cpp<version_number>
|
||||
#
|
||||
# whose generated source file #include-s the files of the batch (so they share
|
||||
# the template instantiations of the library). The tests are still named
|
||||
# test-foo_cpp<version_number>, one per file, but run the batch executable with
|
||||
# a doctest filter that selects the test cases of that file only.
|
||||
#
|
||||
# Files are only batched with files that agree on the macros defined before
|
||||
# the library is included: files that define at most JSON_TESTS_PRIVATE (or
|
||||
# macros derived from global compile definitions) form the pools "plain" and
|
||||
# "private". All other files are added with json_test_add_test_for() as usual,
|
||||
# as are files with test-specific build settings (see
|
||||
# json_test_set_test_options()) and the files listed in the explicit
|
||||
# exclusion list below.
|
||||
# Each <group> names a list variable json_test_unity_group_<group> of test file
|
||||
# stems (file names without "unit-" and ".cpp"). The batchable files of a group
|
||||
# are compiled together (regardless of BATCH_SIZE) as one executable
|
||||
#
|
||||
# test-unity-<group>_cpp<version_number>
|
||||
#
|
||||
# so that related tests, which instantiate the same templates, share one
|
||||
# translation unit. A group may mix the pools "plain" and "private"; if any of
|
||||
# its files needs JSON_TESTS_PRIVATE, the whole group is built with it. Files
|
||||
# that cannot be batched stay standalone even if they are listed in a group.
|
||||
# Files in no group are batched by BATCH_SIZE as described above.
|
||||
# <files> in VARIANT_FILES are also built standalone with other settings, so
|
||||
# they are not marked as header-only sources of the batch.
|
||||
#############################################################################
|
||||
|
||||
function(json_test_add_unity_tests)
|
||||
cmake_parse_arguments(args "FORCE" "MAIN;BATCH_SIZE" "FILES;CXX_STANDARDS;VARIANT_FILES;GROUPS" ${ARGN})
|
||||
|
||||
if("${args_MAIN}" STREQUAL "")
|
||||
message(FATAL_ERROR "Required argument MAIN <main> missing.")
|
||||
endif()
|
||||
|
||||
if("${args_BATCH_SIZE}" STREQUAL "")
|
||||
set(args_BATCH_SIZE 8)
|
||||
endif()
|
||||
|
||||
if("${args_CXX_STANDARDS}" STREQUAL "")
|
||||
set(args_CXX_STANDARDS 11)
|
||||
endif()
|
||||
|
||||
if(args_FORCE)
|
||||
set(force FORCE)
|
||||
else()
|
||||
set(force "")
|
||||
endif()
|
||||
|
||||
set(_json_test_unity_variant_files "")
|
||||
foreach(file ${args_VARIANT_FILES})
|
||||
get_filename_component(file ${file} ABSOLUTE)
|
||||
list(APPEND _json_test_unity_variant_files ${file})
|
||||
endforeach()
|
||||
|
||||
# files that must not be merged into a batch: unit-32bit.cpp is only built
|
||||
# for 32bit targets, and unit-no-macro-leak.cpp checks that including the
|
||||
# library defines no unprefixed macro, which any other file would disturb
|
||||
set(standalone_files unit-32bit.cpp unit-no-macro-leak.cpp)
|
||||
|
||||
set(harmless_macros "^(DOCTEST_.*|SKIP_TESTS_FOR_.*|JSON_TEST_DEPRECATED_FUNCTIONS_DELETED|JSON_TEST_STRICT_NUL_HANDLING_ENABLED|JSON_TEST_STRINGIZE)$")
|
||||
|
||||
# classify the files
|
||||
set(plain_files "")
|
||||
set(private_files "")
|
||||
set(standalone_abs_files "")
|
||||
foreach(file ${args_FILES})
|
||||
get_filename_component(file_name ${file} NAME)
|
||||
get_filename_component(file_basename ${file} NAME_WE)
|
||||
string(REGEX REPLACE "unit-(.+)" "test-\\1" test_name ${file_basename})
|
||||
|
||||
set(batchable TRUE)
|
||||
if(file_name IN_LIST standalone_files)
|
||||
set(batchable FALSE)
|
||||
endif()
|
||||
if(TARGET _json_test_interface_${test_name})
|
||||
set(batchable FALSE)
|
||||
endif()
|
||||
foreach(cxx_standard ${args_CXX_STANDARDS})
|
||||
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
||||
set(batchable FALSE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# files that suppress GCC's -Wnoexcept around their include of the
|
||||
# library: the suppression has no effect once another file of the
|
||||
# batch included the library first
|
||||
if(batchable)
|
||||
file(READ ${file} file_content)
|
||||
string(FIND "${file_content}" "SUPPRESS_WARNING(\"-Wnoexcept\")" suppresses_noexcept)
|
||||
if(NOT ${suppresses_noexcept} EQUAL -1)
|
||||
set(batchable FALSE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(pool plain)
|
||||
if(batchable)
|
||||
# collect the macros (un)defined before the library is included
|
||||
file(READ ${file} file_content)
|
||||
string(FIND "${file_content}" "#include <nlohmann/" include_position)
|
||||
if(NOT ${include_position} EQUAL -1)
|
||||
string(SUBSTRING "${file_content}" 0 ${include_position} file_content)
|
||||
endif()
|
||||
string(REGEX MATCHALL "(^|\n)[ \t]*#[ \t]*(define|undef)[ \t]+[A-Za-z_0-9]+" directives "${file_content}")
|
||||
foreach(directive ${directives})
|
||||
string(REGEX REPLACE "^.*[ \t]([A-Za-z_0-9]+)$" "\\1" macro "${directive}")
|
||||
if(macro MATCHES "${harmless_macros}")
|
||||
continue()
|
||||
elseif("${macro}" STREQUAL JSON_TESTS_PRIVATE)
|
||||
set(pool private)
|
||||
else()
|
||||
set(batchable FALSE)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
get_filename_component(file_abs ${file} ABSOLUTE)
|
||||
if(NOT batchable)
|
||||
list(APPEND standalone_abs_files ${file_abs})
|
||||
json_test_add_test_for(${file} MAIN ${args_MAIN} CXX_STANDARDS ${args_CXX_STANDARDS} ${force})
|
||||
continue()
|
||||
endif()
|
||||
get_filename_component(file ${file} ABSOLUTE)
|
||||
list(APPEND ${pool}_files ${file})
|
||||
endforeach()
|
||||
|
||||
# resolve the explicit groups: group_<name>_files are the (absolute) files
|
||||
# of the group, group_<name>_private tells whether one of them is private
|
||||
set(grouped_files "")
|
||||
foreach(group ${args_GROUPS})
|
||||
if(NOT DEFINED json_test_unity_group_${group})
|
||||
message(FATAL_ERROR "Unity test group '${group}' is not defined (json_test_unity_group_${group}).")
|
||||
endif()
|
||||
set(group_${group}_files "")
|
||||
set(group_${group}_private FALSE)
|
||||
foreach(stem ${json_test_unity_group_${group}})
|
||||
# check against the source directory, because FILES may be filtered (JSON_TestShard)
|
||||
get_filename_component(file ${CMAKE_CURRENT_SOURCE_DIR}/src/unit-${stem}.cpp ABSOLUTE)
|
||||
if(NOT EXISTS ${file})
|
||||
message(FATAL_ERROR "Unity test group '${group}' lists '${stem}', but ${file} does not exist.")
|
||||
endif()
|
||||
if(file IN_LIST grouped_files)
|
||||
message(FATAL_ERROR "Unity test file unit-${stem}.cpp is listed in more than one group (second: '${group}').")
|
||||
endif()
|
||||
list(APPEND grouped_files ${file})
|
||||
|
||||
if(file IN_LIST standalone_abs_files)
|
||||
message(STATUS "Unity test group '${group}': unit-${stem}.cpp cannot be batched and stays standalone")
|
||||
elseif(file IN_LIST plain_files)
|
||||
list(APPEND group_${group}_files ${file})
|
||||
list(REMOVE_ITEM plain_files ${file})
|
||||
elseif(file IN_LIST private_files)
|
||||
list(APPEND group_${group}_files ${file})
|
||||
list(REMOVE_ITEM private_files ${file})
|
||||
set(group_${group}_private TRUE)
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
foreach(cxx_standard ${args_CXX_STANDARDS})
|
||||
if(NOT compiler_supports_cpp_${cxx_standard})
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# explicit groups: one batch per group
|
||||
foreach(group ${args_GROUPS})
|
||||
set(batch_files "")
|
||||
foreach(file ${group_${group}_files})
|
||||
_json_test_unity_applies(${file} ${cxx_standard} "${force}" applies)
|
||||
if(applies)
|
||||
list(APPEND batch_files ${file})
|
||||
endif()
|
||||
endforeach()
|
||||
if(batch_files)
|
||||
_json_test_add_unity_batch(${group} ${cxx_standard} ${args_MAIN} ${group_${group}_private} ${batch_files})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# remaining files: batches of BATCH_SIZE files per pool
|
||||
foreach(pool plain private)
|
||||
set(is_private FALSE)
|
||||
if(pool STREQUAL private)
|
||||
set(is_private TRUE)
|
||||
endif()
|
||||
|
||||
set(batch_files "")
|
||||
set(batch_count 0)
|
||||
set(batch_index 0)
|
||||
foreach(file ${${pool}_files})
|
||||
_json_test_unity_applies(${file} ${cxx_standard} "${force}" applies)
|
||||
if(NOT applies)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
list(APPEND batch_files ${file})
|
||||
math(EXPR batch_count "${batch_count} + 1")
|
||||
if(batch_count EQUAL args_BATCH_SIZE)
|
||||
_json_test_add_unity_batch(${pool}${batch_index} ${cxx_standard} ${args_MAIN} ${is_private} ${batch_files})
|
||||
set(batch_files "")
|
||||
set(batch_count 0)
|
||||
math(EXPR batch_index "${batch_index} + 1")
|
||||
endif()
|
||||
endforeach()
|
||||
if(batch_files)
|
||||
_json_test_add_unity_batch(${pool}${batch_index} ${cxx_standard} ${args_MAIN} ${is_private} ${batch_files})
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
#############################################################################
|
||||
# json_test_should_build_32bit_test(
|
||||
# <build_32bit_var> <build_32bit_only_var> <input>)
|
||||
|
||||
@@ -132,10 +132,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -147,10 +143,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -112,16 +112,6 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
!!! tip "Performance"
|
||||
|
||||
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
||||
`#!cpp std::istream` comes close: the parser scans the characters its stream buffer holds in bulk, without reading
|
||||
ahead, so the stream is left exactly where it would be after reading one character at a time. A `#!cpp FILE*` is
|
||||
read one character at a time, but the file is locked only once, so other threads that use the same `FILE` object
|
||||
wait until parsing is done. See [input sources and performance](../../features/parsing/index.md#input-sources-and-performance),
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md), and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1) parse from a character array"
|
||||
@@ -257,10 +247,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -275,11 +261,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Extended empty-input detection to also cover an `std::istream` without a stream buffer, and fixed a crash
|
||||
(`std::terminate`) when parsing from an `std::istream` with `eofbit` in its exception mask, in version 3.13.0.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) restores reading one character at a
|
||||
time).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) restores `std::fgetc`).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -116,10 +116,6 @@ consumer `sax` has a super-linear complexity.
|
||||
|
||||
A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
When reading from a `#!c FILE*`, the file is locked while it is parsed. The lock is recursive, so the SAX handler may
|
||||
use the same file with stdio functions, but it must not wait for another thread that uses the same `FILE` object; see
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -141,10 +137,6 @@ use the same file with stdio functions, but it must not wait for another thread
|
||||
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [accept](accept.md) - check if the input is valid JSON
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -156,10 +148,6 @@ use the same file with stdio functions, but it must not wait for another thread
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||
after the parsed value when `strict` is `#!cpp false`.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -123,6 +123,4 @@ Linear in the size of the JSON value `j`.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
- Writes unsigned integers wider than 64 bits as high-precision numbers since version 3.13.0; previously, they were
|
||||
silently truncated to 64 bits.
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
@@ -37,9 +37,8 @@ With (2), the bytes written before the exception remain in the output adapter.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer outside
|
||||
the range of int64 (an unsigned integer above 9223372036854775807, or, with a number type wider than 64 bits, any
|
||||
integer beyond int64), which BON8 cannot represent
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer
|
||||
above 9223372036854775807, which BON8 cannot represent
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
|
||||
valid UTF-8
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
|
||||
@@ -47,10 +47,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [`type_error.317`](../../home/exceptions.md#jsonexceptiontype_error317) if the top-level type of the JSON value
|
||||
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains a signed integer
|
||||
outside the range of int64 or an unsigned integer outside the range of uint64, which is only possible with a number
|
||||
type wider than 64 bits; example:
|
||||
`"integer number 9223372036854775808 cannot be represented by BSON as it does not fit int64"`
|
||||
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
||||
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
|
||||
@@ -123,5 +119,3 @@ pass before anything is written.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
is written.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,9 +46,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^64, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
@@ -93,5 +90,3 @@ Linear in the size of the JSON value `j`.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,9 +46,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^63, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
|
||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
||||
@@ -115,5 +112,3 @@ Linear in the size of the JSON value `j`.
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -18,10 +18,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
|
||||
1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many
|
||||
were removed; `#!cpp 0` if `object` has no member with this key.
|
||||
2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`).
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md). `ptr` must not be empty
|
||||
([`root()`](root.md) itself cannot be erased), and the *parent* of the target must already exist. The target is
|
||||
removed as in 1. if the parent is an object (returns the number of removed members, `#!cpp 0` or more) and as in 2.
|
||||
if it is an array (always returns `#!cpp 1`).
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), and returns how many values
|
||||
were removed: the *parent* of the target must already exist, and the target itself is removed as in 1. (an object
|
||||
member; `#!cpp 0` or more) or 2. (an array element; always `#!cpp 1`). `ptr` must not be empty -- [`root()`](root.md)
|
||||
itself cannot be erased.
|
||||
|
||||
## Template parameters
|
||||
|
||||
@@ -69,10 +69,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an
|
||||
object, or a primitive value the pointer's parent resolves to) throws what 1. throws.
|
||||
|
||||
Overloads 1. and 2. also throw [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202)
|
||||
("view does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md)
|
||||
view or a view of a *different* document. Overload 3. never throws `invalid_iterator.202`: it takes no view, and it
|
||||
always starts from this document's own [`root()`](root.md).
|
||||
Every overload also throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view
|
||||
does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a
|
||||
view of a *different* document (overloads 1-2 only; overload 3 always starts from this document's own
|
||||
[`root()`](root.md)).
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -92,10 +92,9 @@ always starts from this document's own [`root()`](root.md).
|
||||
|
||||
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
|
||||
referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. What does change is the container's sequence of
|
||||
*links* to its elements: removing an element of `array` (2.) shifts the links to the elements after it by one, as
|
||||
[`insert`](insert.md) does in the other direction. *Every* iterator already taken over `array`/`object` is invalidated
|
||||
by an erase, not only those at or after the removed element, since the container may now use a different sequence.
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. Removing an element of `array` (2.) does shift the
|
||||
*links* to the elements after it, the same way `insert`, `set`, or `push_back` on the same array would; any iterator
|
||||
already taken over `array`/`object` is invalidated by an erase, since it was walking the old layout.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -110,30 +110,17 @@ enum class image_check
|
||||
|
||||
How thoroughly `load()` validates `image` before trusting it.
|
||||
|
||||
`full` (the default)
|
||||
: Checks everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a
|
||||
string still in the source text, that it contains no quote, backslash, or control character); and that every number
|
||||
token is well-formed and matches the value stored for it. Strings and float tokens may share a range only if the
|
||||
ranges are identical (as nodes that share a value do), and never overlap otherwise. Reading and serializing a
|
||||
checked image is safe and always produces valid JSON, exactly as for a parsed document. This is the right choice for
|
||||
an image from anything you do not fully control -- a file, a cache shared with other processes, a peer on the
|
||||
network.
|
||||
| value | checks | guarantees |
|
||||
|----------|--------------------------------------------------------------------------------------------------------------|------------|
|
||||
| `full` | everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a string still in the source text, that it contains no quote, backslash, or control character); and that every number token is well-formed and matches the value stored for it; strings and float tokens may share a range only if the ranges are identical (as nodes that share a value do), and never overlap otherwise | reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document |
|
||||
| `bounds` | structure and bounds only -- that every offset and count in the node index stays inside the image | reading and serializing stay memory-safe, but a crafted image can hold strings that are not valid UTF-8 or that serialize to invalid JSON ([`dump()`](../basic_json_view/dump.md) writes them unchanged or throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316)), and numbers whose values differ from their text |
|
||||
| `none` | nothing | images from a trusted source only -- reading a damaged image is undefined behavior |
|
||||
|
||||
`bounds`
|
||||
: Checks structure and bounds only: that every offset and count in the node index stays inside the image. So
|
||||
`load()`, [`root()`](root.md), element access, and [`materialize()`](../basic_json_view/materialize.md) can never
|
||||
read outside the image, even on a damaged one. It does *not* guarantee that the image describes valid JSON: a
|
||||
string that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8
|
||||
or invalid JSON, or throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and a number can
|
||||
read back with a value that does not match how it is spelled. An [editable document](../json_editable_document.md)
|
||||
does not take such a string over: [`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw
|
||||
`type_error.316` when they copy it from a view of the loaded document, and leave the editable document unchanged.
|
||||
`bounds` skips scanning the text and the decoded strings, so it fits a cache your own process just wrote and reads
|
||||
straight back, where damage would mean a bug or a hardware fault rather than adversarial input.
|
||||
|
||||
`none`
|
||||
: Checks nothing. Reading a damaged image is undefined behavior, so use it only for an image you trust as much as
|
||||
your own memory.
|
||||
`full` is the default and the right choice for an image from anything you do not fully control -- a file, a cache
|
||||
shared with other processes, a peer on the network. `bounds` skips scanning the text and the decoded strings, so it
|
||||
fits a cache your own process just wrote and reads straight back, where damage would mean a bug or a hardware fault
|
||||
rather than adversarial input; it still cannot crash or read out of bounds. `none` skips validation entirely and
|
||||
should only be used for an image you trust as much as your own memory.
|
||||
|
||||
**Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the
|
||||
returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document
|
||||
@@ -147,6 +134,16 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is
|
||||
("unknown format"). Use images to cache a document within one build of the library, or to hand one to another
|
||||
process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format.
|
||||
|
||||
**What `image_check::bounds` does not guarantee.** A bounds-checked image can never make `load()`,
|
||||
[`root()`](root.md), element access, or [`materialize()`](../basic_json_view/materialize.md) read outside the image,
|
||||
so those stay safe on a damaged one. It does *not* guarantee that the image describes valid JSON: a string
|
||||
that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8 or invalid
|
||||
JSON, or throw `type_error.316`, and a number can read back with a value that does not match how it is spelled.
|
||||
Reserve `bounds` for images you already trust to be well-formed, and use it only to skip the extra scan.
|
||||
An [editable document](../json_editable_document.md) does not take such a string over either:
|
||||
[`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw `type_error.316` when they copy it from
|
||||
a view of the loaded document, and leave the editable document unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Caching a document, ownership, and a rejected image"
|
||||
|
||||
@@ -6,7 +6,7 @@ iterator begin() const noexcept;
|
||||
|
||||
Returns an iterator to the first element of an array, or the first member value of an object, in **document order**
|
||||
-- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one
|
||||
element (itself), and a `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range.
|
||||
element (itself); `#!json null` and a [discarded](is_discarded.md) view iterate as an empty range.
|
||||
|
||||
## Return value
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ iterator end() const noexcept;
|
||||
```
|
||||
|
||||
Returns an iterator to one past the last element of an array, one past the last member value of an object, in
|
||||
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself), and a
|
||||
`#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range (`#!cpp begin() == end()`).
|
||||
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself);
|
||||
`#!json null` and a [discarded](is_discarded.md) view iterate as an empty range, so `#!cpp begin() == end()` for
|
||||
them.
|
||||
|
||||
## Return value
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ Moving the document itself does not invalidate its views: the index is heap-allo
|
||||
|
||||
`basic_json_view` provides the read-only part of the `BasicJsonType` interface:
|
||||
|
||||
- the type-inspection functions
|
||||
- element access
|
||||
- lookup
|
||||
- iteration
|
||||
- the type-inspection functions,
|
||||
- element access,
|
||||
- lookup,
|
||||
- iteration,
|
||||
- conversion: [`get<T>()`](get.md), [`get_string()`](get_string.md), [`number_token()`](number_token.md), and
|
||||
[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand
|
||||
[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand, and
|
||||
- comparison: [`operator==`](operator_eq.md) and [`operator!=`](operator_ne.md) compare two views, or a view and a
|
||||
`BasicJsonType` value, without ever building a `BasicJsonType` value for a view; no ordering comparison
|
||||
(`#!cpp operator<`) is provided
|
||||
(`#!cpp operator<`) is provided.
|
||||
|
||||
[`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and [`value`](value.md) also accept a
|
||||
[`json_pointer`](../json_pointer/index.md). Unlike `basic_json`, there are no implicit conversion operators (no
|
||||
|
||||
@@ -16,10 +16,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
|
||||
## Parsing
|
||||
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
|
||||
instead of scanning its stream buffer in bulk
|
||||
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with `std::fgetc` instead of
|
||||
locking it once and using the unlocked stdio functions
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
|
||||
right after a parsed number
|
||||
- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR,
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
# JSON_DISABLE_STREAM_BULK_SCAN
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp std::istream` reads the input one character at a time through the stream
|
||||
buffer, as in version 3.12.0 and earlier, instead of scanning the characters that the stream buffer already holds in
|
||||
bulk.
|
||||
|
||||
The macro is an escape hatch for stream buffers that do not follow the usual contract of `std::basic_streambuf` (see
|
||||
the requirements on the stream buffer in the notes below). It does not change any result: values, exceptions, and the
|
||||
position of the stream after parsing are the same either way. Only the speed differs.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0`: streams are scanned in bulk.
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "How streams are scanned"
|
||||
|
||||
A `std::streambuf` keeps the part of the input it has read but not yet handed out in its *get area*, the range
|
||||
from `gptr()` to `egptr()`. A `std::filebuf`, for instance, reads the file in blocks of a few kilobytes into its get
|
||||
area, and a `std::stringbuf` exposes its whole string as one get area.
|
||||
|
||||
When parsing from a `std::istream`, the parser scans this get area directly, with the same fast paths that it uses
|
||||
for a `std::string`: runs of ordinary string characters, numbers, and indentation are recognized several bytes at a
|
||||
time instead of being requested from the stream buffer one character at a time. When the parser reaches the end of
|
||||
the get area, it asks the stream buffer for the next character as before, which makes the stream buffer refill its
|
||||
get area.
|
||||
|
||||
The parser only reads characters that it would also have read one at a time, and it advances the stream buffer
|
||||
exactly over the characters it consumes. Nothing is read ahead, buffered elsewhere, or put back. Therefore, the
|
||||
stream is left exactly where it was left before:
|
||||
|
||||
- [`operator>>`](../operator_gtgt.md) and [`sax_parse`](../basic_json/sax_parse.md) with `strict = false` can still
|
||||
read several values from one stream, and the stream can still be used with other `std::istream` functions after a
|
||||
value has been read.
|
||||
- With [`JSON_PRECISE_STREAM_POSITION`](json_precise_stream_position.md), the stream is still positioned right after
|
||||
a parsed number.
|
||||
- Exception messages, including the line, the column, and the last read token, are unchanged.
|
||||
|
||||
!!! note "Performance"
|
||||
|
||||
Parsing from a `std::ifstream` or `std::istringstream` is typically 1.5 to 2 times as fast as before, and reading
|
||||
many small values from one stream with `operator>>` 1.4 to 4 times as fast. Parsing from a stream is then within
|
||||
about 10 to 20 % of first reading the whole file into a `std::string` and parsing that.
|
||||
|
||||
A stream buffer without a get area (one that only overrides `underflow()` and `uflow()` and hands out every
|
||||
character from there) offers nothing to scan in bulk, so it is read one character at a time as before.
|
||||
|
||||
!!! note "Requirements on the stream buffer"
|
||||
|
||||
The bulk scan relies on the following behavior, which every stream buffer of the standard library and every stream
|
||||
buffer that keeps its input in its get area has:
|
||||
|
||||
- When `sgetc()` returns a character while `gptr() < egptr()`, that character is `*gptr()`; when `sbumpc()` returns
|
||||
one, it was at `gptr()` before the call.
|
||||
- Characters in the get area are not changed while they are in the get area.
|
||||
- `underflow()` or `uflow()` either sets up a get area that holds the returned character at `gptr()` (or at
|
||||
`gptr() - 1` after `uflow()`), or leaves the get area empty.
|
||||
|
||||
A stream buffer that returns characters from `uflow()` while leaving unrelated characters in its get area, or that
|
||||
changes the characters in its get area in place, does not meet these requirements. Define the macro to `1` for
|
||||
such stream buffers.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dsbs`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Other inputs"
|
||||
|
||||
Strings, string views, vectors, arrays, and pointer or iterator ranges over contiguous memory are scanned in bulk
|
||||
anyway. Reading from a `#!cpp FILE*` is not affected by this macro; see
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](json_disable_unlocked_file_io.md) for that input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: reading values from a stream (default)"
|
||||
|
||||
The stream is scanned in bulk, and the values are read one after the other:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
|
||||
|
||||
std::string word;
|
||||
input >> word; // "rest"
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Example: reading one character at a time (macro defined to 1)"
|
||||
|
||||
With the macro, the same program reads the stream one character at a time, with the same results:
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 1
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
|
||||
|
||||
std::string word;
|
||||
input >> word; // "rest"
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - leave an input stream positioned right after a
|
||||
parsed number
|
||||
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -1,115 +0,0 @@
|
||||
# JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
|
||||
earlier, instead of locking the file once and reading it with the unlocked stdio functions.
|
||||
|
||||
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
|
||||
Only the speed and the locking of the file differ (see the notes on locking below).
|
||||
|
||||
Builds with MemorySanitizer use `std::fgetc` automatically, because the inline `getc_unlocked` of glibc reads the stdio
|
||||
buffer directly, which glibc (not instrumented) filled, and MemorySanitizer would report it as uninitialized.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0`: the file is locked once and read with the unlocked stdio functions where they are available.
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
The C standard library makes every `std::fgetc` call thread-safe by locking and unlocking the `FILE` object. When
|
||||
parsing from a `FILE*`, the parser reads one character at a time, so this happens for every character of the input.
|
||||
|
||||
Instead, the parser locks the file once when parsing starts and unlocks it when parsing ends - also when an
|
||||
exception is thrown - and reads the characters in between without locking:
|
||||
|
||||
| C library | functions |
|
||||
|------------------------------------------------------------|----------------------------------------------------|
|
||||
| glibc (Linux), Apple (macOS, iOS), Android, FreeBSD, NetBSD, OpenBSD, DragonFly BSD | `flockfile`, `getc_unlocked`, `funlockfile` (POSIX) |
|
||||
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
|
||||
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
|
||||
|
||||
This makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3 times as fast with
|
||||
glibc, whose locking is cheaper. The binary formats ([`from_cbor`](../basic_json/from_cbor.md),
|
||||
[`from_msgpack`](../basic_json/from_msgpack.md), ...) read from a `FILE*` the same way.
|
||||
|
||||
!!! note "Locking"
|
||||
|
||||
While a value is parsed from a `FILE*`, the parsing thread holds the file's lock. The lock is recursive, so the
|
||||
parsing thread itself - for instance, a [SAX](../json_sax/index.md) handler or a
|
||||
[parser callback](../basic_json/parser_callback_t.md) - can keep using the file with stdio functions such as
|
||||
`std::ftell`. Other threads that use the same `FILE` object wait until the parser is done. Before, they could
|
||||
interleave their reads with the parser's, which made the parsed input unpredictable anyway.
|
||||
|
||||
A program in which a SAX handler or parser callback waits for **another** thread that uses the **same** `FILE`
|
||||
object would deadlock. Define the macro to `1` for such programs.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dufio`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Other inputs"
|
||||
|
||||
Reading from a `#!cpp std::istream` is not affected by this macro; see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md) for that input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: parsing from a `FILE*` (default)"
|
||||
|
||||
The file is locked once while it is parsed:
|
||||
|
||||
```cpp
|
||||
#include <cstdio>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::FILE* f = std::fopen("example.json", "rb");
|
||||
json j = json::parse(f); // f is locked until parse() returns
|
||||
std::fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Example: locking for every character (macro defined to 1)"
|
||||
|
||||
With the macro, every character is read with `std::fgetc`, with the same result:
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 1
|
||||
#include <cstdio>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::FILE* f = std::fopen("example.json", "rb");
|
||||
json j = json::parse(f); // std::fgetc for every character
|
||||
std::fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a
|
||||
time
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -72,12 +72,6 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Bulk scanning"
|
||||
|
||||
The parser scans the characters in the stream buffer in bulk (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)), but it never reads ahead. The stream is left
|
||||
at the same position with and without bulk scanning, in both modes of this macro.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
|
||||
|
||||
@@ -12,9 +12,8 @@ non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||
message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact
|
||||
diagnostic is preserved). A `std::istream` uses simdutf for the characters its stream buffer holds (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other streaming inputs (`FILE*`, wide strings,
|
||||
user-defined adapters) always use the scalar path.
|
||||
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the
|
||||
scalar path.
|
||||
|
||||
When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the
|
||||
simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
|
||||
|
||||
@@ -39,6 +39,6 @@ the library.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
|
||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_vns`, and
|
||||
`_vssse3` in version 3.13.0; see
|
||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_vns`, and `_vssse3` in version
|
||||
3.13.0; see
|
||||
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
||||
@@ -121,7 +121,6 @@ being read.
|
||||
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) - read the stream one character at a time
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -134,6 +133,3 @@ being read.
|
||||
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
||||
- Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed
|
||||
value if parsing throws.
|
||||
- Scans the characters in the stream buffer of `i` in bulk in version 3.13.0; the position of `i` after the call is
|
||||
unchanged ([`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) restores reading one character
|
||||
at a time).
|
||||
@@ -135,9 +135,6 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
headers are included.
|
||||
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
|
||||
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
|
||||
- [x] The library is compiled with Qt's keyword macros (`slots`, `signals`, `emit`, `foreach`, `forever`) being defined
|
||||
to make sure it does not use them as identifiers (see
|
||||
[`unit-qt_keywords.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-qt_keywords.cpp)).
|
||||
- [x] The library is compiled with exceptions disabled to support alternative means of error handling.
|
||||
|
||||
## Stable public API
|
||||
|
||||
@@ -14,8 +14,8 @@ C++ types, and finally serialize it again.
|
||||
[parsing untrusted input](parsing/untrusted_input.md).
|
||||
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
|
||||
strings and numbers stay in the input and are only decoded when needed.
|
||||
- [Editable documents](json_view.md#editing-a-document) — modify a parsed document without building a `json` tree.
|
||||
- [Images](json_view.md#images) — save a parsed document and load it again without parsing it.
|
||||
[Editable documents](json_view.md#editing-a-document) can also be modified, and [images](json_view.md#images) load a
|
||||
parsed document again without parsing it.
|
||||
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
||||
|
||||
## Accessing and modifying values
|
||||
|
||||
@@ -133,14 +133,6 @@ When defined, default parse and serialize functions for enums are excluded and h
|
||||
|
||||
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
||||
|
||||
## `JSON_DISABLE_STREAM_BULK_SCAN`
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp std::istream` reads one character at a time instead of scanning the
|
||||
characters that the stream buffer holds in bulk. Results are the same either way; the macro is an escape hatch for
|
||||
stream buffers that do not follow the usual `std::streambuf` contract. The default value is `0`.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md).
|
||||
|
||||
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
||||
|
||||
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||
@@ -149,14 +141,6 @@ is planned to become the default in version 4.0.0.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||
|
||||
## `JSON_DISABLE_UNLOCKED_FILE_IO`
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, which locks the file each
|
||||
time, instead of locking the file once and reading it with the unlocked stdio functions. Results are the same either
|
||||
way. The default value is `0`.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## `JSON_NO_AUTOMATIC_UDLS`
|
||||
|
||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||
|
||||
@@ -23,10 +23,6 @@ The complete default namespace name is derived as follows:
|
||||
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
|
||||
appends `_ekmo`.
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) defined non-zero appends
|
||||
`_dsbs`.
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
||||
`_dufio`.
|
||||
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
|
||||
- [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`.
|
||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||
@@ -112,8 +108,6 @@ follows:
|
||||
([`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md)), `_snul`
|
||||
([`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md)), `_sbu8`
|
||||
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
|
||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs`
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio`
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_vns`
|
||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_vns`
|
||||
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
|
||||
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
|
||||
@@ -55,30 +55,6 @@ whitespace, because `operator>>` consumes the character that terminates a number
|
||||
[`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) is defined to `1` — see the
|
||||
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||
|
||||
## Input sources and performance
|
||||
|
||||
All inputs produce the same values and the same exceptions, but they are read in different ways, which makes some of
|
||||
them faster than others:
|
||||
|
||||
| input | how it is read |
|
||||
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
|
||||
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
|
||||
| `#!cpp FILE*` | one character at a time, with the file locked once for the whole parse |
|
||||
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
|
||||
|
||||
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
|
||||
`#!cpp std::string` first and parsing that, and it needs no memory for a copy of the file. The stream is never read
|
||||
ahead: after parsing, it is positioned exactly as if every character had been read one at a time, so
|
||||
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
|
||||
[JSON Lines](json_lines.md)).
|
||||
|
||||
Two macros switch these optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not
|
||||
follow the usual `std::streambuf` contract, and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
||||
thread must use the same `#!cpp FILE` object while it is parsed.
|
||||
|
||||
## SAX vs. DOM parsing
|
||||
|
||||
The library offers two parsing models:
|
||||
|
||||
@@ -194,8 +194,8 @@ packet-beta
|
||||
|
||||
| Bytes | Field | Type | Contents |
|
||||
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below). There is no node for a discarded value: a [discarded](../api/basic_json_view/is_discarded.md) view has none |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bit 3: moved, bit 4: new, bit 5: linked (see below) |
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) |
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
@@ -257,8 +257,6 @@ edited:
|
||||
sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
|
||||
8-15 hold the address of the value's node, so values never move.
|
||||
- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
|
||||
- A value that an entry of such a sequence links to gets the flag *linked* (bit 5): its extent in the parsed layout
|
||||
(`next`) no longer matters, so a scalar can replace an array or object there in place.
|
||||
- Views of read-only documents compile without any of this: how views walk the index is a template parameter
|
||||
(`navigation<Editable>`).
|
||||
|
||||
|
||||
@@ -972,19 +972,9 @@ double-precision number when `number_float_t` is `#!cpp float`.
|
||||
|
||||
### json.exception.out_of_range.407
|
||||
|
||||
An integer number cannot be represented by the binary format it is serialized to:
|
||||
|
||||
- [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so
|
||||
[`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807.
|
||||
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and
|
||||
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
|
||||
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
|
||||
[`number_unsigned_t`](../api/basic_json/number_unsigned_t.md) wider than 64 bits (e.g., `__int128`) can hold values
|
||||
outside the range of the format: [-2^64, 2^64-1] for CBOR, [-2^63, 2^64-1] for MessagePack, and the range of int64
|
||||
(signed integers) or uint64 (unsigned integers) for BSON.
|
||||
|
||||
[UBJSON](../features/binary_formats/ubjson.md) and [BJData](../features/binary_formats/bjdata.md) never throw this
|
||||
exception, because they serialize integers beyond 64 bits as high-precision numbers.
|
||||
An unsigned integer number greater than 9223372036854775807 cannot be represented in the binary format. This exception
|
||||
is only thrown by [`to_bon8`](../api/basic_json/to_bon8.md), as [BON8](../features/binary_formats/bon8.md) has no
|
||||
unsigned 64-bit integer type.
|
||||
|
||||
!!! note "Former occurrences"
|
||||
|
||||
@@ -993,17 +983,11 @@ exception, because they serialize integers beyond 64 bits as high-precision numb
|
||||
- Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers.
|
||||
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
|
||||
|
||||
Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
|
||||
truncated unsigned integers wider than 64 bits.
|
||||
|
||||
!!! failure "Example messages"
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64
|
||||
```
|
||||
```
|
||||
integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]
|
||||
```
|
||||
|
||||
### json.exception.out_of_range.408
|
||||
|
||||
|
||||
@@ -235,17 +235,6 @@ Build the unit tests against the [simdutf](https://github.com/simdutf/simdutf) U
|
||||
its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on
|
||||
`JSON_BuildTests`.
|
||||
|
||||
### `JSON_TestUnityBuild`
|
||||
|
||||
Build the unit tests in batches of several test files per executable to speed up compilation: the files of a batch are
|
||||
compiled as one translation unit, so the template instantiations of the library are shared. Every test file still gets
|
||||
its own CTest test (e.g., `test-foo_cpp11`), which runs only the test cases of that file from the shared executable.
|
||||
This option is `ON` by default (and `OFF` with MinGW). Set it to `OFF` to get one executable per test file, e.g., when
|
||||
debugging a single file. The number of files per executable is set by the cache variable `JSON_TestUnityBatchSize`
|
||||
(default: `8`). Related test files (e.g., all binary formats) are grouped explicitly in `tests/CMakeLists.txt`; each
|
||||
group is compiled as one executable per C++ standard regardless of the batch size, and only the remaining files are
|
||||
batched by size. Depends on `JSON_BuildTests`.
|
||||
|
||||
### `JSON_Valgrind`
|
||||
|
||||
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||
|
||||
@@ -370,9 +370,7 @@ nav:
|
||||
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
|
||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||
- 'JSON_DISABLE_STREAM_BULK_SCAN': api/macros/json_disable_stream_bulk_scan.md
|
||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||
- 'JSON_DISABLE_UNLOCKED_FILE_IO': api/macros/json_disable_unlocked_file_io.md
|
||||
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md
|
||||
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||
- 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md
|
||||
|
||||
@@ -54,14 +54,6 @@
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -110,18 +102,6 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||
#else
|
||||
@@ -139,9 +119,9 @@
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -153,8 +133,6 @@
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||
|
||||
|
||||
@@ -1342,32 +1342,14 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep
|
||||
return end + (three ? 5 : 4);
|
||||
}
|
||||
|
||||
/// the powers of ten up to 10^19 (all that fit in std::uint64_t)
|
||||
inline const std::array<std::uint64_t, 20>& powers_of_ten() noexcept
|
||||
/// the powers of ten up to 10^16
|
||||
inline const std::array<std::uint64_t, 17>& powers_of_ten_16() noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 20> powers =
|
||||
static const std::array<std::uint64_t, 17> powers =
|
||||
{
|
||||
{
|
||||
1u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u
|
||||
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u,
|
||||
100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u, 10000000000000000u
|
||||
}
|
||||
};
|
||||
return powers;
|
||||
@@ -1385,9 +1367,9 @@ JSON_HEDLEY_NON_NULL(1)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept
|
||||
{
|
||||
JSON_ASSERT(digits >= powers_of_ten()[static_cast<std::size_t>(count - 1)] && count <= 15);
|
||||
JSON_ASSERT(digits >= powers_of_ten_16()[static_cast<std::size_t>(count - 1)] && count <= 15);
|
||||
const int scale = 16 - count;
|
||||
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false});
|
||||
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten_16()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false});
|
||||
}
|
||||
|
||||
/// as write_short_decimal(), counting the digits (not 0, less than 10^15)
|
||||
@@ -1398,7 +1380,7 @@ inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noe
|
||||
JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
|
||||
// floor(log10(2^bits)) + 1 digits, or one less
|
||||
const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12;
|
||||
const int count = log2_bound + (digits >= powers_of_ten()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
|
||||
const int count = log2_bound + (digits >= powers_of_ten_16()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
|
||||
return write_short_decimal(first, digits, count, exp);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||
#include <limits> // numeric_limits
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
#include <utility> // pair, declval
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
#include <cstdio> // FILE *
|
||||
@@ -32,33 +30,6 @@
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
// Which unlocked stdio functions file_input_adapter uses (see
|
||||
// JSON_DISABLE_UNLOCKED_FILE_IO): 1 = POSIX flockfile/getc_unlocked,
|
||||
// 2 = Microsoft CRT _lock_file/_fgetc_nolock, 0 = none (std::fgetc, which
|
||||
// locks the file for every character). Only C libraries known to declare
|
||||
// the functions are listed; the macros are tested after <cstdio>, which
|
||||
// defines __GLIBC__. MemorySanitizer builds always use std::fgetc: glibc's
|
||||
// inline getc_unlocked reads the stdio buffer directly, which glibc (not
|
||||
// instrumented) filled, so MemorySanitizer would report a false positive.
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(memory_sanitizer)
|
||||
#define JSON_UNLOCKED_FILE_IO_MSAN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO || defined(JSON_UNLOCKED_FILE_IO_MSAN)
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#elif defined(_MSC_VER)
|
||||
#define JSON_UNLOCKED_FILE_IO 2
|
||||
#elif defined(__GLIBC__) || defined(__APPLE__) || defined(__ANDROID__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
#define JSON_UNLOCKED_FILE_IO 1
|
||||
#else
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -85,35 +56,18 @@ class file_input_adapter
|
||||
: m_file(f)
|
||||
{
|
||||
JSON_ASSERT(m_file != nullptr);
|
||||
lock();
|
||||
}
|
||||
|
||||
// make class move-only
|
||||
file_input_adapter(const file_input_adapter&) = delete;
|
||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||
: m_file(rhs.m_file)
|
||||
{
|
||||
// the lock is owned by the thread, not the adapter: hand it over
|
||||
rhs.m_file = nullptr;
|
||||
}
|
||||
file_input_adapter(file_input_adapter&&) noexcept = default;
|
||||
file_input_adapter& operator=(const file_input_adapter&) = delete;
|
||||
file_input_adapter& operator=(file_input_adapter&&) = delete;
|
||||
~file_input_adapter()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
~file_input_adapter() = default;
|
||||
|
||||
std::char_traits<char>::int_type get_character() noexcept
|
||||
{
|
||||
// the file is locked for the lifetime of the adapter, so the
|
||||
// per-character lock of std::fgetc can be skipped
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
return getc_unlocked(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
return _fgetc_nolock(m_file);
|
||||
#else
|
||||
return std::fgetc(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns the number of characters successfully read
|
||||
@@ -124,32 +78,6 @@ class file_input_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
// Lock the file once for the whole parse instead of once per character.
|
||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||
// same thread keep working.
|
||||
void lock() noexcept
|
||||
{
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
flockfile(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
_lock_file(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
void unlock() noexcept
|
||||
{
|
||||
// a moved-from adapter has no file and holds no lock
|
||||
if (m_file == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
funlockfile(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
_unlock_file(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the file pointer to read from
|
||||
std::FILE* m_file;
|
||||
};
|
||||
@@ -199,26 +127,15 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
, lookahead(rhs.lookahead)
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
, token_carry(std::move(rhs.token_carry))
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_get_area(rhs.current_in_get_area)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
#endif
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
rhs.lookahead = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
@@ -238,41 +155,17 @@ class input_stream_adapter
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sgetc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the peeked character is at gptr(), unless the streambuf has
|
||||
// no get area
|
||||
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_end(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -286,6 +179,13 @@ class input_stream_adapter
|
||||
lookahead = false;
|
||||
}
|
||||
#else
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
}
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
@@ -294,37 +194,12 @@ class input_stream_adapter
|
||||
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
@@ -343,165 +218,7 @@ class input_stream_adapter
|
||||
return res;
|
||||
}
|
||||
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// The lexer may scan the characters in the get area of the streambuf
|
||||
// directly (SWAR fast paths). The get area is only a window into the
|
||||
// input: it ends wherever the streambuf's buffer ends, and is refilled by
|
||||
// the next get_character(). The lexer therefore keeps counting the
|
||||
// position itself and treats the end of the window as "unknown", not as
|
||||
// the end of the input.
|
||||
static constexpr bool supports_bulk_scan = true;
|
||||
static constexpr bool bulk_window = true;
|
||||
|
||||
// Pointer to the next unread character; only valid when
|
||||
// bulk_remaining() > 0. The character before it is the one last returned
|
||||
// by get_character().
|
||||
const char* bulk_data() const
|
||||
{
|
||||
return current_end();
|
||||
}
|
||||
|
||||
// Number of unread characters in the get area.
|
||||
std::size_t bulk_remaining() const
|
||||
{
|
||||
if (!current_in_get_area)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::size_t>(streambuf_access::get_end(sb) - current_end());
|
||||
}
|
||||
|
||||
// Consume @a n characters previously inspected via bulk_data(); the last
|
||||
// of them becomes the character last returned by get_character().
|
||||
void bulk_skip(std::size_t n)
|
||||
{
|
||||
JSON_ASSERT(n <= bulk_remaining());
|
||||
// gbump() takes an int, but a get area (e.g., of a std::stringbuf) can
|
||||
// be larger
|
||||
constexpr auto max_bump = static_cast<std::size_t>((std::numeric_limits<int>::max)());
|
||||
for (; n > max_bump; n -= max_bump)
|
||||
{
|
||||
streambuf_access::bump(sb, static_cast<int>(max_bump));
|
||||
}
|
||||
streambuf_access::bump(sb, static_cast<int>(n));
|
||||
}
|
||||
|
||||
// The adapter keeps the current token for the lexer's error messages: the
|
||||
// part in the get area is only remembered as a pointer, and copied out
|
||||
// when the get area is about to be refilled.
|
||||
static constexpr bool supports_token_tracking = true;
|
||||
|
||||
// Start a token with the characters read from now on. Until this is
|
||||
// called, nothing is tracked (the binary readers never call it).
|
||||
void begin_token() noexcept
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
token_segment = current_end();
|
||||
}
|
||||
|
||||
// Start a new token with the character @a c last returned by
|
||||
// get_character().
|
||||
void mark_token_start(std::char_traits<char>::int_type c)
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
if (current_in_get_area)
|
||||
{
|
||||
token_segment = current_end() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_segment = nullptr;
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the characters read since mark_token_start() to @a out, without the
|
||||
// last one if @a drop_last.
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
out = token_carry;
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, current_end());
|
||||
}
|
||||
if (drop_last && !out.empty())
|
||||
{
|
||||
out.pop_back();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
||||
// members through a derived class makes forming the member pointers
|
||||
// accessible; calling them on another streambuf object is then ordinary
|
||||
// member access through a pointer to member.
|
||||
struct streambuf_access : std::streambuf
|
||||
{
|
||||
static const char* get_ptr(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::gptr))();
|
||||
}
|
||||
static const char* get_end(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::egptr))();
|
||||
}
|
||||
static const char* get_begin(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::eback))();
|
||||
}
|
||||
static void bump(std::streambuf* s, int n)
|
||||
{
|
||||
(s->*(&streambuf_access::gbump))(n);
|
||||
}
|
||||
};
|
||||
|
||||
// one past the character last returned by get_character(), if that is in
|
||||
// the get area
|
||||
const char* current_end() const
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// the character is peeked at gptr() and not stepped over yet, unless
|
||||
// it was given back with release_lookahead()
|
||||
return streambuf_access::get_ptr(sb) + (lookahead ? 1 : 0);
|
||||
#else
|
||||
return streambuf_access::get_ptr(sb);
|
||||
#endif
|
||||
}
|
||||
|
||||
// the get area is about to be replaced: copy the token's part in it
|
||||
void save_token_segment()
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
||||
token_segment = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// character @a c was just read into a new get area (@a in_get_area) or
|
||||
// returned without one
|
||||
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
||||
{
|
||||
current_in_get_area = in_get_area;
|
||||
if (!tracking_token)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (in_get_area)
|
||||
{
|
||||
token_segment = current_end() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
@@ -523,16 +240,6 @@ class input_stream_adapter
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
/// the part of the current token that was in earlier get areas
|
||||
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
/// start of the current token's part in the get area, or nullptr
|
||||
const char* token_segment = nullptr;
|
||||
/// whether the character last returned by get_character() is in the get area
|
||||
bool current_in_get_area = false;
|
||||
/// whether a token is tracked at all; the binary readers never start one
|
||||
bool tracking_token = false;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
|
||||
@@ -166,43 +166,6 @@ constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter keeps the current token itself and can
|
||||
// hand it out on demand (see input_stream_adapter::supports_token_tracking),
|
||||
// so the lexer need not copy every scanned character into token_string.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_token_tracking = decltype(InputAdapterType::supports_token_tracking);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_token_tracking(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_token_tracking;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_token_tracking(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether the contiguous block of a bulk-scan adapter is only a window
|
||||
// into the input (see input_stream_adapter::bulk_window): its end is not the
|
||||
// end of the input, so a token that touches it must be finished character by
|
||||
// character.
|
||||
template<typename InputAdapterType>
|
||||
using detect_bulk_window = decltype(InputAdapterType::bulk_window);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_bulk_window(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::bulk_window;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_bulk_window(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief lexical analysis
|
||||
|
||||
@@ -221,20 +184,9 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// whether the last read token can be reconstructed from the input adapter
|
||||
/// on demand (in error paths) instead of being copied on every scanned
|
||||
/// character; see input_adapter_supports_seek
|
||||
static constexpr bool seekable =
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether the adapter keeps the token itself (mark_token_start() /
|
||||
/// copy_token()); see input_adapter_supports_token_tracking
|
||||
static constexpr bool adapter_token =
|
||||
!seekable && input_adapter_supports_token_tracking<InputAdapterType>(is_detected<detect_supports_token_tracking, InputAdapterType> {});
|
||||
|
||||
/// whether the last read token is obtained from the input adapter on
|
||||
/// demand (in error paths) instead of being copied on every scanned
|
||||
/// character: seekable adapters reconstruct it from the consumed input,
|
||||
/// token-tracking adapters keep it themselves
|
||||
static constexpr bool lazy_token_string = seekable || adapter_token;
|
||||
|
||||
/// whether the read position is derived from the input adapter on demand
|
||||
/// instead of being counted on every scanned character. Seekable adapters
|
||||
/// know how much input they consumed and can revisit it, so
|
||||
@@ -242,7 +194,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// and line and column are recomputed from the consumed input only when
|
||||
/// get_position() is called (errors). Counting per character kept two
|
||||
/// increments and a newline check in the hot path of every get().
|
||||
static constexpr bool lazy_position = seekable;
|
||||
static constexpr bool lazy_position = lazy_token_string;
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
@@ -251,17 +203,14 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). Without
|
||||
/// lazy_token_string / lazy_position, the bulk paths copy the consumed
|
||||
/// characters into token_string and advance the position counters
|
||||
/// themselves (see bulk_consume).
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
/// the per-character capture in get() cannot lose error diagnostics. It
|
||||
/// also implies lazy_position, so the bulk paths need not advance the
|
||||
/// position counters.
|
||||
static constexpr bool bulk_scan =
|
||||
input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
|
||||
/// whether the contiguous block is only a window into the input, so that
|
||||
/// its end does not end a token; see input_adapter_bulk_window
|
||||
static constexpr bool bulk_window =
|
||||
input_adapter_bulk_window<InputAdapterType>(is_detected<detect_bulk_window, InputAdapterType> {});
|
||||
lazy_token_string
|
||||
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
|
||||
public:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
@@ -270,11 +219,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
: ia(std::move(adapter))
|
||||
, ignore_comments(ignore_comments_)
|
||||
, discard_number_values(discard_number_values_)
|
||||
{
|
||||
// the token string of an error before the first token is everything
|
||||
// read so far
|
||||
begin_token(std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
{}
|
||||
|
||||
// deleted because of pointer members
|
||||
lexer(const lexer&) = delete;
|
||||
@@ -291,8 +236,8 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
||||
/// to get(). On success, advances the adapter exactly as those 4 get()
|
||||
/// calls would (including the position and token bookkeeping of a
|
||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
||||
/// calls would (bulk_scan implies lazy_position, so there are no position
|
||||
/// counters to advance) and leaves @a current holding the last of
|
||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||
/// written to @a out. Makes no state change and returns false for a
|
||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||
@@ -311,7 +256,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bulk_consume(reinterpret_cast<const char*>(raw), 4);
|
||||
ia.bulk_skip(4);
|
||||
current = char_traits<char_type>::to_int_type(raw[3]);
|
||||
out = codepoint;
|
||||
return true;
|
||||
@@ -440,36 +385,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
return;
|
||||
}
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||
}
|
||||
|
||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||
/// the bookkeeping get() would have done for them; the characters are
|
||||
/// never newlines
|
||||
void bulk_consume(const char* data, std::size_t n)
|
||||
{
|
||||
bulk_track_position(n, std::integral_constant<bool, lazy_position> {});
|
||||
bulk_track_token(data, n, std::integral_constant<bool, lazy_token_string> {});
|
||||
ia.bulk_skip(n);
|
||||
}
|
||||
|
||||
/// seekable adapter: the position is derived from the adapter
|
||||
void bulk_track_position(std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: count the characters
|
||||
void bulk_track_position(std::size_t n, std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
position.chars_read_total += n;
|
||||
position.chars_read_current_line += n;
|
||||
}
|
||||
|
||||
/// the token is obtained from the adapter
|
||||
void bulk_track_token(const char* /*data*/, std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: capture the characters
|
||||
void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/)
|
||||
{
|
||||
token_string.insert(token_string.end(), data, data + n);
|
||||
ia.bulk_skip(pos);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
@@ -1687,10 +1603,6 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
const std::size_t len = i;
|
||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
|
||||
// reset() records where this token starts (for diagnostics), so it has
|
||||
// to run before the input position advances below
|
||||
@@ -1708,8 +1620,7 @@ scan_number_done:
|
||||
const token_type integer_result = convert_integer(number_type, data, data + len);
|
||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||
{
|
||||
bulk_consume(data + 1, len - 1);
|
||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||
ia.bulk_skip(len - 1);
|
||||
return integer_result;
|
||||
}
|
||||
// The value does not fit an integer, so this token converts as a
|
||||
@@ -1725,37 +1636,11 @@ scan_number_done:
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
bulk_consume(data + 1, len - 1);
|
||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||
ia.bulk_skip(len - 1);
|
||||
|
||||
return convert_number(number_type, mantissa_end);
|
||||
}
|
||||
|
||||
/// window adapter: a number that reaches the end of the window may continue
|
||||
/// after it, so scan_number() has to read it character by character
|
||||
static constexpr bool number_may_continue(bool at_end, std::true_type /*window*/) noexcept
|
||||
{
|
||||
return at_end;
|
||||
}
|
||||
|
||||
/// the end of the block is the end of the input, which ends the number
|
||||
static constexpr bool number_may_continue(bool /*at_end*/, std::false_type /*window*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// window adapter: read the character after a bulk-scanned number and unget
|
||||
/// it, as scan_number() does. The adapter is a stream that is handed back to
|
||||
/// the user, who must find the same input consumed either way.
|
||||
void read_number_terminator(std::true_type /*window*/)
|
||||
{
|
||||
get();
|
||||
unget();
|
||||
}
|
||||
|
||||
/// the input is not handed back, so the terminator can stay unread
|
||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
||||
|
||||
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||
{
|
||||
@@ -1811,30 +1696,11 @@ scan_number_done:
|
||||
/// seekable adapter: remember where the current token starts so it can be
|
||||
/// reconstructed from the input on error; current has already been
|
||||
/// consumed, hence the -1
|
||||
void note_token_start(std::true_type /*lazy*/)
|
||||
{
|
||||
note_token_start_lazy(std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
void note_token_start_lazy(std::false_type /*adapter_token*/) noexcept
|
||||
void note_token_start(std::true_type /*lazy*/) noexcept
|
||||
{
|
||||
token_string_start = ia.get_consumed_count() - 1;
|
||||
}
|
||||
|
||||
/// token-tracking adapter: start tracking the token at the first character
|
||||
void begin_token(std::true_type /*adapter_token*/) noexcept
|
||||
{
|
||||
ia.begin_token();
|
||||
}
|
||||
|
||||
void begin_token(std::false_type /*adapter_token*/) const noexcept {}
|
||||
|
||||
/// token-tracking adapter: the token starts with the current character
|
||||
void note_token_start_lazy(std::true_type /*adapter_token*/)
|
||||
{
|
||||
ia.mark_token_start(current);
|
||||
}
|
||||
|
||||
/// streaming adapter: start copying the token eagerly, beginning with the
|
||||
/// already-read first character
|
||||
void note_token_start(std::false_type /*lazy*/) noexcept
|
||||
@@ -2155,20 +2021,6 @@ scan_number_done:
|
||||
public:
|
||||
/// seekable adapter: rebuild the last read token from the input on demand
|
||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||
{
|
||||
return collect_token_chars_lazy(out, std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
/// token-tracking adapter: the adapter hands out everything read since
|
||||
/// mark_token_start(), except a pending unget (see below)
|
||||
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::true_type /*adapter_token*/) const
|
||||
{
|
||||
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
|
||||
ia.copy_token(out, pending_real_unget);
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::false_type /*adapter_token*/) const
|
||||
{
|
||||
// a pending unget of a real (non-EOF) character means that character
|
||||
// was consumed from the input but is not part of the token; EOF is
|
||||
@@ -2290,8 +2142,9 @@ scan_number_done:
|
||||
Pretty-printed JSON indents every line with a run of spaces, which the loop
|
||||
in skip_whitespace() would otherwise read one character at a time. The
|
||||
spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any
|
||||
other whitespace are left to that loop. Skipping bypasses get();
|
||||
bulk_consume() does its bookkeeping.
|
||||
other whitespace are left to that loop. Skipping bypasses get(), which is
|
||||
fine because bulk_scan implies lazy_position: the position is derived from
|
||||
the adapter, not counted per character.
|
||||
*/
|
||||
void skip_indentation(std::true_type /*bulk*/)
|
||||
{
|
||||
@@ -2314,7 +2167,7 @@ scan_number_done:
|
||||
break;
|
||||
}
|
||||
}
|
||||
bulk_consume(data, pos);
|
||||
ia.bulk_skip(pos);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
|
||||
@@ -532,7 +532,7 @@ class json_pointer
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<slot> table;
|
||||
std::vector<slot> slots;
|
||||
std::size_t used = 0;
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
@@ -547,19 +547,19 @@ class json_pointer
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
std::vector<slot> old(slots.size() * 2);
|
||||
old.swap(slots);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.token != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (table[i].token != nullptr)
|
||||
while (slots[i].token != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[i] = s;
|
||||
slots[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -572,7 +572,7 @@ class json_pointer
|
||||
|
||||
// start with the empty prefix only
|
||||
prefix_tree()
|
||||
: table(16)
|
||||
: slots(16)
|
||||
, parent(1, 0)
|
||||
, is_array(1, false)
|
||||
{}
|
||||
@@ -582,20 +582,20 @@ class json_pointer
|
||||
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||
{
|
||||
const std::size_t hash = hash_of(id, reference_token);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (table[i].token != nullptr)
|
||||
while (slots[i].token != nullptr)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
|
||||
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
||||
{
|
||||
return table[i].id;
|
||||
return slots[i].id;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
// new prefix
|
||||
const std::size_t child = parent.size();
|
||||
table[i] = slot(hash, id, &reference_token, child);
|
||||
slots[i] = slot(hash, id, &reference_token, child);
|
||||
if (reference_token == "0")
|
||||
{
|
||||
is_array[id] = true;
|
||||
@@ -604,7 +604,7 @@ class json_pointer
|
||||
is_array.push_back(false);
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > table.size())
|
||||
if (++used * 2 > slots.size())
|
||||
{
|
||||
grow();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_UNLOCKED_FILE_IO
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_DTOA_SSE2
|
||||
#undef JSON_DTOA_NEON
|
||||
|
||||
@@ -46,10 +47,6 @@
|
||||
#undef JSON_STRICT_BINARY_UTF8
|
||||
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#undef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
@@ -205,12 +204,24 @@ class binary_writer
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// a negative integer n is encoded as -1 - n
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
// one case for both, so that an array mixing negative
|
||||
// (number_integer) and non-negative (number_unsigned) values
|
||||
// does not mispredict the switch
|
||||
write_cbor_integer(j, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -245,7 +256,13 @@ class binary_writer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
write_cbor_array(j, depth);
|
||||
// step 1: write control byte and the array size
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -403,12 +420,54 @@ class binary_writer
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
// one case for both, so that an array mixing negative
|
||||
// (number_integer) and non-negative (number_unsigned) values
|
||||
// does not mispredict the switch
|
||||
write_msgpack_integer(j, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -426,7 +485,13 @@ class binary_writer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
write_msgpack_array(j, depth);
|
||||
// step 1: write control byte and the array size
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -759,142 +824,6 @@ class binary_writer
|
||||
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
/// how the range of integer type @a NumberType relates to that of @a TargetType
|
||||
template<typename TargetType, typename NumberType>
|
||||
struct integer_range
|
||||
{
|
||||
/// whether NumberType can hold values above the maximum of TargetType
|
||||
static constexpr bool may_exceed_max = std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits;
|
||||
/// whether NumberType can hold values below the minimum of TargetType
|
||||
static constexpr bool may_fall_below_min = std::numeric_limits<NumberType>::is_signed
|
||||
&& (!std::numeric_limits<TargetType>::is_signed || may_exceed_max);
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n does not exceed the maximum of @a TargetType
|
||||
|
||||
The binary formats store integers in at most 64 bits, but number_integer_t
|
||||
and number_unsigned_t may be wider (e.g., __int128). The range of the
|
||||
integer is taken from std::numeric_limits, so a number type whose range
|
||||
fits into @a TargetType is never checked: the function is then constant
|
||||
true, and the default int64_t/uint64_t types pay nothing.
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_max<TargetType>(n, std::integral_constant<bool, integer_range<TargetType, NumberType>::may_exceed_max>());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType /*unused*/, std::false_type /*may_exceed*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n, std::true_type /*may_exceed*/) noexcept
|
||||
{
|
||||
// NumberType has more digits than TargetType, so it can hold its maximum
|
||||
return n <= static_cast<NumberType>((std::numeric_limits<TargetType>::max)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n is not below the minimum of @a TargetType;
|
||||
constant true if NumberType cannot hold such a value
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n, std::integral_constant<bool, integer_range<TargetType, NumberType>::may_fall_below_min>());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType /*unused*/, std::false_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n, std::true_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
// NumberType is signed and either TargetType is unsigned (minimum 0)
|
||||
// or NumberType has more digits, so it can hold the minimum
|
||||
return n >= static_cast<NumberType>((std::numeric_limits<TargetType>::min)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n lies in the range of @a TargetType
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n) && integer_fits_max<TargetType>(n);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the non-negative integer @a n
|
||||
*/
|
||||
template<typename NumberType>
|
||||
static std::string integer_to_string(NumberType n, std::false_type /*is_signed*/)
|
||||
{
|
||||
std::string result;
|
||||
do
|
||||
{
|
||||
result.push_back(static_cast<char>('0' + (n % 10)));
|
||||
n = static_cast<NumberType>(n / 10);
|
||||
}
|
||||
while (n != 0);
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
||||
is negative
|
||||
*/
|
||||
template<typename NumberType>
|
||||
static std::string integer_to_string(NumberType n, std::true_type /*is_signed*/)
|
||||
{
|
||||
// the remainder of a negative n is not positive; each digit is
|
||||
// negated on its own, because negating the minimum of NumberType
|
||||
// as a whole would overflow
|
||||
const bool negative = n < 0;
|
||||
std::string result;
|
||||
do
|
||||
{
|
||||
const auto remainder = n % 10;
|
||||
result.push_back(static_cast<char>('0' + (remainder < 0 ? -remainder : remainder)));
|
||||
n = static_cast<NumberType>(n / 10);
|
||||
}
|
||||
while (n != 0);
|
||||
if (negative)
|
||||
{
|
||||
result.push_back('-');
|
||||
}
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format_name
|
||||
@throw out_of_range.407 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
|
||||
{
|
||||
// neither std::to_string(), which has no overload for integer types
|
||||
// wider than 64 bits, nor dump(), which would also instantiate the
|
||||
// serializer for objects and fail for key types it cannot write
|
||||
using integer_limits = std::numeric_limits<typename BasicJsonType::number_integer_t>;
|
||||
using unsigned_limits = std::numeric_limits<typename BasicJsonType::number_unsigned_t>;
|
||||
const std::string number = j.is_number_unsigned()
|
||||
? integer_to_string(j.m_data.m_value.number_unsigned, std::integral_constant<bool, unsigned_limits::is_signed>())
|
||||
: integer_to_string(j.m_data.m_value.number_integer, std::integral_constant<bool, integer_limits::is_signed>());
|
||||
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
static_cast<void>(range);
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
|
||||
}
|
||||
|
||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
@@ -1651,8 +1580,6 @@ class binary_writer
|
||||
is neither an object nor an array
|
||||
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
|
||||
into a byte, before anything is written
|
||||
@throw out_of_range.407 if @a j is an integer that does not fit the 64 bits
|
||||
of a BSON integer, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
@@ -1671,17 +1598,9 @@ class binary_writer
|
||||
return 8ul;
|
||||
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "int64");
|
||||
}
|
||||
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "uint64");
|
||||
}
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
@@ -1720,9 +1639,8 @@ class binary_writer
|
||||
case value_t::number_float:
|
||||
return write_bson_double(name, j.m_data.m_value.number_float);
|
||||
|
||||
// calc_bson_value_size() checked that integers fit 64 bits
|
||||
case value_t::number_integer:
|
||||
return write_bson_integer(name, static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
@@ -2033,376 +1951,6 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief size class of an unsigned integer from its bit length
|
||||
|
||||
1, 2, 3, or 4 if @a x fits 1, 2, 4, or 8 bytes. Looked up from the number
|
||||
of leading zero bits instead of found by branches: the sizes of a stream
|
||||
of numbers are hard to predict, and a mispredicted branch costs more than
|
||||
writing the number. (A table, as comparisons are vectorized by clang into
|
||||
slower code.)
|
||||
*/
|
||||
static unsigned integer_size_class(const std::uint64_t x) noexcept
|
||||
{
|
||||
static constexpr std::array<std::uint8_t, 64> size_class_by_leading_zeros =
|
||||
{
|
||||
{
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1, 1, 1
|
||||
}
|
||||
};
|
||||
// x | 1 keeps count_leading_zeros() away from 0
|
||||
return size_class_by_leading_zeros[static_cast<std::size_t>(count_leading_zeros(x | 1u))];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode @a first_byte and the low-order bytes of @a value that
|
||||
@a size_class calls for (none, or 1, 2, 4, or 8) in network order
|
||||
to @a out
|
||||
@return the number of characters of the encoding; up to
|
||||
@ref integer_encoding_space characters at @a out are overwritten
|
||||
|
||||
The bytes are written as one byte and one 8-byte word whose bytes after
|
||||
the value are scratch that the caller drops, so that writing them does not
|
||||
depend on the size class. For class 0 the word is all scratch; it is
|
||||
shifted by 60 bits rather than 64, which would be undefined.
|
||||
*/
|
||||
std::size_t encode_first_byte_and_tail(CharType* out, const std::uint8_t first_byte, const unsigned size_class, const std::uint64_t value) const noexcept
|
||||
{
|
||||
out[0] = to_char_type(first_byte);
|
||||
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
|
||||
const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits;
|
||||
std::memcpy(out + 1, &tail, sizeof(tail));
|
||||
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
||||
}
|
||||
|
||||
/// characters encode_first_byte_and_tail() may overwrite: the first byte
|
||||
/// and one 8-byte word
|
||||
static constexpr std::size_t integer_encoding_space = 9;
|
||||
|
||||
/*!
|
||||
@brief the 64 bits of a number_integer or number_unsigned value, and a mask
|
||||
that is all ones if it is negative, else zero
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
return raw;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||
size
|
||||
@return the number of characters of the head
|
||||
|
||||
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||
run of integers are not, so an integer's head is assembled without them.
|
||||
*/
|
||||
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||
{
|
||||
// an argument of at most 23 is in size class 1 (one byte) and is moved
|
||||
// to class 0, the first byte, by subtracting 1
|
||||
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||
const unsigned size_class = integer_size_class(argument) - is_immediate;
|
||||
|
||||
// the first byte: the argument itself if it is immediate, else
|
||||
// 0x17 + size_class, selected through a mask
|
||||
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||
const auto first_byte = static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask)));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an array value (head and elements) as CBOR
|
||||
|
||||
Integer elements, the most common scalar, are written without a call to
|
||||
@ref write_cbor. The loop is kept out of line: inlined into write_cbor, its
|
||||
registers would be saved and restored on every call of write_cbor, which
|
||||
is one call per value nested in an object.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
write_cbor_array_elements(j, depth, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
}
|
||||
|
||||
void write_cbor_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_cbor_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void write_cbor_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the elements of the array @a j, runs of integers with
|
||||
@a encode_integer and all other values with @a write_value
|
||||
|
||||
Runs of integers are encoded straight into space reserved from the sink
|
||||
for a whole run: the write pointer stays a local, where it would otherwise
|
||||
be reloaded from the sink for every value, as a store of a character may
|
||||
alias it.
|
||||
*/
|
||||
template<typename EncodeInteger, typename WriteValue>
|
||||
void write_array_elements(const BasicJsonType& j, EncodeInteger encode_integer, WriteValue write_value)
|
||||
{
|
||||
constexpr std::size_t max_run = max_reserve_size / integer_encoding_space;
|
||||
auto it = j.m_data.m_value.array->begin();
|
||||
const auto end = j.m_data.m_value.array->end();
|
||||
while (it != end)
|
||||
{
|
||||
if (it->is_number_integer())
|
||||
{
|
||||
CharType* const start = oa.reserve(max_run * integer_encoding_space);
|
||||
CharType* out = start;
|
||||
for (std::size_t n = 0; n < max_run && it != end && it->is_number_integer(); ++n, ++it)
|
||||
{
|
||||
out += encode_integer(out, *it);
|
||||
}
|
||||
oa.commit(static_cast<std::size_t>(out - start));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_value(*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a number_integer or number_unsigned value as a CBOR integer
|
||||
|
||||
A negative integer n has major type 1 and the argument -1 - n, which is ~n.
|
||||
Kept out of line, like the other per-value paths, so that write_cbor stays
|
||||
small (runs of integers in an array are encoded inline instead).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
oa.commit(encode_cbor_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// write a number_integer or number_unsigned value of a number type wider
|
||||
/// than 64 bits as a CBOR integer, with range checks on the whole value
|
||||
void write_cbor_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
if (j.m_data.m_type == value_t::number_unsigned)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "uint64");
|
||||
}
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// a negative integer n is encoded as -1 - n
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_cbor_integer) and return the number of characters
|
||||
std::size_t encode_cbor_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
// ~raw is -1 - n in the 64 bits that are written
|
||||
return encode_cbor_integer_head(out, static_cast<std::uint8_t>(sign_mask & 0x20u), raw ^ sign_mask);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether the CBOR and MessagePack integers of BasicJsonType are
|
||||
written by @ref encode_cbor_integer and @ref encode_msgpack_integer
|
||||
|
||||
These pick the format from the low 64 bits; a wider number type is written
|
||||
with range checks on the whole value instead.
|
||||
*/
|
||||
static constexpr bool integers_fit_64_bits() noexcept
|
||||
{
|
||||
return sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
&& sizeof(typename BasicJsonType::number_unsigned_t) <= sizeof(std::uint64_t);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a number_integer or number_unsigned value as a MessagePack
|
||||
integer without branching on its sign or size
|
||||
|
||||
Same output as @ref write_msgpack: a non-negative integer as positive fixint
|
||||
(< 128) or uint 8/16/32/64, a negative one as negative fixint (>= -32) or
|
||||
int 8/16/32/64. Kept out of line (see @ref write_cbor_integer).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
oa.commit(encode_msgpack_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_msgpack_integer) and return the number of characters
|
||||
std::size_t encode_msgpack_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
|
||||
// A negative n fits int 8/16/32 exactly if 2 * (-1 - n) + 1 fits
|
||||
// uint 8/16/32, so x gives the size class of either sign.
|
||||
const std::uint64_t is_negative = sign_mask & 1u;
|
||||
const std::uint64_t x = ((raw ^ sign_mask) << is_negative) | is_negative;
|
||||
|
||||
// A fixint is at most 127, or at least -32 (x at most 63). It is in
|
||||
// size class 1 and is moved to class 0, the first byte, by subtracting 1.
|
||||
const auto is_fixint = static_cast<unsigned>(x <= (127u ^ (sign_mask & 64u)));
|
||||
const unsigned size_class = integer_size_class(x) - is_fixint;
|
||||
|
||||
// the first byte: the low byte of the integer for a fixint, else the
|
||||
// marker 0xCC..0xCF (uint) or 0xD0..0xD3 (int), selected through a mask
|
||||
const std::uint64_t fixint_mask = std::uint64_t{0} - is_fixint;
|
||||
const std::uint64_t marker = 0xCBu + size_class + (sign_mask & 4u);
|
||||
const auto first_byte = static_cast<std::uint8_t>((raw & fixint_mask) | (marker & ~fixint_mask));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, raw);
|
||||
}
|
||||
|
||||
/// write a number_integer or number_unsigned value of a number type wider
|
||||
/// than 64 bits as a MessagePack integer, with range checks on the whole value
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
if (j.m_data.m_type == value_t::number_unsigned)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "uint64");
|
||||
}
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an array value (head and elements) as MessagePack
|
||||
|
||||
See @ref write_cbor_array.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
write_msgpack_array_elements(j, depth, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_msgpack_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an object key as a CBOR text string
|
||||
|
||||
@@ -2599,7 +2147,7 @@ class binary_writer
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value && integer_fits_max<std::uint64_t>(n))
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
@@ -2724,16 +2272,14 @@ class binary_writer
|
||||
@brief checks whether a JSON number fits into @a TargetType
|
||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||
@return whether @a el's value can be represented by @a TargetType without
|
||||
wrapping, regardless of which of the two kinds it is stored as;
|
||||
false for a value that does not even fit the 64-bit type it is
|
||||
read as (possible for number types wider than 64 bits)
|
||||
wrapping, regardless of which of the two kinds it is stored as
|
||||
*/
|
||||
template<typename TargetType>
|
||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||
{
|
||||
return el.is_number_unsigned()
|
||||
? integer_fits_max<std::uint64_t>(el.m_data.m_value.number_unsigned) && value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: integer_fits<std::int64_t>(el.m_data.m_value.number_integer) && value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2965,11 +2511,10 @@ class binary_writer
|
||||
for (const auto& el : dims)
|
||||
{
|
||||
// a dimension is read as an unsigned value below, so anything that
|
||||
// is not a non-negative integer in the range of std::uint64_t is
|
||||
// rejected: a non-integer entry would pun unrelated bytes as the
|
||||
// dimension, and a negative or wider one would wrap into a
|
||||
// nonsensical length
|
||||
if (!el.is_number_integer() || !bjdata_ndarray_value_in_range<std::uint64_t>(el))
|
||||
// is not a non-negative integer is rejected: a non-integer entry
|
||||
// would pun unrelated bytes as the dimension, and a negative one
|
||||
// would wrap into a nonsensical length
|
||||
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -3101,9 +2646,9 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
|
||||
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j));
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
@@ -3112,10 +2657,6 @@ class binary_writer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // conditional, integral_constant, is_same
|
||||
@@ -51,170 +48,32 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// the largest number of characters a writer may reserve() from a sink at once;
|
||||
/// output_vector_sink also appends writes of at least this size with insert()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
/// passed to binary_writer by value as a template parameter, so
|
||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||
/// vtable lookup and no shared_ptr. It is used for the common
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector.
|
||||
///
|
||||
/// Most writes are a few bytes - a type marker, or a number of 1, 2, 4, or 8
|
||||
/// bytes - and appending those with push_back() or insert() cost more than
|
||||
/// encoding them: insert() is an out-of-line call into a routine that can insert
|
||||
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
|
||||
/// its own write position: it grows the vector geometrically ahead of the
|
||||
/// position and copies each write to it, so a write is a capacity check and a
|
||||
/// short copy. The destructor shrinks the vector to the bytes actually written,
|
||||
/// also when a write throws; shrinking a vector never allocates. The sink owns
|
||||
/// that trim, so it can be moved but not copied, and the vector must not be used
|
||||
/// while the sink is alive.
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
||||
/// wraps this same sink to provide the virtual interface.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(&vec), position(vec.size())
|
||||
{}
|
||||
|
||||
output_vector_sink(const output_vector_sink&) = delete;
|
||||
output_vector_sink& operator=(const output_vector_sink&) = delete;
|
||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||
|
||||
output_vector_sink(output_vector_sink&& other) noexcept
|
||||
: v(other.v), position(other.position)
|
||||
{
|
||||
other.v = nullptr;
|
||||
}
|
||||
|
||||
~output_vector_sink()
|
||||
{
|
||||
if (v != nullptr)
|
||||
{
|
||||
v->resize(position);
|
||||
}
|
||||
}
|
||||
|
||||
// always inlined: GCC otherwise keeps these out of line in a large
|
||||
// translation unit, and a call per byte or number costs more than the write
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
(*v)[position++] = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values; memcpy must not
|
||||
// see it.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
if (length >= max_reserve_size)
|
||||
{
|
||||
append(s, length);
|
||||
return;
|
||||
}
|
||||
grow(length);
|
||||
}
|
||||
if (length != 0)
|
||||
{
|
||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
||||
position += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief space for writing up to @a length characters directly
|
||||
///
|
||||
/// Returns a pointer to at least @a length (at most @ref max_reserve_size)
|
||||
/// writable characters at the write position; @ref commit then keeps the
|
||||
/// first ones of them. A writer can store whole words there and keep only
|
||||
/// the bytes it needs, and keep its write pointer in a register across many
|
||||
/// values instead of updating the sink after each.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
return v->data() + position;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
position += length;
|
||||
}
|
||||
|
||||
private:
|
||||
/// make room for at least @a length more characters after the position;
|
||||
/// kept out of line so that the write functions stay small enough for
|
||||
/// GCC to inline them into the writer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void grow(const std::size_t length)
|
||||
{
|
||||
// use the capacity the vector already has, else at least double it;
|
||||
// never less than 64 characters, so that a small value does not
|
||||
// reallocate for every few bytes
|
||||
const std::size_t needed = position + length;
|
||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
}
|
||||
|
||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||
/// the space ahead of the position: insert() copies them once into the
|
||||
/// grown vector, where grow() would first zero the new space and then copy
|
||||
/// over it - noticeable for a large string or binary value
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void append(const CharType* s, const std::size_t length)
|
||||
{
|
||||
v->resize(position);
|
||||
if (v->capacity() - position < length)
|
||||
{
|
||||
// grow geometrically, with room for the writes that follow, so
|
||||
// that the next grow() need not reallocate right away
|
||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
||||
}
|
||||
v->insert(v->end(), s, s + length);
|
||||
position = v->size();
|
||||
}
|
||||
|
||||
std::vector<CharType, AllocatorType>* v;
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
void write_character(CharType c)
|
||||
{
|
||||
v.push_back(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values; appending an empty
|
||||
// range is a no-op
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
||||
// virtual base, and the concrete sink must do the same.
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
v.insert(v.end(), s, s + length);
|
||||
}
|
||||
@@ -223,6 +82,33 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
std::vector<CharType, AllocatorType>& v;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
///
|
||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
||||
/// type-erased and the templated path share one implementation.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: sink(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
sink.write_character(c);
|
||||
}
|
||||
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
sink.write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_vector_sink<CharType, AllocatorType> sink;
|
||||
};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// output adapter for output streams
|
||||
template<typename CharType>
|
||||
@@ -302,24 +188,8 @@ class output_adapter_sink
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
CharType* reserve(const std::size_t length) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
static_cast<void>(length);
|
||||
return reserved.data();
|
||||
}
|
||||
|
||||
/// write the first @a length characters written to the space from reserve()
|
||||
void commit(const std::size_t length)
|
||||
{
|
||||
oa->write_characters(reserved.data(), length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_adapter_t<CharType> oa;
|
||||
std::array<CharType, max_reserve_size> reserved{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
@@ -1525,7 +1525,7 @@ class serializer
|
||||
// without leading zeros
|
||||
if (abs_value < 10000000000000000u)
|
||||
{
|
||||
const auto value = static_cast<std::uint64_t>(abs_value); // fits: abs_value < 10^16
|
||||
const std::uint64_t value = abs_value;
|
||||
const std::uint64_t upper = value / 100000000u;
|
||||
const std::uint64_t first = dtoa_impl::eight_digit_bytes(upper != 0 ? upper : value);
|
||||
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_signed
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
@@ -207,16 +208,13 @@ FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noe
|
||||
|
||||
// values of IntegerType lie in [-bound, bound) when signed and in
|
||||
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a
|
||||
// power of two that the float represents exactly; the signedness comes
|
||||
// from numeric_limits as well, because std::is_signed is false for class
|
||||
// types such as 128-bit or multiprecision integers
|
||||
using limits = std::numeric_limits<IntegerType>;
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), limits::digits);
|
||||
// power of two that the float represents exactly
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits);
|
||||
if (f >= bound)
|
||||
{
|
||||
return ordered(-1);
|
||||
}
|
||||
if (limits::is_signed ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
{
|
||||
return ordered(1);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <cstring> // memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
@@ -56,16 +56,14 @@ struct document_data
|
||||
std::vector<object_index> indexes;
|
||||
std::vector<std::uint32_t> index_slots;
|
||||
std::vector<std::uint32_t> large_objects; ///< positions of the objects to index (noted while parsing)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena, unused (indexed by flags & node_flags::storage)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage)
|
||||
bool discarded = true;
|
||||
|
||||
/// The storage of edits (editable documents only; see edit_storage.hpp).
|
||||
/// No edit moves or resizes the parsed index, and the nodes of values
|
||||
/// written by edits never move either, so a view stays valid whenever it
|
||||
/// was taken, before the first edit or between later ones: an array/object
|
||||
/// whose elements change gets node_flags::moved, and its elements then live
|
||||
/// in a separate sequence (a header node, then the entries), whose entries
|
||||
/// link to the values.
|
||||
/// Edits never move or resize the parsed index, so views stay valid: an
|
||||
/// array/object whose elements change gets node_flags::moved, and its
|
||||
/// elements then live in a separate sequence (a header node, then the
|
||||
/// entries), whose entries link to the values.
|
||||
struct edit_state
|
||||
{
|
||||
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
|
||||
@@ -185,12 +183,6 @@ struct document_data
|
||||
return base[n.flags & node_flags::storage] + n.off;
|
||||
}
|
||||
|
||||
/// whether the string of node k (a key) is the n bytes at key
|
||||
bool key_equals(const node& k, const char* key, std::size_t n) const noexcept
|
||||
{
|
||||
return k.len == n && (n == 0 || std::memcmp(str(k), key, n) == 0);
|
||||
}
|
||||
|
||||
/// the node after n's subtree (containers span `next` nodes, scalars one)
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <cmath> // isinf, isnan
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
|
||||
#include <cstring> // memmove
|
||||
#include <cstring> // memcmp, memmove
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
|
||||
@@ -313,7 +313,7 @@ class editor
|
||||
|
||||
bool key_equals(const node& k, string_view_t key) const noexcept
|
||||
{
|
||||
return m_doc.key_equals(k, key.data(), key.size());
|
||||
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
||||
}
|
||||
|
||||
/// remove the members with this key (all, or all but the first) from an object
|
||||
|
||||
@@ -50,19 +50,7 @@ inline node* alloc_nodes(document_data& d, std::size_t k)
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k))
|
||||
{
|
||||
if (k >= e.chunk_next / 2)
|
||||
{
|
||||
// a large request (half of the next chunk or more) gets a chunk of
|
||||
// its own, so that the rest of the current chunk stays available
|
||||
// for smaller ones
|
||||
std::unique_ptr<node[]> own(new node[k]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(own));
|
||||
e.bytes += k * sizeof(node);
|
||||
return e.chunks.back().get();
|
||||
}
|
||||
// a smaller request leaves the rest of the current chunk unused
|
||||
// (fewer than k nodes, and k is less than half of the new chunk)
|
||||
const std::size_t count = e.chunk_next;
|
||||
const std::size_t count = (std::max)(k, e.chunk_next);
|
||||
std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(fresh));
|
||||
e.chunk_cur = e.chunks.back().get();
|
||||
@@ -93,8 +81,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
||||
}
|
||||
|
||||
/// copy n bytes into the edit arena and return their offset; a new buffer
|
||||
/// leaves the old one alive, so that string views into it remain valid; n is
|
||||
/// 0 for an empty string (written by an edit, or the key of a new member)
|
||||
/// leaves the old one alive, so that string views into it remain valid
|
||||
inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
@@ -102,7 +89,7 @@ inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
const std::size_t cap = text_capacity(e.text_cap, e.text_used, n);
|
||||
std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (NLOHMANN_VIEW_LIKELY(e.text_used != 0)) // (0 only for the first buffer, and then texts is empty)
|
||||
if (e.text_used != 0)
|
||||
{
|
||||
std::memcpy(fresh.get(), e.texts.back().get(), e.text_used);
|
||||
}
|
||||
@@ -140,10 +127,8 @@ inline void reserve_moved(document_data& d)
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
// both grow before either changes, so that the push_backs cannot throw
|
||||
// (both conditions are needed: reserve() may allocate more than asked,
|
||||
// so the two capacities can differ)
|
||||
e.moved.reserve((e.moved.size() * 2) + 16);
|
||||
e.moved_cap.reserve((e.moved.size() * 2) + 16);
|
||||
e.moved.reserve((2 * e.moved.size()) + 16);
|
||||
e.moved_cap.reserve((2 * e.moved.size()) + 16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min, sort
|
||||
#include <algorithm> // sort
|
||||
#include <array> // array
|
||||
#include <cstddef> // offsetof, size_t
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memcpy, memset
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
@@ -162,7 +162,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
copy_node(d.nodes);
|
||||
while (!stack.empty())
|
||||
{
|
||||
const frame top = stack.back();
|
||||
frame& top = stack.back();
|
||||
if (top.cur == top.end)
|
||||
{
|
||||
node& c = out[top.index];
|
||||
@@ -171,14 +171,20 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
++top.count;
|
||||
const node* v = nullptr;
|
||||
if (top.object)
|
||||
{
|
||||
out.push_back(string_node(*top.cur));
|
||||
v = document_data::deref(top.cur + 1);
|
||||
top.cur = document_data::after(top.cur + 1);
|
||||
}
|
||||
const node* const value = top.cur + (top.object ? 1 : 0);
|
||||
stack.back().cur = document_data::after(value);
|
||||
++stack.back().count;
|
||||
copy_node(document_data::deref(value)); // may grow the stack
|
||||
else
|
||||
{
|
||||
v = document_data::deref(top.cur);
|
||||
top.cur = document_data::after(top.cur);
|
||||
}
|
||||
copy_node(v); // may grow the stack (top is not used afterwards)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,15 +195,17 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
const std::size_t arena_size = d.arena_size;
|
||||
const node* nodes = d.nodes;
|
||||
std::size_t count = d.node_count;
|
||||
std::vector<node> compacted;
|
||||
std::string text_tail;
|
||||
std::string arena_tail;
|
||||
if (d.edits)
|
||||
{
|
||||
compact_nodes(d, arena_size, compacted, text_tail, arena_tail);
|
||||
nodes = compacted.data();
|
||||
count = compacted.size();
|
||||
}
|
||||
const node* const nodes = d.edits ? compacted.data() : d.nodes;
|
||||
const std::size_t count = d.edits ? compacted.size() : d.node_count;
|
||||
const std::size_t text_size = d.size + text_tail.size();
|
||||
const std::size_t total_arena = arena_size + arena_tail.size();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||
@@ -217,13 +225,14 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
std::memcpy(o, &h, sizeof(h));
|
||||
o += sizeof(h);
|
||||
std::memcpy(o, nodes, count * sizeof(node));
|
||||
// the hash indexes are rebuilt by load(), whose check rejects any other
|
||||
// extra of an object, and the same document saves to the same bytes
|
||||
// the hash indexes are rebuilt by load()
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0)
|
||||
{
|
||||
std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra));
|
||||
node n = nodes[i];
|
||||
n.extra = 0;
|
||||
std::memcpy(o + (i * sizeof(node)), &n, sizeof(node));
|
||||
}
|
||||
}
|
||||
o += count * sizeof(node);
|
||||
@@ -262,22 +271,21 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
const unsigned char* p = s;
|
||||
t.negative = p != e && *p == '-';
|
||||
p += t.negative ? 1 : 0;
|
||||
t.int_start = p;
|
||||
if (p == e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
t.int_start = p;
|
||||
if (*p == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
do
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -310,12 +318,11 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
t.exponent = t.exponent < 100000 ? (t.exponent * 10) + (*p - '0') : t.exponent;
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
t.exponent = exp_negative ? -t.exponent : t.exponent;
|
||||
t.is_float = true;
|
||||
}
|
||||
@@ -326,7 +333,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
/// writes "many" instead; the caller accepts both)
|
||||
inline std::uint16_t float_layout(const number_token& t)
|
||||
{
|
||||
return static_cast<std::uint16_t>((std::min)(t.int_digits, std::size_t{255}) | ((std::min)(t.frac_digits, std::size_t{255}) << 8u));
|
||||
return static_cast<std::uint16_t>((t.int_digits < 255 ? t.int_digits : 255) | ((t.frac_digits < 255 ? t.frac_digits : 255) << 8u));
|
||||
}
|
||||
|
||||
/// whether a float token (well-formed, per scan_number_token) is finite as
|
||||
@@ -728,10 +735,14 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
||||
// the hash indexes of large objects, as after parsing
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
const node& n = d.nodes[i];
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::object) && n.len >= document_data::index_min_members)
|
||||
node& n = d.nodes[i];
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::object))
|
||||
{
|
||||
d.large_objects.push_back(static_cast<std::uint32_t>(i));
|
||||
n.extra = 0;
|
||||
if (n.len >= document_data::index_min_members)
|
||||
{
|
||||
d.large_objects.push_back(static_cast<std::uint32_t>(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
build_object_indexes(d);
|
||||
|
||||
@@ -112,7 +112,7 @@ const node* find_member(const document_data& d, const node* object, const char*
|
||||
}
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (d.key_equals(*m, key, n))
|
||||
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ std::size_t count_members(const document_data& d, const node* object, const char
|
||||
const node* const end = nav::end(d, object);
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (d.key_equals(*m, key, n))
|
||||
if (m->len == n && (n == 0 || std::memcmp(d.str(*m), key, n) == 0))
|
||||
{
|
||||
++result;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct node_flags
|
||||
{
|
||||
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
||||
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
|
||||
static constexpr std::uint8_t storage = escaped | edited; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
||||
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
||||
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
||||
|
||||
@@ -45,11 +45,10 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
while (digit(p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
dot = std::string::npos;
|
||||
if (p != last && *p == '.')
|
||||
{
|
||||
@@ -58,11 +57,10 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
while (digit(p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
}
|
||||
mantissa_end = static_cast<std::size_t>(p - first);
|
||||
if (p != last && (*p == 'e' || *p == 'E'))
|
||||
@@ -73,11 +71,10 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
while (digit(p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
}
|
||||
return p == last;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t, uint64_t
|
||||
#include <cstring> // memcmp
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
@@ -93,7 +95,7 @@ inline void build_object_index(document_data& d, node* obj)
|
||||
while (table[slot] != 0)
|
||||
{
|
||||
const node* const other = obj + table[slot];
|
||||
if (d.key_equals(*other, key, k->len))
|
||||
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
||||
{
|
||||
duplicate = true; // keep the first
|
||||
break;
|
||||
@@ -140,7 +142,7 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
||||
return nullptr;
|
||||
}
|
||||
const node* const k = obj + s;
|
||||
if (d.key_equals(*k, key, n))
|
||||
if (k->len == n && (n == 0 || std::memcmp(d.str(*k), key, n) == 0))
|
||||
{
|
||||
return k;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,32 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
|
||||
/// powers of ten up to 10^19 as integers
|
||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
||||
{
|
||||
return ::nlohmann::detail::dtoa_impl::powers_of_ten()[k];
|
||||
static const std::array<std::uint64_t, 20> table =
|
||||
{
|
||||
{
|
||||
1u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u
|
||||
}
|
||||
};
|
||||
return table[k];
|
||||
}
|
||||
|
||||
/// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below
|
||||
|
||||
@@ -75,7 +75,7 @@ class output_buffer
|
||||
|
||||
/// a string literal, without its terminating NUL
|
||||
template<std::size_t N>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N])
|
||||
{
|
||||
put(s, N - 1);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ class output_buffer
|
||||
/// which a static constexpr member does not have before C++17)
|
||||
static constexpr std::size_t step() noexcept
|
||||
{
|
||||
return 0x10000;
|
||||
return 65536;
|
||||
}
|
||||
|
||||
static StringType& sized(StringType& out, std::size_t estimate)
|
||||
@@ -174,17 +174,14 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||
/// shallow documents need no allocation. The top is a pointer, as in
|
||||
/// std::vector. grow() is static and out of line: it gets the vector and the
|
||||
/// used range and returns the new storage, so the address of the stack never
|
||||
/// reaches a call. Otherwise the compiler would have to keep the three
|
||||
/// pointers in memory and load and store them on every push_back() and
|
||||
/// pop_back(), not just when growing.
|
||||
/// std::vector. The address of the stack never escapes (the growth gets the
|
||||
/// vector and returns the new storage), so its pointers stay in registers.
|
||||
template<typename T>
|
||||
class small_stack
|
||||
{
|
||||
public:
|
||||
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
|
||||
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(heap)
|
||||
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap)
|
||||
{}
|
||||
small_stack(const small_stack&) = delete;
|
||||
small_stack(small_stack&&) = delete;
|
||||
@@ -198,7 +195,7 @@ class small_stack
|
||||
{
|
||||
const std::size_t used = size();
|
||||
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
|
||||
m_begin = grow(m_heap, m_begin, used, capacity);
|
||||
m_begin = grow(*m_heap, m_begin, used, capacity);
|
||||
m_top = m_begin + used;
|
||||
m_end = m_begin + capacity;
|
||||
}
|
||||
@@ -238,18 +235,9 @@ class small_stack
|
||||
T* m_begin;
|
||||
T* m_top;
|
||||
T* m_end;
|
||||
std::vector<T>& m_heap;
|
||||
std::vector<T>* m_heap;
|
||||
};
|
||||
|
||||
/// a string literal at w, without its terminating NUL (after room())
|
||||
/// @return a pointer past it
|
||||
template<std::size_t N>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexcept // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
std::memcpy(w, s, N - 1);
|
||||
return w + N - 1;
|
||||
}
|
||||
|
||||
/// how the view's dump() writes a value
|
||||
struct dump_style
|
||||
{
|
||||
@@ -338,15 +326,15 @@ class view_serializer
|
||||
m_out.finish();
|
||||
return;
|
||||
}
|
||||
if (stack.back().pos == stack.back().end)
|
||||
frame& f = stack.back();
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
const bool object = stack.back().object;
|
||||
const bool object = f.object; // (f dangles after pop_back)
|
||||
stack.pop_back();
|
||||
newline(stack.size());
|
||||
m_out.put(object ? '}' : ']');
|
||||
continue;
|
||||
}
|
||||
frame& f = stack.back();
|
||||
if (!f.first)
|
||||
{
|
||||
m_out.put(',');
|
||||
@@ -473,6 +461,12 @@ class view_serializer
|
||||
}
|
||||
w += n;
|
||||
};
|
||||
// a literal of n bytes (after room(n))
|
||||
const auto literal = [&](const char* text, std::size_t n)
|
||||
{
|
||||
std::memcpy(w, text, n);
|
||||
w += n;
|
||||
};
|
||||
// a string that is not a plain string of the source (out of line, so
|
||||
// that the cursor stays in a register here)
|
||||
const auto escaped = [&](const node & n)
|
||||
@@ -558,11 +552,11 @@ class view_serializer
|
||||
room(8);
|
||||
if ((n->flags & node_flags::is_true) != 0)
|
||||
{
|
||||
w = put_literal(w, "true");
|
||||
literal("true", 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
w = put_literal(w, "false");
|
||||
literal("false", 5);
|
||||
}
|
||||
break;
|
||||
case value_t::object:
|
||||
@@ -572,7 +566,7 @@ class view_serializer
|
||||
room(8);
|
||||
if (n->len == 0)
|
||||
{
|
||||
w = object ? put_literal(w, "{}") : put_literal(w, "[]");
|
||||
literal(object ? "{}" : "[]", 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -592,7 +586,7 @@ class view_serializer
|
||||
}
|
||||
case value_t::null:
|
||||
room(8);
|
||||
w = put_literal(w, "null");
|
||||
literal("null", 4);
|
||||
break;
|
||||
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
||||
case value_t::discarded: // LCOV_EXCL_LINE
|
||||
@@ -772,7 +766,7 @@ class view_serializer
|
||||
// check also keeps an image that was only checked for bounds,
|
||||
// whose token may not be made of digits, from the counted
|
||||
// overload)
|
||||
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten();
|
||||
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten_16();
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count]
|
||||
? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(count), static_cast<int>(d.exponent))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // decay, enable_if, is_arithmetic, is_constructible, is_integral, is_same
|
||||
#include <type_traits> // enable_if, is_constructible
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -104,20 +104,6 @@ Map map_value(const View& v)
|
||||
template<typename Key>
|
||||
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
|
||||
|
||||
/// the arithmetic types arithmetic_value() converts to (bool has its own
|
||||
/// conversion)
|
||||
template<typename T>
|
||||
struct is_non_bool_arithmetic : conjunction<std::is_arithmetic<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// the default values of value() other than C strings, which have their own
|
||||
/// overload (returning string_t)
|
||||
template<typename T>
|
||||
struct is_non_c_string_default : negation<std::is_same<typename std::decay<T>::type, const char*>> {};
|
||||
|
||||
/// the integral types accepted as an array index by the edits of a document
|
||||
template<typename T>
|
||||
struct is_non_bool_integral : conjunction<std::is_integral<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// whether View::get<T>() converts to T without materialize(); get_to()
|
||||
/// assigns these, and passes the others to from_json
|
||||
template<typename T, typename BasicJsonType, typename View>
|
||||
|
||||
@@ -379,7 +379,7 @@ class basic_json_view
|
||||
/// the member with this key converted to T, or the default value if there
|
||||
/// is no such member (the first one, should the key occur more than
|
||||
/// once). Throws type_error.306 if this is not an object.
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(string_view_t key, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
@@ -398,7 +398,7 @@ class basic_json_view
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
/// value if the pointer cannot be resolved. Throws type_error.306 if this
|
||||
/// is neither an object nor an array.
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(const json_pointer& ptr, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
@@ -750,10 +750,8 @@ class basic_json_view
|
||||
{
|
||||
if (editable() && m_doc->edits != nullptr)
|
||||
{
|
||||
// positions of moved and new values are not source offsets; the root
|
||||
// (the first node) spans the source text and every edit
|
||||
const bool is_root = m_node == m_doc->nodes;
|
||||
return is_root ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
// positions of moved and new values are not source offsets
|
||||
return m_node == m_doc->nodes ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
}
|
||||
const node* const end = m_doc->nodes + m_doc->node_count;
|
||||
if ((m_node->flags & detail::view::node_flags::storage) != 0)
|
||||
@@ -800,7 +798,7 @@ class basic_json_view
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_bool_arithmetic<T>::value, int>::type = 0>
|
||||
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
@@ -1040,7 +1038,7 @@ class basic_json_document
|
||||
return m_data ? m_data->node_count : 0;
|
||||
}
|
||||
|
||||
/// bytes held by the document (index, decoded strings, and owned text or image)
|
||||
/// bytes held by the document (index, decoded strings, owned text or image)
|
||||
std::size_t memory_usage() const noexcept
|
||||
{
|
||||
if (!m_data)
|
||||
@@ -1188,7 +1186,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
view_type set(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().set(array, index(idx), std::forward<V>(value));
|
||||
@@ -1210,14 +1208,14 @@ class basic_json_document
|
||||
const auto& token = ptr.back();
|
||||
if (parent.is_null())
|
||||
{
|
||||
if (token == "-")
|
||||
{
|
||||
return push_back(parent, std::forward<V>(value));
|
||||
}
|
||||
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
});
|
||||
if (token == "-")
|
||||
{
|
||||
return push_back(parent, std::forward<V>(value));
|
||||
}
|
||||
if (digits)
|
||||
{
|
||||
// (an invalid index is an error before the parent changes)
|
||||
@@ -1255,7 +1253,7 @@ class basic_json_document
|
||||
|
||||
/// insert into an array before position idx (idx <= size()); returns a
|
||||
/// view of the new element
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
view_type insert(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().insert(array, index(idx), std::forward<V>(value));
|
||||
@@ -1268,7 +1266,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// remove an array element
|
||||
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
template < typename I, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
void erase(view_type array, I idx)
|
||||
{
|
||||
editor().erase(array, index(idx));
|
||||
|
||||
Generated
-454656
File diff suppressed because it is too large.
Load diff
Generated
+185
-1260
File diff suppressed because it is too large.
Load diff
Generated
+3
-25
@@ -71,14 +71,6 @@
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -127,18 +119,6 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||
#else
|
||||
@@ -156,9 +136,9 @@
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -170,8 +150,6 @@
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||
|
||||
|
||||
Generated
+187
-268
File diff suppressed because it is too large.
Load diff
+3
-67
@@ -2,14 +2,12 @@ cmake_minimum_required(VERSION 3.13...4.0)
|
||||
|
||||
option(JSON_Valgrind "Execute test suite with Valgrind." OFF)
|
||||
option(JSON_FastTests "Skip expensive/slow tests." OFF)
|
||||
option(JSON_TestUnityBuild "Build the unit tests in batches of several files per executable to speed up compilation." ON)
|
||||
option(JSON_TestSimdutf "Build the unit tests against the simdutf UTF-8 validation backend." OFF)
|
||||
|
||||
set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_TestSimdutf.")
|
||||
|
||||
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
||||
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
||||
set(JSON_TestUnityBatchSize 8 CACHE STRING "The number of unit test files compiled into one executable with JSON_TestUnityBuild.")
|
||||
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
||||
|
||||
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
||||
@@ -22,11 +20,6 @@ endif()
|
||||
|
||||
include(test)
|
||||
|
||||
# MinGW's GNU ld hits section and relocation limits with the large merged translation units
|
||||
if(MINGW)
|
||||
set(JSON_TestUnityBuild OFF)
|
||||
endif()
|
||||
|
||||
#############################################################################
|
||||
# override standard support
|
||||
#############################################################################
|
||||
@@ -292,10 +285,6 @@ if (JSON_TestSimdutf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# test-file_input checks from a second thread that parsing from a FILE* releases the file's lock
|
||||
find_package(Threads REQUIRED)
|
||||
json_test_set_test_options("test-file_input;test-file_input_unlocked_io_disabled" LINK_LIBRARIES Threads::Threads)
|
||||
|
||||
# *DO* use json_test_set_test_options() above this line
|
||||
|
||||
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
|
||||
@@ -333,28 +322,9 @@ if(NOT "${JSON_TestShard}" STREQUAL "")
|
||||
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
||||
endif()
|
||||
|
||||
# Explicit groups of test files (file names without "unit-" and ".cpp") for
|
||||
# JSON_TestUnityBuild: all files of a group are compiled into one translation
|
||||
# unit per C++ standard, so that related tests share their template
|
||||
# instantiations (all binary formats instantiate the same readers and
|
||||
# writers). Only add a group where this was measured to pay off: grouping
|
||||
# files that share little just creates one slow translation unit. Files in no
|
||||
# group are batched alphabetically by JSON_TestUnityBatchSize.
|
||||
set(json_test_unity_groups binary)
|
||||
set(json_test_unity_group_binary binary_formats binary_utf8_error_handler binary_writer_sinks bjdata bon8 bon8-cpp17 bson byte_container_with_subtype cbor msgpack msgpack-cpp17 ubjson)
|
||||
|
||||
if(JSON_TestUnityBuild)
|
||||
message(STATUS "Building the unit tests in batches of ${JSON_TestUnityBatchSize} files (JSON_TestUnityBuild)")
|
||||
json_test_add_unity_tests(FILES ${files} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
BATCH_SIZE ${JSON_TestUnityBatchSize}
|
||||
GROUPS ${json_test_unity_groups}
|
||||
VARIANT_FILES src/unit-comparison.cpp src/unit-comparison-cpp20.cpp src/unit-class_parser.cpp src/unit-diagnostic-positions.cpp
|
||||
)
|
||||
else()
|
||||
foreach(file ${files})
|
||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||
endforeach()
|
||||
endif()
|
||||
foreach(file ${files})
|
||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||
endforeach()
|
||||
|
||||
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
||||
# so its test targets must be built after generate_hedley_undef_checks.
|
||||
@@ -380,13 +350,6 @@ json_test_add_test_for(src/unit-comparison.cpp
|
||||
NAME test-comparison_legacy
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
json_test_set_test_options(test-comparison_legacy-cpp20
|
||||
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-comparison-cpp20.cpp
|
||||
NAME test-comparison_legacy-cpp20
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
||||
json_test_set_test_options(test-class_parser_diagnostic_positions
|
||||
@@ -406,33 +369,6 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test reading from a std::istream again without scanning the get area in bulk
|
||||
json_test_set_test_options(test-stream-bulk-scan_disabled
|
||||
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
|
||||
NAME test-stream-bulk-scan_disabled
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test reading from a std::istream again with JSON_PRECISE_STREAM_POSITION
|
||||
json_test_set_test_options(test-stream-bulk-scan_precise
|
||||
COMPILE_DEFINITIONS JSON_PRECISE_STREAM_POSITION=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
|
||||
NAME test-stream-bulk-scan_precise
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test reading from a FILE* again with the locking stdio functions
|
||||
json_test_set_test_options(test-file_input_unlocked_io_disabled
|
||||
COMPILE_DEFINITIONS JSON_DISABLE_UNLOCKED_FILE_IO=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-file_input.cpp
|
||||
NAME test-file_input_unlocked_io_disabled
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# the json_view parser again with the portable string scanning instead of
|
||||
# NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3)
|
||||
json_test_set_test_options(test-json_view_builder_portable
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
# Unit tests
|
||||
|
||||
The unit tests are in [`src/unit-*.cpp`](src) and use [doctest](https://github.com/doctest/doctest). Each file becomes
|
||||
one CTest test per C++ standard it is built for, named after the file: `src/unit-foo.cpp` becomes `test-foo_cpp11`.
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -DJSON_BuildTests=ON
|
||||
cmake --build build -j 10
|
||||
ctest --test-dir build -j 10
|
||||
```
|
||||
|
||||
The [CMake options](../docs/mkdocs/docs/integration/cmake.md) starting with `JSON_Test` (and `JSON_FastTests`,
|
||||
`JSON_Valgrind`) control the test build. The most relevant ones:
|
||||
|
||||
- `JSON_TestStandards`: build every test file for the given standards, e.g., `-DJSON_TestStandards=17`. By default,
|
||||
a file is built for C++11 and for each standard whose `JSON_HAS_CPP_<N>` macro it mentions (see below).
|
||||
- `JSON_TestUnityBuild`: compile several test files together (see below). Set it to `OFF` to get one executable per
|
||||
test file, e.g., to debug a single test in a debugger.
|
||||
- `JSON_TestShard`: build only a part of the test files, e.g., `-DJSON_TestShard=0/2`.
|
||||
|
||||
To run the tests of one file only, run its CTest test, e.g., `ctest --test-dir build -R test-foo_cpp11`.
|
||||
|
||||
## How test files are built
|
||||
|
||||
Two mechanisms keep the compile time down. Both are automatic, but they set a few rules for test files.
|
||||
|
||||
### C++ standards
|
||||
|
||||
A test file is built for a C++ standard beyond C++11 if its text contains `JSON_HAS_CPP_<N>`, for instance in
|
||||
`#ifdef JSON_HAS_CPP_17`, or in a comment `// JSON_HAS_CPP_17` next to code that needs a macro only defined for that
|
||||
standard, such as `JSON_HAS_FILESYSTEM`. Then the *whole file* is compiled again for that standard.
|
||||
|
||||
So that this does not happen for the large files, tests that need C++14, C++17, or C++20 go into a separate file
|
||||
`src/unit-<name>-cpp<N>.cpp` (e.g., [`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp)), whose body
|
||||
is wrapped in `#ifdef JSON_HAS_CPP_<N>`. The main file `src/unit-<name>.cpp` must not mention `JSON_HAS_CPP_<N>` at
|
||||
all, not even in a comment, so it is built for C++11 only. The CI jobs `ci_test_*_cxx<N>` still build every test file
|
||||
for every standard.
|
||||
|
||||
### Unity build
|
||||
|
||||
With `JSON_TestUnityBuild` (`ON` by default, `OFF` with MinGW), CMake compiles several test files as one translation
|
||||
unit, so they share the template instantiations of the library. CMake generates `build/tests/unity/test-unity-*.cpp`,
|
||||
which `#include` the test files of a batch. Every test file still gets its own CTest test, which runs the batch
|
||||
executable with `--source-file=*unit-foo.cpp`, so only the test cases of that file run.
|
||||
|
||||
CMake decides from the macros a file defines *before* including `<nlohmann/json.hpp>`:
|
||||
|
||||
| Macros defined before the include | Built as |
|
||||
|-------------------------------------------------------------------------------------------|-----------------------------------|
|
||||
| none (or only `SKIP_TESTS_FOR_*` and similar macros derived from global definitions) | batch with the other plain files |
|
||||
| only `JSON_TESTS_PRIVATE` | batch with the other such files |
|
||||
| any library configuration macro (`JSON_DIAGNOSTICS`, `JSON_NO_IO`, `JSON_ASSERT`, ...) | its own executable |
|
||||
|
||||
Files with test-specific build options (`json_test_set_test_options(test-foo ...)` in
|
||||
[`CMakeLists.txt`](CMakeLists.txt)) also get their own executable. The batchable files are split alphabetically into
|
||||
batches of `JSON_TestUnityBatchSize` files, except for explicit groups of related files: `json_test_unity_group_binary`
|
||||
compiles all binary-format tests together, because they share the binary readers and writers. Only add a group if it
|
||||
measurably pays off; files that share little just make one slow translation unit.
|
||||
|
||||
## Rules for test files
|
||||
|
||||
Because the files of a batch share one translation unit, a test file must not affect the files that follow it:
|
||||
|
||||
1. **Give file-scope helpers file-specific names.** An anonymous namespace does not help, as the batch is one
|
||||
translation unit. Use a name such as `my_allocator_2982`, or wrap the helpers in a namespace named after the file
|
||||
(e.g., `namespace unit_comparison_detail`). Clashes show up as compile errors.
|
||||
2. **Do not `#undef` library macros**, such as `JSON_HAS_CPP_17`, at the end of a file: this silently disables the tests
|
||||
that depend on them in the later files of the batch. `#undef` macros you define yourself after including the library.
|
||||
3. **Put version-dependent tests into `unit-<name>-cpp<N>.cpp`** (see [C++ standards](#c-standards)).
|
||||
4. **Define test cases in the `.cpp` file itself.** The batch executable selects the tests of a file by its name, so a
|
||||
`TEST_CASE` in a shared header is not run. The only exception is the test case in
|
||||
[`src/make_test_data_available.hpp`](src/make_test_data_available.hpp), which CMake handles explicitly.
|
||||
|
||||
To check a new file for clashes with all others at once, build with `-DJSON_TestUnityBatchSize=1000`, which puts all
|
||||
batchable files of a kind into one translation unit.
|
||||
|
||||
## Where to add tests
|
||||
|
||||
Tests are structured along the features of the library. Usually, an existing file is the right place:
|
||||
|
||||
- For a bug fix, add a section referencing the issue to [`src/unit-regression3.cpp`](src/unit-regression3.cpp), or to
|
||||
[`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp) /
|
||||
[`src/unit-regression3-cpp20.cpp`](src/unit-regression3-cpp20.cpp) if the test needs C++17 / C++20.
|
||||
[`src/unit-regression2.cpp`](src/unit-regression2.cpp) holds older tests and should not grow further.
|
||||
- When testing exceptions, use `CHECK_THROWS_WITH_AS`, which also checks the `what()` message.
|
||||
|
||||
A new file `src/unit-<name>.cpp` is picked up automatically when CMake runs again; no change to `CMakeLists.txt` is
|
||||
needed.
|
||||
|
||||
See also [`fuzzing.md`](fuzzing.md) for fuzz testing.
|
||||
@@ -22,8 +22,6 @@ foreach(test default noversion)
|
||||
JSON_DIAGNOSTICS=1
|
||||
JSON_DIAGNOSTIC_POSITIONS=1
|
||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||
JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||
JSON_DISABLE_UNLOCKED_FILE_IO=1
|
||||
JSON_VIEW_NO_SIMD
|
||||
JSON_VIEW_USE_SSSE3)
|
||||
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
||||
|
||||
@@ -52,14 +52,6 @@ TEST_CASE("default namespace")
|
||||
expected += "_ekmo";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
expected += "_dsbs";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
expected += "_dufio";
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
expected += "_vns";
|
||||
#endif
|
||||
|
||||
@@ -53,14 +53,6 @@ TEST_CASE("default namespace without version component")
|
||||
expected += "_ekmo";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
expected += "_dsbs";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
expected += "_dufio";
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
expected += "_vns";
|
||||
#endif
|
||||
|
||||
@@ -8,13 +8,11 @@
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <test_data.hpp>
|
||||
|
||||
@@ -53,65 +51,6 @@ BENCHMARK_CAPTURE(ParseFile, signed_ints, TEST_DATA_DIRECTORY "/regression
|
||||
BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
|
||||
BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// parse JSON from a FILE*
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ParseFILE(benchmark::State& state, const char* filename)
|
||||
{
|
||||
while (state.KeepRunning())
|
||||
{
|
||||
state.PauseTiming();
|
||||
std::FILE* f = std::fopen(filename, "rb");
|
||||
auto* j = new json();
|
||||
state.ResumeTiming();
|
||||
|
||||
*j = json::parse(f);
|
||||
|
||||
state.PauseTiming();
|
||||
std::fclose(f);
|
||||
delete j;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
state.SetBytesProcessed(state.iterations() * file.tellg());
|
||||
}
|
||||
BENCHMARK_CAPTURE(ParseFILE, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(ParseFILE, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(ParseFILE, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// read many small values from one stream (NDJSON)
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ParseStreamValues(benchmark::State& state, bool numbers)
|
||||
{
|
||||
std::string input;
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
input += numbers ? std::to_string(i * 7919) + " "
|
||||
: R"({"id":)" + std::to_string(i) + R"(,"name":"user)" + std::to_string(i) + R"(","active":true,"score":)" + std::to_string(i * 0.25) + "}\n";
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
std::istringstream is(input);
|
||||
json j;
|
||||
std::size_t count = 0;
|
||||
while (!(is >> std::ws).eof())
|
||||
{
|
||||
is >> j;
|
||||
++count;
|
||||
}
|
||||
benchmark::DoNotOptimize(count);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(static_cast<int64_t>(state.iterations() * input.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(ParseStreamValues, ndjson, false);
|
||||
BENCHMARK_CAPTURE(ParseStreamValues, numbers, true);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// parse JSON from string
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-allocator.cpp (std::string_view key
|
||||
// lookup with a failing allocator). It is kept in a separate translation unit so the (much
|
||||
// larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every C++
|
||||
// standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool next_construct_fails_cpp17 = false;
|
||||
bool next_destroy_fails_cpp17 = false;
|
||||
bool next_deallocate_fails_cpp17 = false;
|
||||
|
||||
template<class T>
|
||||
struct my_allocator_cpp17 : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class... Args>
|
||||
void construct(T* p, Args&& ... args)
|
||||
{
|
||||
if (next_construct_fails_cpp17)
|
||||
{
|
||||
next_construct_fails_cpp17 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (next_deallocate_fails_cpp17)
|
||||
{
|
||||
next_deallocate_fails_cpp17 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
void destroy(T* p)
|
||||
{
|
||||
if (next_destroy_fails_cpp17)
|
||||
{
|
||||
next_destroy_fails_cpp17 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
static_cast<void>(p); // fix MSVC's C4100 warning
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = my_allocator_cpp17<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||
// next_construct_fails_cpp17 set for the next allocation outside a check
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("a failed allocation leaves the value unchanged (C++17)")
|
||||
{
|
||||
// create JSON type using the throwing allocator
|
||||
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp17>;
|
||||
|
||||
SECTION("turning a null value into an array or object")
|
||||
{
|
||||
my_json j;
|
||||
|
||||
next_construct_fails_cpp17 = true;
|
||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails_cpp17 = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,115 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-allocator.cpp (creating a value from a
|
||||
// std::ranges view with a failing allocator). It is kept in a separate translation unit so
|
||||
// the (much larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every
|
||||
// C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool next_construct_fails_cpp20 = false;
|
||||
bool next_destroy_fails_cpp20 = false;
|
||||
bool next_deallocate_fails_cpp20 = false;
|
||||
|
||||
template<class T>
|
||||
struct my_allocator_cpp20 : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class... Args>
|
||||
void construct(T* p, Args&& ... args)
|
||||
{
|
||||
if (next_construct_fails_cpp20)
|
||||
{
|
||||
next_construct_fails_cpp20 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (next_deallocate_fails_cpp20)
|
||||
{
|
||||
next_deallocate_fails_cpp20 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
void destroy(T* p)
|
||||
{
|
||||
if (next_destroy_fails_cpp20)
|
||||
{
|
||||
next_destroy_fails_cpp20 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
static_cast<void>(p); // fix MSVC's C4100 warning
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = my_allocator_cpp20<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||
// next_construct_fails_cpp20 set for the next allocation outside a check
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("a failed allocation leaves the value unchanged (C++20)")
|
||||
{
|
||||
// create JSON type using the throwing allocator
|
||||
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp20>;
|
||||
|
||||
// With iterator debugging, VS 2015's containers construct a proxy with the
|
||||
// allocator in constructors that cannot report its failure, so a failing
|
||||
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||
SECTION("converting into an existing value")
|
||||
{
|
||||
// to_json replaces the value it is given; the old one must survive a
|
||||
// failed creation of the new one
|
||||
my_json j = "old";
|
||||
|
||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||
const std::vector<int> numbers = {1, 2};
|
||||
next_construct_fails_cpp20 = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||
{
|
||||
return true;
|
||||
})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
#endif
|
||||
|
||||
next_construct_fails_cpp20 = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -13,6 +13,9 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <valarray>
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -681,6 +684,12 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
#endif
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
@@ -778,6 +787,16 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||
const std::vector<int> numbers = {1, 2};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||
{
|
||||
return true;
|
||||
})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
#endif
|
||||
|
||||
next_construct_fails = false;
|
||||
nlohmann::to_json(j, std::vector<int> {1, 2});
|
||||
CHECK(j == my_json({1, 2}));
|
||||
|
||||
@@ -11,10 +11,8 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -37,17 +35,6 @@ std::vector<json> test_values()
|
||||
big_object[std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
// large strings and binary values between small ones: a write too large
|
||||
// for the space the vector sink has ahead takes a different path than a
|
||||
// small one, and the small writes after it must still land behind it
|
||||
json large_payloads = json::array();
|
||||
for (int i = 0; i < 50; ++i)
|
||||
{
|
||||
large_payloads.push_back(std::string(300, static_cast<char>('a' + (i % 26))));
|
||||
large_payloads.push_back(i);
|
||||
large_payloads.push_back(json::binary(std::vector<std::uint8_t>(static_cast<std::size_t>(100 * i), static_cast<std::uint8_t>(i))));
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
json(nullptr), json(true), json(false),
|
||||
@@ -59,23 +46,10 @@ std::vector<json> test_values()
|
||||
json::array(), json::object(),
|
||||
json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}),
|
||||
json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}),
|
||||
big_array, big_object, large_payloads,
|
||||
json::binary(std::vector<std::uint8_t>(5000, 0x42))
|
||||
big_array, big_object
|
||||
};
|
||||
}
|
||||
|
||||
// explicit char -> uint8_t conversion (the iterator-range constructor converts implicitly, which -fsanitize=integer flags)
|
||||
std::vector<std::uint8_t> to_bytes(const std::string& str)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(str.size());
|
||||
for (const char c : str)
|
||||
{
|
||||
result.push_back(static_cast<std::uint8_t>(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range, so to_bon8() rejects them
|
||||
bool bon8_representable(const json& j)
|
||||
{
|
||||
@@ -196,258 +170,6 @@ TEST_CASE("binary writer output sinks")
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the CBOR head of an integer, encoded independently of binary_writer
|
||||
void reference_cbor_head(std::vector<std::uint8_t>& out, const std::uint8_t major_type, const std::uint64_t argument)
|
||||
{
|
||||
if (argument <= 23)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(major_type + argument));
|
||||
return;
|
||||
}
|
||||
|
||||
int length = 8;
|
||||
std::uint8_t additional = 27;
|
||||
if (argument <= 0xFF)
|
||||
{
|
||||
length = 1;
|
||||
additional = 24;
|
||||
}
|
||||
else if (argument <= 0xFFFF)
|
||||
{
|
||||
length = 2;
|
||||
additional = 25;
|
||||
}
|
||||
else if (argument <= 0xFFFFFFFF)
|
||||
{
|
||||
length = 4;
|
||||
additional = 26;
|
||||
}
|
||||
|
||||
out.push_back(static_cast<std::uint8_t>(major_type + additional));
|
||||
for (int i = length - 1; i >= 0; --i)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(argument >> (8 * i)));
|
||||
}
|
||||
}
|
||||
|
||||
// the CBOR encoding of an integer value
|
||||
void reference_cbor_integer(std::vector<std::uint8_t>& out, const json& j)
|
||||
{
|
||||
if (j.is_number_unsigned())
|
||||
{
|
||||
reference_cbor_head(out, 0x00, j.get<std::uint64_t>());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto n = j.get<std::int64_t>();
|
||||
if (n >= 0)
|
||||
{
|
||||
reference_cbor_head(out, 0x00, static_cast<std::uint64_t>(n));
|
||||
}
|
||||
else
|
||||
{
|
||||
reference_cbor_head(out, 0x20, static_cast<std::uint64_t>(-1 - n));
|
||||
}
|
||||
}
|
||||
|
||||
// the MessagePack encoding of an integer value, encoded independently of
|
||||
// binary_writer
|
||||
void reference_msgpack_integer(std::vector<std::uint8_t>& out, const json& j)
|
||||
{
|
||||
const auto put = [&out](const std::uint8_t marker, const std::uint64_t bits, const int length)
|
||||
{
|
||||
out.push_back(marker);
|
||||
for (int i = length - 1; i >= 0; --i)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(bits >> (8 * i)));
|
||||
}
|
||||
};
|
||||
|
||||
if (j.is_number_unsigned() || j.get<std::int64_t>() >= 0)
|
||||
{
|
||||
const auto u = j.get<std::uint64_t>();
|
||||
if (u < 128)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(u));
|
||||
}
|
||||
else if (u <= 0xFF)
|
||||
{
|
||||
put(0xCC, u, 1);
|
||||
}
|
||||
else if (u <= 0xFFFF)
|
||||
{
|
||||
put(0xCD, u, 2);
|
||||
}
|
||||
else if (u <= 0xFFFFFFFF)
|
||||
{
|
||||
put(0xCE, u, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
put(0xCF, u, 8);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto n = j.get<std::int64_t>();
|
||||
const auto bits = static_cast<std::uint64_t>(n);
|
||||
if (n >= -32)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(bits));
|
||||
}
|
||||
else if (n >= -128)
|
||||
{
|
||||
put(0xD0, bits, 1);
|
||||
}
|
||||
else if (n >= -32768)
|
||||
{
|
||||
put(0xD1, bits, 2);
|
||||
}
|
||||
else if (n >= -2147483648LL)
|
||||
{
|
||||
put(0xD2, bits, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
put(0xD3, bits, 8);
|
||||
}
|
||||
}
|
||||
|
||||
// integers at every boundary of the CBOR and MessagePack sizes, signed and
|
||||
// unsigned
|
||||
std::vector<json> boundary_integers()
|
||||
{
|
||||
std::vector<json> result;
|
||||
const std::array<std::uint64_t, 38> boundaries =
|
||||
{
|
||||
{
|
||||
0, 1, 22, 23, 24, 25, 31, 32, 33, 126, 127, 128, 129, 254, 255, 256, 257,
|
||||
32766, 32767, 32768, 32769, 65534, 65535, 65536, 65537,
|
||||
2147483646ULL, 2147483647ULL, 2147483648ULL, 2147483649ULL,
|
||||
4294967294ULL, 4294967295ULL, 4294967296ULL, 4294967297ULL,
|
||||
9223372036854775806ULL, 9223372036854775807ULL, 9223372036854775808ULL,
|
||||
18446744073709551614ULL, 18446744073709551615ULL
|
||||
}
|
||||
};
|
||||
for (const std::uint64_t u : boundaries)
|
||||
{
|
||||
result.emplace_back(u);
|
||||
if (u <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
const auto n = static_cast<std::int64_t>(u);
|
||||
result.emplace_back(n);
|
||||
result.emplace_back(-n);
|
||||
result.emplace_back(-n - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Arrays of integers are written in runs straight into space reserved from the
|
||||
// sink, with branch-free encodings; check them against independent encoders for
|
||||
// runs shorter and longer than one reservation, broken up by other values, and
|
||||
// through each sink.
|
||||
TEST_CASE("CBOR and MessagePack integer arrays")
|
||||
{
|
||||
const std::vector<json> integers = boundary_integers();
|
||||
|
||||
for (const std::size_t length :
|
||||
{
|
||||
std::size_t{1}, std::size_t{27}, std::size_t{28}, std::size_t{29}, std::size_t{57}, std::size_t{1000}
|
||||
})
|
||||
{
|
||||
for (const std::size_t break_every :
|
||||
{
|
||||
std::size_t{0}, std::size_t{1}, std::size_t{7}, std::size_t{28}
|
||||
})
|
||||
{
|
||||
CAPTURE(length)
|
||||
CAPTURE(break_every)
|
||||
|
||||
json array = json::array();
|
||||
std::vector<std::uint8_t> expected_cbor;
|
||||
std::vector<std::uint8_t> expected_msgpack;
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
{
|
||||
if (break_every != 0 && i % (break_every + 1) == break_every)
|
||||
{
|
||||
array.push_back("x");
|
||||
expected_cbor.push_back(0x61);
|
||||
expected_cbor.push_back('x');
|
||||
expected_msgpack.push_back(0xA1);
|
||||
expected_msgpack.push_back('x');
|
||||
}
|
||||
else
|
||||
{
|
||||
const json& value = integers[(i * 7) % integers.size()];
|
||||
array.push_back(value);
|
||||
reference_cbor_integer(expected_cbor, value);
|
||||
reference_msgpack_integer(expected_msgpack, value);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> head;
|
||||
reference_cbor_head(head, 0x80, length);
|
||||
expected_cbor.insert(expected_cbor.begin(), head.begin(), head.end());
|
||||
|
||||
head.clear();
|
||||
if (length <= 15)
|
||||
{
|
||||
head.push_back(static_cast<std::uint8_t>(0x90 + length));
|
||||
}
|
||||
else
|
||||
{
|
||||
head = {0xDC, static_cast<std::uint8_t>(length >> 8), static_cast<std::uint8_t>(length)};
|
||||
}
|
||||
expected_msgpack.insert(expected_msgpack.begin(), head.begin(), head.end());
|
||||
|
||||
CHECK(json::to_cbor(array) == expected_cbor);
|
||||
CHECK(json::to_msgpack(array) == expected_msgpack);
|
||||
|
||||
std::vector<std::uint8_t> through_adapter = {0xAA};
|
||||
json::to_cbor(array, through_adapter);
|
||||
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected_cbor);
|
||||
through_adapter = {0xAA};
|
||||
json::to_msgpack(array, through_adapter);
|
||||
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected_msgpack);
|
||||
|
||||
std::ostringstream stream;
|
||||
json::to_cbor(array, stream);
|
||||
std::string streamed = stream.str();
|
||||
CHECK(to_bytes(streamed) == expected_cbor);
|
||||
stream.str("");
|
||||
json::to_msgpack(array, stream);
|
||||
streamed = stream.str();
|
||||
CHECK(to_bytes(streamed) == expected_msgpack);
|
||||
|
||||
CHECK(json::from_cbor(expected_cbor) == array);
|
||||
CHECK(json::from_msgpack(expected_msgpack) == array);
|
||||
|
||||
// the same array as object members
|
||||
const json object = {{"a", array}, {"b", array}};
|
||||
CHECK(json::from_cbor(json::to_cbor(object)) == object);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(object)) == object);
|
||||
}
|
||||
}
|
||||
|
||||
// single values, outside an array
|
||||
for (const auto& value : integers)
|
||||
{
|
||||
CAPTURE(value)
|
||||
std::vector<std::uint8_t> expected;
|
||||
reference_cbor_integer(expected, value);
|
||||
CHECK(json::to_cbor(value) == expected);
|
||||
expected.clear();
|
||||
reference_msgpack_integer(expected, value);
|
||||
CHECK(json::to_msgpack(value) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// binary_reserve_hint() is documented as a *lower* bound on the serialized size,
|
||||
// so that reserving it up front can never leave the returned vector holding
|
||||
// capacity beyond what the value actually needs.
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-bon8.cpp (BON8 with std::byte
|
||||
// containers). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// bon8.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("BON8 with std::byte")
|
||||
{
|
||||
SECTION("vector roundtrip")
|
||||
{
|
||||
const json original =
|
||||
{
|
||||
{"name", "test"},
|
||||
{"value", 42},
|
||||
{"array", {1, 2, 3}}
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||
std::vector<std::byte> bon8_data(temp.size());
|
||||
for (size_t i = 0; i < temp.size(); ++i)
|
||||
{
|
||||
bon8_data[i] = std::byte(temp[i]);
|
||||
}
|
||||
|
||||
json from_bytes;
|
||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||
CHECK(from_bytes == original);
|
||||
}
|
||||
|
||||
SECTION("empty vector")
|
||||
{
|
||||
const std::vector<std::byte> empty_data;
|
||||
CHECK_THROWS_WITH_AS([&]()
|
||||
{
|
||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||
return true;
|
||||
}
|
||||
(),
|
||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||
json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1099,3 +1099,41 @@ TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
TEST_CASE("BON8 with std::byte")
|
||||
{
|
||||
SECTION("vector roundtrip")
|
||||
{
|
||||
const json original =
|
||||
{
|
||||
{"name", "test"},
|
||||
{"value", 42},
|
||||
{"array", {1, 2, 3}}
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||
std::vector<std::byte> bon8_data(temp.size());
|
||||
for (size_t i = 0; i < temp.size(); ++i)
|
||||
{
|
||||
bon8_data[i] = std::byte(temp[i]);
|
||||
}
|
||||
|
||||
json from_bytes;
|
||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||
CHECK(from_bytes == original);
|
||||
}
|
||||
|
||||
SECTION("empty vector")
|
||||
{
|
||||
const std::vector<std::byte> empty_data;
|
||||
CHECK_THROWS_WITH_AS([&]()
|
||||
{
|
||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||
return true;
|
||||
}
|
||||
(),
|
||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||
json::parse_error&);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,526 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-comparison.cpp (tests of operator<=> and
|
||||
// other three-way comparison specific behavior). It is kept in a separate translation unit
|
||||
// so the (much larger) unit-comparison.cpp is built for C++11 only and not rebuilt for
|
||||
// every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
#include <cmath>
|
||||
#include <compare>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// this can be replaced with the doctest stl extension header in version 2.5
|
||||
namespace doctest
|
||||
{
|
||||
template<> struct StringMaker<std::partial_ordering>
|
||||
{
|
||||
static String convert(const std::partial_ordering& order)
|
||||
{
|
||||
if (order == std::partial_ordering::less)
|
||||
{
|
||||
return "std::partial_ordering::less";
|
||||
}
|
||||
if (order == std::partial_ordering::equivalent)
|
||||
{
|
||||
return "std::partial_ordering::equivalent";
|
||||
}
|
||||
if (order == std::partial_ordering::greater)
|
||||
{
|
||||
return "std::partial_ordering::greater";
|
||||
}
|
||||
if (order == std::partial_ordering::unordered)
|
||||
{
|
||||
return "std::partial_ordering::unordered";
|
||||
}
|
||||
return "{?}";
|
||||
}
|
||||
};
|
||||
} // namespace doctest
|
||||
|
||||
TEST_CASE("lexicographical comparison operators (C++20)")
|
||||
{
|
||||
constexpr auto f_ = false;
|
||||
constexpr auto _t = true;
|
||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||
constexpr auto lt = std::partial_ordering::less;
|
||||
constexpr auto gt = std::partial_ordering::greater;
|
||||
constexpr auto eq = std::partial_ordering::equivalent;
|
||||
constexpr auto un = std::partial_ordering::unordered;
|
||||
|
||||
INFO("using 3-way comparison");
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
INFO("using legacy comparison");
|
||||
#endif
|
||||
|
||||
SECTION("types")
|
||||
{
|
||||
std::vector<json::value_t> j_types =
|
||||
{
|
||||
json::value_t::null,
|
||||
json::value_t::boolean,
|
||||
json::value_t::number_integer,
|
||||
json::value_t::number_unsigned,
|
||||
json::value_t::number_float,
|
||||
json::value_t::object,
|
||||
json::value_t::array,
|
||||
json::value_t::string,
|
||||
json::value_t::binary,
|
||||
json::value_t::discarded
|
||||
};
|
||||
|
||||
std::vector<std::vector<bool>> expected_lt =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9
|
||||
{f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1
|
||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2
|
||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3
|
||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4
|
||||
{f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5
|
||||
{f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
||||
};
|
||||
|
||||
SECTION("comparison: less")
|
||||
{
|
||||
for (size_t i = 0; i < j_types.size(); ++i)
|
||||
{
|
||||
for (size_t j = 0; j < j_types.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
// check precomputed values
|
||||
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
||||
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
||||
REQUIRE(std::isnan(nan));
|
||||
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9
|
||||
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
||||
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
||||
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
||||
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
||||
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
||||
{un, un, un, un, un, un, un, un, un, un}, // 9
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected boolean
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
// check 3-way comparison against expected partial_ordering
|
||||
REQUIRE(expected.size() == j_types.size());
|
||||
for (size_t i = 0; i < j_types.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_types.size());
|
||||
for (size_t j = 0; j < j_types.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values")
|
||||
{
|
||||
json j_values =
|
||||
{
|
||||
nullptr, nullptr, // 0 1
|
||||
-17, 42, // 2 3
|
||||
8u, 13u, // 4 5
|
||||
3.14159, 23.42, // 6 7
|
||||
nan, nan, // 8 9
|
||||
"foo", "bar", // 10 11
|
||||
true, false, // 12 13
|
||||
{1, 2, 3}, {"one", "two", "three"}, // 14 15
|
||||
{{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17
|
||||
json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19
|
||||
json(json::value_t::discarded), json(json::value_t::discarded) // 20 21
|
||||
};
|
||||
|
||||
std::vector<std::vector<bool>> expected_eq =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
|
||||
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
|
||||
{f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
|
||||
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
|
||||
{f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
|
||||
{f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
|
||||
{f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
|
||||
{f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
||||
};
|
||||
|
||||
std::vector<std::vector<bool>> expected_lt =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1
|
||||
{f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3
|
||||
{f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4
|
||||
{f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5
|
||||
{f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6
|
||||
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
||||
};
|
||||
|
||||
SECTION("signed/unsigned mixed comparison above INT64_MAX")
|
||||
{
|
||||
const json above_int64_max = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL;
|
||||
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||
const json negative_one = -1;
|
||||
const json one = 1;
|
||||
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||
|
||||
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
}
|
||||
|
||||
SECTION("integer/float mixed comparison is exact")
|
||||
{
|
||||
// Widening the integer to a double loses precision past the
|
||||
// mantissa, so 2^63-2 and 2^63-1 both used to compare equal to the
|
||||
// double 2^63 while differing from each other. That makes equality
|
||||
// intransitive and the ordering not a strict weak ordering.
|
||||
const json below_two_63 = static_cast<std::int64_t>(9223372036854775806LL);
|
||||
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||
const json two_63 = 9223372036854775808.0;
|
||||
|
||||
// the same past the unsigned range
|
||||
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||
const json two_64 = 18446744073709551616.0;
|
||||
|
||||
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
}
|
||||
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
||||
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
||||
REQUIRE(std::isnan(nan));
|
||||
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
||||
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
||||
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
||||
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
||||
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
||||
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
||||
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected booleans
|
||||
REQUIRE(expected.size() == expected_eq.size());
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_eq[i].size());
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
if (std::is_gt(expected[i][j]))
|
||||
{
|
||||
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check that two values compare according to their expected ordering
|
||||
REQUIRE(expected.size() == j_values.size());
|
||||
for (size_t i = 0; i < j_values.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_values.size());
|
||||
for (size_t j = 0; j < j_values.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
||||
{
|
||||
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
||||
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
||||
// Verify that heterogeneous comparisons now work.
|
||||
|
||||
SECTION("string vs json")
|
||||
{
|
||||
std::string s = "string";
|
||||
json j = "string";
|
||||
CHECK(s == j);
|
||||
CHECK(j == s);
|
||||
CHECK_FALSE(s != j);
|
||||
CHECK_FALSE(j != s);
|
||||
}
|
||||
|
||||
SECTION("other heterogeneous types")
|
||||
{
|
||||
int i = 42;
|
||||
json j = 42;
|
||||
CHECK(i == j);
|
||||
CHECK(j == i);
|
||||
CHECK_FALSE(i != j);
|
||||
CHECK_FALSE(j != i);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||
{
|
||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||
// other three operand orders.
|
||||
const json discarded(json::value_t::discarded);
|
||||
const json one = 1;
|
||||
|
||||
CHECK(discarded <= 1);
|
||||
CHECK(discarded >= 1);
|
||||
CHECK(one <= discarded);
|
||||
CHECK(one >= discarded);
|
||||
CHECK(1 <= discarded);
|
||||
CHECK(1 >= discarded);
|
||||
CHECK(1.5 <= discarded);
|
||||
CHECK(1.5 >= discarded);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("containers are compared element by element (C++20)")
|
||||
{
|
||||
// Containers nested deeper than a bound are compared without the call
|
||||
// stack, by code of their own; every relation is checked both at the top
|
||||
// level and below that bound.
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
// objects with different keys
|
||||
{
|
||||
const json a = deep({{"a", 1}}, depth);
|
||||
const json b = deep({{"b", 1}}, depth);
|
||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
}
|
||||
|
||||
// a container that is a prefix of the other one
|
||||
{
|
||||
// the one that runs out of elements first is the smaller one
|
||||
const json shorter = deep({1}, depth);
|
||||
const json longer = deep({1, 2}, depth);
|
||||
|
||||
const json smaller_object = deep({{"a", 1}}, depth);
|
||||
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||
}
|
||||
|
||||
// elements that cannot be ordered
|
||||
{
|
||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
const json lhs = deep({nan, 1}, depth);
|
||||
const json rhs = deep({nan, 2}, depth);
|
||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||
// does, and operator< is derived from it
|
||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK_FALSE(lhs < rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||
{
|
||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||
// pair of binary values with the same bytes but a different subtype is
|
||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||
// that a NaN has. Within the nesting bound, an array compares itself
|
||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||
// undecided and lets the next element decide, same as
|
||||
// std::lexicographical_compare_three_way does. Past the bound,
|
||||
// compare_iteratively<true>() takes over and must classify the pair the
|
||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||
// from it - depends on how deeply the values are nested.
|
||||
const json a = json::array({json::binary({1}, 1), 1});
|
||||
const json b = json::array({json::binary({1}, 2), 2});
|
||||
|
||||
// the root inconsistency: unequal, yet <=>-equivalent
|
||||
CHECK_FALSE(a[0] == b[0]);
|
||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||
// and must still agree with the levels that do
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json x = deep(a, depth);
|
||||
const json y = deep(b, depth);
|
||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK(x < y);
|
||||
CHECK(y > x);
|
||||
CHECK_FALSE(y < x);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("comparison of scalars (inline fast path): operator<=>")
|
||||
{
|
||||
// the three-way part of "comparison of scalars (inline fast path)" in
|
||||
// unit-comparison.cpp, which is built for C++11 only
|
||||
const json::number_integer_t neg = -1;
|
||||
|
||||
SECTION("NaN")
|
||||
{
|
||||
const json nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||
const json one = 1.0;
|
||||
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
|
||||
}
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
const json d(json::value_t::discarded);
|
||||
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||
}
|
||||
}
|
||||
|
||||
#endif // JSON_HAS_THREE_WAY_COMPARISON
|
||||
#endif
|
||||
+356
-59
@@ -6,11 +6,12 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-comparison-cpp<NN>.cpp. This file stays C++11-only.
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -28,6 +29,37 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// this can be replaced with the doctest stl extension header in version 2.5
|
||||
namespace doctest
|
||||
{
|
||||
template<> struct StringMaker<std::partial_ordering>
|
||||
{
|
||||
static String convert(const std::partial_ordering& order)
|
||||
{
|
||||
if (order == std::partial_ordering::less)
|
||||
{
|
||||
return "std::partial_ordering::less";
|
||||
}
|
||||
if (order == std::partial_ordering::equivalent)
|
||||
{
|
||||
return "std::partial_ordering::equivalent";
|
||||
}
|
||||
if (order == std::partial_ordering::greater)
|
||||
{
|
||||
return "std::partial_ordering::greater";
|
||||
}
|
||||
if (order == std::partial_ordering::unordered)
|
||||
{
|
||||
return "std::partial_ordering::unordered";
|
||||
}
|
||||
return "{?}";
|
||||
}
|
||||
};
|
||||
} // namespace doctest
|
||||
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// helper function to check std::less<json::value_t>
|
||||
@@ -44,6 +76,16 @@ TEST_CASE("lexicographical comparison operators")
|
||||
constexpr auto f_ = false;
|
||||
constexpr auto _t = true;
|
||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
constexpr auto lt = std::partial_ordering::less;
|
||||
constexpr auto gt = std::partial_ordering::greater;
|
||||
constexpr auto eq = std::partial_ordering::equivalent;
|
||||
constexpr auto un = std::partial_ordering::unordered;
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
INFO("using 3-way comparison");
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
INFO("using legacy comparison");
|
||||
@@ -94,13 +136,62 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
// check precomputed values
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
||||
#else
|
||||
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||
#endif
|
||||
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9
|
||||
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
||||
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
||||
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
||||
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
||||
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
||||
{un, un, un, un, un, un, un, un, un, un}, // 9
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected boolean
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
// check 3-way comparison against expected partial_ordering
|
||||
REQUIRE(expected.size() == j_types.size());
|
||||
for (size_t i = 0; i < j_types.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_types.size());
|
||||
for (size_t j = 0; j < j_types.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("values")
|
||||
@@ -229,6 +320,18 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CHECK_FALSE(above_int64_max <= max_int64);
|
||||
CHECK(above_int64_max > max_int64);
|
||||
CHECK(above_int64_max >= max_int64);
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("integer/float mixed comparison is exact")
|
||||
@@ -279,6 +382,16 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CHECK_FALSE(json(1) < json(nan));
|
||||
CHECK_FALSE(json(nan) < json(1));
|
||||
CHECK_FALSE(json(1u) == json(nan));
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("compares unordered")
|
||||
@@ -505,6 +618,72 @@ TEST_CASE("lexicographical comparison operators")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
||||
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
||||
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
||||
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
||||
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
||||
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
||||
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected booleans
|
||||
REQUIRE(expected.size() == expected_eq.size());
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_eq[i].size());
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
if (std::is_gt(expected[i][j]))
|
||||
{
|
||||
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check that two values compare according to their expected ordering
|
||||
REQUIRE(expected.size() == j_values.size());
|
||||
for (size_t i = 0; i < j_values.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_values.size());
|
||||
for (size_t j = 0; j < j_values.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
@@ -543,7 +722,60 @@ TEST_CASE("lexicographical comparison operators")
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace unit_comparison_detail
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
|
||||
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
||||
{
|
||||
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
||||
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
||||
// Verify that heterogeneous comparisons now work.
|
||||
|
||||
SECTION("string vs json")
|
||||
{
|
||||
std::string s = "string";
|
||||
json j = "string";
|
||||
CHECK(s == j);
|
||||
CHECK(j == s);
|
||||
CHECK_FALSE(s != j);
|
||||
CHECK_FALSE(j != s);
|
||||
}
|
||||
|
||||
SECTION("other heterogeneous types")
|
||||
{
|
||||
int i = 42;
|
||||
json j = 42;
|
||||
CHECK(i == j);
|
||||
CHECK(j == i);
|
||||
CHECK_FALSE(i != j);
|
||||
CHECK_FALSE(j != i);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||
{
|
||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||
// other three operand orders.
|
||||
const json discarded(json::value_t::discarded);
|
||||
const json one = 1;
|
||||
|
||||
CHECK(discarded <= 1);
|
||||
CHECK(discarded >= 1);
|
||||
CHECK(one <= discarded);
|
||||
CHECK(one >= discarded);
|
||||
CHECK(1 <= discarded);
|
||||
CHECK(1 >= discarded);
|
||||
CHECK(1.5 <= discarded);
|
||||
CHECK(1.5 >= discarded);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// orders keys ascending or descending, as chosen when a map is created
|
||||
template<class Key>
|
||||
@@ -595,6 +827,29 @@ struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
|
||||
};
|
||||
using unordered_json = nlohmann::json::with_object_t<unordered_object_t>;
|
||||
|
||||
// the entries "0" to "9", enumerated in ascending or in descending order
|
||||
unordered_json make_unordered_object(const bool descending)
|
||||
{
|
||||
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
j[std::to_string(i)] = i;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
template<typename Json>
|
||||
Json nest(Json j, const std::size_t depth)
|
||||
{
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
Json outer = Json::object();
|
||||
outer["x"] = std::move(j);
|
||||
j = std::move(outer);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
// a std::map comparator with state: case-insensitive, unless constructed
|
||||
// case-sensitive. Used to check that copying an object copies the original's
|
||||
// comparator rather than default-constructing a new one (see #5649).
|
||||
@@ -623,6 +878,18 @@ template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
||||
using key_case_json = nlohmann::json::with_object_t<key_case_map>;
|
||||
|
||||
// the innermost value of a chain of single-element arrays
|
||||
template<typename Json>
|
||||
const Json& innermost(const Json& j)
|
||||
{
|
||||
const Json* p = &j;
|
||||
while (p->is_array())
|
||||
{
|
||||
p = &(*p)[0];
|
||||
}
|
||||
return *p;
|
||||
}
|
||||
|
||||
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
||||
// (neither less than the other) although they are not equal
|
||||
struct case_insensitive_less
|
||||
@@ -640,58 +907,7 @@ struct case_insensitive_less
|
||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
||||
using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
|
||||
|
||||
// the types above keep external linkage: a basic_json specialized with a type
|
||||
// of an anonymous namespace has internal linkage, which GCC reports in a file
|
||||
// #include-d into a batch (-Wsubobject-linkage); the functions need none
|
||||
namespace
|
||||
{
|
||||
// the entries "0" to "9", enumerated in ascending or in descending order
|
||||
unordered_json make_unordered_object(const bool descending)
|
||||
{
|
||||
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
j[std::to_string(i)] = i;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
template<typename Json>
|
||||
Json nest(Json j, const std::size_t depth)
|
||||
{
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
Json outer = Json::object();
|
||||
outer["x"] = std::move(j);
|
||||
j = std::move(outer);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
// the innermost value of a chain of single-element arrays
|
||||
template<typename Json>
|
||||
const Json& innermost(const Json& j)
|
||||
{
|
||||
const Json* p = &j;
|
||||
while (p->is_array())
|
||||
{
|
||||
p = &(*p)[0];
|
||||
}
|
||||
return *p;
|
||||
}
|
||||
} // namespace
|
||||
} // namespace unit_comparison_detail
|
||||
|
||||
// using-declarations rather than a using-directive, as this file may be
|
||||
// #include-d into a batch with other test files (JSON_TestUnityBuild)
|
||||
using unit_comparison_detail::ci_json;
|
||||
using unit_comparison_detail::innermost;
|
||||
using unit_comparison_detail::key_case_json;
|
||||
using unit_comparison_detail::key_case_less;
|
||||
using unit_comparison_detail::make_unordered_object;
|
||||
using unit_comparison_detail::nest;
|
||||
using unit_comparison_detail::unordered_json;
|
||||
|
||||
TEST_CASE("equality of objects whose entries have no fixed order")
|
||||
{
|
||||
@@ -834,6 +1050,12 @@ TEST_CASE("containers are compared element by element")
|
||||
CHECK(a < b);
|
||||
CHECK(b > a);
|
||||
CHECK_FALSE(b < a);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// a container that is a prefix of the other one
|
||||
@@ -851,6 +1073,11 @@ TEST_CASE("containers are compared element by element")
|
||||
CHECK(smaller_object < larger_object);
|
||||
CHECK(larger_object > smaller_object);
|
||||
CHECK_FALSE(smaller_object == larger_object);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// elements that cannot be ordered
|
||||
@@ -861,7 +1088,13 @@ TEST_CASE("containers are compared element by element")
|
||||
|
||||
CHECK_FALSE(lhs == lhs);
|
||||
CHECK_FALSE(rhs < lhs);
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||
// does, and operator< is derived from it
|
||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK_FALSE(lhs < rhs);
|
||||
#else
|
||||
// operator< skips a pair of elements that cannot be ordered, as
|
||||
// std::lexicographical_compare does, and the next pair decides
|
||||
CHECK(lhs < rhs);
|
||||
@@ -870,6 +1103,54 @@ TEST_CASE("containers are compared element by element")
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||
{
|
||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||
// pair of binary values with the same bytes but a different subtype is
|
||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||
// that a NaN has. Within the nesting bound, an array compares itself
|
||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||
// undecided and lets the next element decide, same as
|
||||
// std::lexicographical_compare_three_way does. Past the bound,
|
||||
// compare_iteratively<true>() takes over and must classify the pair the
|
||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||
// from it - depends on how deeply the values are nested.
|
||||
const json a = json::array({json::binary({1}, 1), 1});
|
||||
const json b = json::array({json::binary({1}, 2), 2});
|
||||
|
||||
// the root inconsistency: unequal, yet <=>-equivalent
|
||||
CHECK_FALSE(a[0] == b[0]);
|
||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||
// and must still agree with the levels that do
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json x = deep(a, depth);
|
||||
const json y = deep(b, depth);
|
||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK(x < y);
|
||||
CHECK(y > x);
|
||||
CHECK_FALSE(y < x);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("comparison of scalars (inline fast path)")
|
||||
{
|
||||
const json::number_integer_t neg = -1;
|
||||
@@ -968,6 +1249,17 @@ TEST_CASE("comparison of scalars (inline fast path)")
|
||||
CHECK_FALSE(nan < json(1u));
|
||||
CHECK_FALSE(json(1u) < nan);
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("discarded")
|
||||
@@ -983,6 +1275,11 @@ TEST_CASE("comparison of scalars (inline fast path)")
|
||||
CHECK_FALSE(d == json(nullptr));
|
||||
CHECK_FALSE(json(nullptr) == d);
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("scalar overloads")
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-conversions1.cpp (conversions to and from
|
||||
// std::string_view). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// conversions1.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion (C++17)")
|
||||
{
|
||||
SECTION("get a string (explicit)")
|
||||
{
|
||||
const json::string_t s_reference{"Hello world"};
|
||||
json j(s_reference);
|
||||
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-string type using string_view")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get a string (explicit, get_to)")
|
||||
{
|
||||
const json::string_t s_reference{"Hello world"};
|
||||
json j(s_reference);
|
||||
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string const s = "previous value";
|
||||
std::string_view sv = s;
|
||||
j.get_to(sv);
|
||||
CHECK(json(sv) == j);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
SECTION("get a string (implicit)")
|
||||
{
|
||||
const json::string_t s_reference{"Hello world"};
|
||||
json j(s_reference);
|
||||
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,11 +6,12 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-conversions1-cpp<NN>.cpp. This file stays C++11-only.
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -30,6 +31,25 @@ using nlohmann::json;
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#define JSON_HAS_CPP_14
|
||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||
#define JSON_HAS_CPP_14
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion")
|
||||
{
|
||||
SECTION("get an object (explicit)")
|
||||
@@ -499,6 +519,13 @@ TEST_CASE("value conversion")
|
||||
const std::string s = j.get<std::string>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("exception in case of a non-string type")
|
||||
{
|
||||
@@ -524,6 +551,26 @@ TEST_CASE("value conversion")
|
||||
json(json::value_t::number_float).get<json::string_t>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
}
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("exception in case of a non-string type using string_view")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("get a string (explicit, get_to)")
|
||||
@@ -544,6 +591,15 @@ TEST_CASE("value conversion")
|
||||
j.get_to(s);
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string const s = "previous value";
|
||||
std::string_view sv = s;
|
||||
j.get_to(sv);
|
||||
CHECK(json(sv) == j);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("get null (explicit)")
|
||||
@@ -582,6 +638,14 @@ TEST_CASE("value conversion")
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("std::string")
|
||||
{
|
||||
const std::string s = j;
|
||||
@@ -1192,4 +1256,11 @@ TEST_CASE("value conversion")
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#undef JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
#undef JSON_HAS_CPP_14
|
||||
#endif
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -1,225 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-conversions2.cpp (conversions of
|
||||
// std::filesystem::path, std::u8string and std::optional). It is kept in a separate
|
||||
// translation unit so the (much larger) unit-conversions2.cpp is built for C++11 only and
|
||||
// not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
TEST_CASE("std::filesystem::path")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
json const j_string = "Path";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
TEST_CASE("std::u8string")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
const std::u8string s = u8"Path";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "Path");
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
// use \u universal-character-names (rather than raw \x byte escapes
|
||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||
// literal as a nonstandard extension (warning C5321), which some of
|
||||
// our CI configs promote to an error; \u is portable and produces
|
||||
// the exact same encoded bytes without depending on the source
|
||||
// file's encoding
|
||||
const std::u8string s = u8"P\u011B\u0161ina";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// a type whose to_json reports an error by throwing, used below to check that
|
||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||
// converting its contained value instead of calling std::terminate (#5642)
|
||||
struct throwing_to_json_type {};
|
||||
|
||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||
{
|
||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("std::optional")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
const json j_null;
|
||||
const std::optional<std::string> opt_null;
|
||||
|
||||
CHECK(json(opt_null) == j_null);
|
||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||
|
||||
// Constructing std::optional<T> directly from JSON null throws because
|
||||
// std::optional's own converting constructor is chosen over basic_json's
|
||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||
// constructible from T, and T is constructible from basic_json via the
|
||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||
|
||||
// Assignment goes through the same overload resolution as direct
|
||||
// construction, so it throws for the same reason. This relies on
|
||||
// basic_json's implicit conversion operator, so it only applies
|
||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
std::optional<std::string> opt_assign;
|
||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
#endif
|
||||
|
||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||
std::optional<std::string> opt_get_to = "placeholder";
|
||||
j_null.get_to(opt_get_to);
|
||||
CHECK(opt_get_to == std::nullopt);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j_string = "string";
|
||||
std::optional<std::string> opt_string = "string";
|
||||
|
||||
CHECK(json(opt_string) == j_string);
|
||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||
// @infer-ignore USE_AFTER_DELETE
|
||||
}
|
||||
|
||||
SECTION("bool")
|
||||
{
|
||||
json j_bool = true;
|
||||
std::optional<bool> opt_bool = true;
|
||||
|
||||
CHECK(json(opt_bool) == j_bool);
|
||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||
}
|
||||
|
||||
SECTION("number")
|
||||
{
|
||||
json j_number = 1;
|
||||
std::optional<int> opt_int = 1;
|
||||
|
||||
CHECK(json(opt_int) == j_number);
|
||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json j_array = {1, 2, nullptr};
|
||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||
|
||||
CHECK(json(opt_array) == j_array);
|
||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||
|
||||
CHECK(json(opt_object) == j_object);
|
||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||
{
|
||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||
// to_json that reports an error, or std::bad_alloc for T =
|
||||
// std::string/vector/json). Before the fix, this called
|
||||
// std::terminate() instead of letting the exception propagate.
|
||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||
|
||||
// the conversion is noexcept exactly when converting the contained value is
|
||||
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
||||
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
#endif
|
||||
@@ -6,11 +6,12 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-conversions2-cpp<NN>.cpp. This file stays C++11-only.
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -35,6 +36,25 @@ using nlohmann::json;
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#define JSON_HAS_CPP_14
|
||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||
#define JSON_HAS_CPP_14
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion")
|
||||
{
|
||||
SECTION("get a binary value (explicit)")
|
||||
@@ -660,4 +680,194 @@ TEST_CASE("Strict JSON to enum mapping")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
TEST_CASE("std::filesystem::path")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
json const j_string = "Path";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
TEST_CASE("std::u8string")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
const std::u8string s = u8"Path";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "Path");
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
// use \u universal-character-names (rather than raw \x byte escapes
|
||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||
// literal as a nonstandard extension (warning C5321), which some of
|
||||
// our CI configs promote to an error; \u is portable and produces
|
||||
// the exact same encoded bytes without depending on the source
|
||||
// file's encoding
|
||||
const std::u8string s = u8"P\u011B\u0161ina";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// a type whose to_json reports an error by throwing, used below to check that
|
||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||
// converting its contained value instead of calling std::terminate (#5642)
|
||||
struct throwing_to_json_type {};
|
||||
|
||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||
{
|
||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("std::optional")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
const json j_null;
|
||||
const std::optional<std::string> opt_null;
|
||||
|
||||
CHECK(json(opt_null) == j_null);
|
||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||
|
||||
// Constructing std::optional<T> directly from JSON null throws because
|
||||
// std::optional's own converting constructor is chosen over basic_json's
|
||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||
// constructible from T, and T is constructible from basic_json via the
|
||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||
|
||||
// Assignment goes through the same overload resolution as direct
|
||||
// construction, so it throws for the same reason. This relies on
|
||||
// basic_json's implicit conversion operator, so it only applies
|
||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
std::optional<std::string> opt_assign;
|
||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
#endif
|
||||
|
||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||
std::optional<std::string> opt_get_to = "placeholder";
|
||||
j_null.get_to(opt_get_to);
|
||||
CHECK(opt_get_to == std::nullopt);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j_string = "string";
|
||||
std::optional<std::string> opt_string = "string";
|
||||
|
||||
CHECK(json(opt_string) == j_string);
|
||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||
// @infer-ignore USE_AFTER_DELETE
|
||||
}
|
||||
|
||||
SECTION("bool")
|
||||
{
|
||||
json j_bool = true;
|
||||
std::optional<bool> opt_bool = true;
|
||||
|
||||
CHECK(json(opt_bool) == j_bool);
|
||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||
}
|
||||
|
||||
SECTION("number")
|
||||
{
|
||||
json j_number = 1;
|
||||
std::optional<int> opt_int = 1;
|
||||
|
||||
CHECK(json(opt_int) == j_number);
|
||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json j_array = {1, 2, nullptr};
|
||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||
|
||||
CHECK(json(opt_array) == j_array);
|
||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||
|
||||
CHECK(json(opt_object) == j_object);
|
||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||
{
|
||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||
// to_json that reports an error, or std::bad_alloc for T =
|
||||
// std::string/vector/json). Before the fix, this called
|
||||
// std::terminate() instead of letting the exception propagate.
|
||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||
|
||||
// the conversion is noexcept exactly when converting the contained value is
|
||||
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
||||
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#undef JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
#undef JSON_HAS_CPP_14
|
||||
#endif
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -9,9 +9,7 @@
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -166,47 +164,3 @@ TEST_CASE("array type without at()")
|
||||
CHECK(j.at(no_at_json::json_pointer("/1")) == 2);
|
||||
CHECK_THROWS_AS(j.at(no_at_json::json_pointer("/3")), no_at_json::out_of_range);
|
||||
}
|
||||
|
||||
TEST_CASE("regression test #5476 - array type without reserve()")
|
||||
{
|
||||
// the capacity reserved for definite-length arrays must not require the
|
||||
// array type to have a reserve() member function
|
||||
SECTION("std::deque")
|
||||
{
|
||||
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
||||
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
||||
|
||||
// the binary formats pass a definite length to start_array()
|
||||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||||
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
|
||||
CHECK(deque_json::from_bon8(deque_json::to_bon8(j)) == j);
|
||||
|
||||
// parse() instantiates the callback parser as well, which reserves too
|
||||
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK(with_callback == deque_json({1, 2, 3}));
|
||||
}
|
||||
|
||||
SECTION("std::vector still reserves")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
array.push_back(i);
|
||||
}
|
||||
|
||||
const auto j = json::from_cbor(json::to_cbor(array));
|
||||
CHECK(j == array);
|
||||
CHECK(j.get_ref<const json::array_t&>().capacity() >= 100);
|
||||
}
|
||||
|
||||
SECTION("the reservation stays capped")
|
||||
{
|
||||
// CBOR array announcing 2^32-1 elements, but truncated right after the
|
||||
// header: the input must be rejected without reserving that capacity
|
||||
const std::vector<std::uint8_t> truncated = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
||||
}
|
||||
}
|
||||
@@ -44,10 +44,10 @@ TEST_CASE("JSON Node Metadata")
|
||||
{
|
||||
SECTION("type int")
|
||||
{
|
||||
using json_t = json_with_metadata<int>;
|
||||
json_t null;
|
||||
auto obj = json_t::object();
|
||||
auto array = json_t::array();
|
||||
using json = json_with_metadata<int>;
|
||||
json null;
|
||||
auto obj = json::object();
|
||||
auto array = json::array();
|
||||
|
||||
null.metadata() = 1;
|
||||
obj.metadata() = 2;
|
||||
@@ -61,8 +61,8 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("type vector<int>")
|
||||
{
|
||||
using json_t = json_with_metadata<std::vector<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::vector<int>>;
|
||||
json value;
|
||||
value.metadata().emplace_back(1);
|
||||
auto copy = value;
|
||||
value.metadata().emplace_back(2);
|
||||
@@ -75,12 +75,12 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("copy ctor")
|
||||
{
|
||||
using json_t = json_with_metadata<std::vector<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::vector<int>>;
|
||||
json value;
|
||||
value.metadata().emplace_back(1);
|
||||
value.metadata().emplace_back(2);
|
||||
|
||||
json_t copy = value;
|
||||
json copy = value;
|
||||
|
||||
CHECK(copy.metadata().size() == 2);
|
||||
CHECK(copy.metadata().at(0) == 1);
|
||||
@@ -95,12 +95,12 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("move ctor")
|
||||
{
|
||||
using json_t = json_with_metadata<std::vector<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::vector<int>>;
|
||||
json value;
|
||||
value.metadata().emplace_back(1);
|
||||
value.metadata().emplace_back(2);
|
||||
|
||||
const json_t moved = std::move(value);
|
||||
const json moved = std::move(value);
|
||||
|
||||
CHECK(moved.metadata().size() == 2);
|
||||
CHECK(moved.metadata().at(0) == 1);
|
||||
@@ -108,12 +108,12 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("move assign")
|
||||
{
|
||||
using json_t = json_with_metadata<std::vector<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::vector<int>>;
|
||||
json value;
|
||||
value.metadata().emplace_back(1);
|
||||
value.metadata().emplace_back(2);
|
||||
|
||||
json_t moved;
|
||||
json moved;
|
||||
moved = std::move(value);
|
||||
|
||||
CHECK(moved.metadata().size() == 2);
|
||||
@@ -122,12 +122,12 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("copy assign")
|
||||
{
|
||||
using json_t = json_with_metadata<std::vector<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::vector<int>>;
|
||||
json value;
|
||||
value.metadata().emplace_back(1);
|
||||
value.metadata().emplace_back(2);
|
||||
|
||||
json_t copy;
|
||||
json copy;
|
||||
copy = value;
|
||||
|
||||
CHECK(copy.metadata().size() == 2);
|
||||
@@ -143,8 +143,8 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("type unique_ptr<int>")
|
||||
{
|
||||
using json_t = json_with_metadata<std::unique_ptr<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::unique_ptr<int>>;
|
||||
json value;
|
||||
value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
auto moved = std::move(value);
|
||||
|
||||
@@ -153,12 +153,12 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("type vector<int> in json array")
|
||||
{
|
||||
using json_t = json_with_metadata<std::vector<int>>;
|
||||
json_t value;
|
||||
using json = json_with_metadata<std::vector<int>>;
|
||||
json value;
|
||||
value.metadata().emplace_back(1);
|
||||
value.metadata().emplace_back(2);
|
||||
|
||||
json_t const array(10, value);
|
||||
json const array(10, value);
|
||||
|
||||
CHECK(value.metadata().size() == 2);
|
||||
CHECK(value.metadata().at(0) == 1);
|
||||
@@ -173,10 +173,10 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("member swap")
|
||||
{
|
||||
using json_t = json_with_metadata<int>;
|
||||
json_t a = 1;
|
||||
using json = json_with_metadata<int>;
|
||||
json a = 1;
|
||||
a.metadata() = 100;
|
||||
json_t b = 2;
|
||||
json b = 2;
|
||||
b.metadata() = 200;
|
||||
|
||||
a.swap(b);
|
||||
@@ -188,10 +188,10 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("nonmember swap")
|
||||
{
|
||||
using json_t = json_with_metadata<int>;
|
||||
json_t a = 1;
|
||||
using json = json_with_metadata<int>;
|
||||
json a = 1;
|
||||
a.metadata() = 100;
|
||||
json_t b = 2;
|
||||
json b = 2;
|
||||
b.metadata() = 200;
|
||||
|
||||
using std::swap;
|
||||
@@ -204,10 +204,10 @@ TEST_CASE("JSON Node Metadata")
|
||||
}
|
||||
SECTION("std::swap")
|
||||
{
|
||||
using json_t = json_with_metadata<int>;
|
||||
json_t a = 1;
|
||||
using json = json_with_metadata<int>;
|
||||
json a = 1;
|
||||
a.metadata() = 100;
|
||||
json_t b = 2;
|
||||
json b = 2;
|
||||
b.metadata() = 200;
|
||||
|
||||
std::swap(a, b);
|
||||
@@ -221,15 +221,15 @@ TEST_CASE("JSON Node Metadata")
|
||||
{
|
||||
// std::sort mixes swap() with moves; each value's metadata must
|
||||
// travel with it, just as it does for copy, move, and assignment
|
||||
using json_t = json_with_metadata<int>;
|
||||
std::vector<json_t> values;
|
||||
using json = json_with_metadata<int>;
|
||||
std::vector<json> values;
|
||||
for (const int v :
|
||||
{
|
||||
5, 3, 9, 1, 7, 2, 8, 4, 6, 0, 15, 13, 19, 11, 17, 12, 18, 14, 16, 10,
|
||||
25, 23, 29, 21, 27, 22, 28, 24, 26, 20, 35, 33
|
||||
})
|
||||
{
|
||||
json_t value = v;
|
||||
json value = v;
|
||||
value.metadata() = v;
|
||||
values.push_back(value);
|
||||
}
|
||||
@@ -267,19 +267,19 @@ template <class Ptr, class Fnc>
|
||||
void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
|
||||
{
|
||||
using value_t = nlohmann::detail::value_t;
|
||||
const json_with_visitor_t& self = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
switch (self.type())
|
||||
const json_with_visitor_t& json = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
switch (json.type())
|
||||
{
|
||||
case value_t::object:
|
||||
for (const auto& entry : self.items())
|
||||
for (const auto& entry : json.items())
|
||||
{
|
||||
entry.value().do_visit(ptr / entry.key(), fnc);
|
||||
}
|
||||
break;
|
||||
case value_t::array:
|
||||
for (std::size_t i = 0; i < self.size(); ++i)
|
||||
for (std::size_t i = 0; i < json.size(); ++i)
|
||||
{
|
||||
self.at(i).do_visit(ptr / std::to_string(i), fnc);
|
||||
json.at(i).do_visit(ptr / std::to_string(i), fnc);
|
||||
}
|
||||
break;
|
||||
case value_t::discarded:
|
||||
@@ -292,22 +292,22 @@ void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
default:
|
||||
fnc(ptr, self);
|
||||
fnc(ptr, json);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON Visit Node")
|
||||
{
|
||||
json_with_visitor_t root;
|
||||
root["null"];
|
||||
root["int"] = -1;
|
||||
root["uint"] = 1U;
|
||||
root["float"] = 1.0;
|
||||
root["boolean"] = true;
|
||||
root["string"] = "string";
|
||||
root["array"].push_back(0);
|
||||
root["array"].push_back(1);
|
||||
root["array"].push_back(root);
|
||||
json_with_visitor_t json;
|
||||
json["null"];
|
||||
json["int"] = -1;
|
||||
json["uint"] = 1U;
|
||||
json["float"] = 1.0;
|
||||
json["boolean"] = true;
|
||||
json["string"] = "string";
|
||||
json["array"].push_back(0);
|
||||
json["array"].push_back(1);
|
||||
json["array"].push_back(json);
|
||||
|
||||
std::set<std::string> expected
|
||||
{
|
||||
@@ -330,7 +330,7 @@ TEST_CASE("JSON Visit Node")
|
||||
"/array/2/array/1 - number_integer - 1"
|
||||
};
|
||||
|
||||
root.visit(
|
||||
json.visit(
|
||||
[&](const json_with_visitor_t::json_pointer & p,
|
||||
const json_with_visitor_t& j)
|
||||
{
|
||||
@@ -374,7 +374,7 @@ TEST_CASE("JSON Visit Node")
|
||||
break;
|
||||
}
|
||||
str << " - " << j.dump();
|
||||
CHECK(root.at(p) == j);
|
||||
CHECK(json.at(p) == j);
|
||||
INFO(str.str());
|
||||
CHECK(expected.count(str.str()) == 1);
|
||||
expected.erase(str.str());
|
||||
@@ -404,22 +404,22 @@ using json_with_hidden_base_members = nlohmann::json::with_base_class_t<base_cla
|
||||
|
||||
TEST_CASE("JSON Node as_base_class")
|
||||
{
|
||||
using json_t = json_with_hidden_base_members;
|
||||
using json = json_with_hidden_base_members;
|
||||
|
||||
static_assert(std::is_same<decltype(std::declval<json_t&>().as_base_class()), json_t::json_base_class_t&>::value, "");
|
||||
static_assert(std::is_same<decltype(std::declval<const json_t&>().as_base_class()), const json_t::json_base_class_t&>::value, "");
|
||||
static_assert(noexcept(std::declval<json_t&>().as_base_class()), "");
|
||||
static_assert(noexcept(std::declval<const json_t&>().as_base_class()), "");
|
||||
static_assert(std::is_same<decltype(std::declval<json&>().as_base_class()), json::json_base_class_t&>::value, "");
|
||||
static_assert(std::is_same<decltype(std::declval<const json&>().as_base_class()), const json::json_base_class_t&>::value, "");
|
||||
static_assert(noexcept(std::declval<json&>().as_base_class()), "");
|
||||
static_assert(noexcept(std::declval<const json&>().as_base_class()), "");
|
||||
|
||||
SECTION("non-const")
|
||||
{
|
||||
json_t j = {1, 2, 3};
|
||||
json j = {1, 2, 3};
|
||||
|
||||
CHECK(std::string(j.type_name()) == "array");
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
||||
CHECK(j.as_base_class().size() == 42);
|
||||
CHECK(&j.as_base_class() == &static_cast<json_t::json_base_class_t&>(j));
|
||||
CHECK(&j.as_base_class() == &static_cast<json::json_base_class_t&>(j));
|
||||
|
||||
j.as_base_class().m_size = 7;
|
||||
CHECK(j.as_base_class().size() == 7);
|
||||
@@ -428,13 +428,13 @@ TEST_CASE("JSON Node as_base_class")
|
||||
|
||||
SECTION("const")
|
||||
{
|
||||
const json_t j = {1, 2, 3};
|
||||
const json j = {1, 2, 3};
|
||||
|
||||
CHECK(std::string(j.type_name()) == "array");
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
||||
CHECK(j.as_base_class().size() == 42);
|
||||
CHECK(&j.as_base_class() == &static_cast<const json_t::json_base_class_t&>(j));
|
||||
CHECK(&j.as_base_class() == &static_cast<const json::json_base_class_t&>(j));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-custom-binary-type.cpp (binary types
|
||||
// whose value type is std::byte). It is kept in a separate translation unit so the (much
|
||||
// larger) unit-custom-binary-type.cpp is built for C++11 only and not rebuilt for every
|
||||
// C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// capture whether JSON_DISABLE_ENUM_SERIALIZATION was enabled on the command
|
||||
// line before including json.hpp, which #undefs it; std::byte is an enum, so
|
||||
// converting std::vector<std::byte> needs enum serialization
|
||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// a BinaryType whose value type is not an integer type at all
|
||||
using byte_binary_json = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
|
||||
TEST_CASE("binary type whose value type is not std::uint8_t (C++17)")
|
||||
{
|
||||
SECTION("dumping a value type that is not an integer")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("hashing and the binary formats")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
const auto j = byte_binary_json::binary(bytes);
|
||||
|
||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
// the same holds for BON8
|
||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
}
|
||||
|
||||
// moved from unit-regression3.cpp, so that file does not instantiate a second
|
||||
// basic_json specialization (MinGW section limit, see windows.yml)
|
||||
TEST_CASE("std::vector<std::byte> as binary type")
|
||||
{
|
||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||
{
|
||||
const std::vector<std::uint8_t> data = {0x80};
|
||||
const auto decoded = byte_binary_json::from_cbor(data);
|
||||
CHECK((decoded == byte_binary_json::array()));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||
{
|
||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||
const byte_binary_json j = original;
|
||||
CHECK(j.is_binary());
|
||||
CHECK(!j.is_array());
|
||||
|
||||
// Test round-tripping: extracting the binary value back as the custom container type
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted == original);
|
||||
|
||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||
const nlohmann::json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||
CHECK(default_json.is_array());
|
||||
CHECK(!default_json.is_binary());
|
||||
}
|
||||
|
||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||
{
|
||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||
// (not just from a binary-typed node)
|
||||
const auto j = byte_binary_json::parse("[1,2,3]");
|
||||
CHECK(j.is_array());
|
||||
CHECK(!j.is_binary());
|
||||
|
||||
// Extracting as custom BinaryType should work from arrays
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted.size() == 3);
|
||||
CHECK(extracted[0] == std::byte{1});
|
||||
CHECK(extracted[1] == std::byte{2});
|
||||
CHECK(extracted[2] == std::byte{3});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,13 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// capture whether JSON_DISABLE_ENUM_SERIALIZATION was enabled on the command
|
||||
// line before including json.hpp, which #undefs it; std::byte is an enum, so
|
||||
// converting std::vector<std::byte> needs enum serialization
|
||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -17,6 +24,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -24,6 +35,11 @@ namespace
|
||||
// processed as the numbers 0..255
|
||||
using char_binary_json = nlohmann::json::with_binary_t<std::vector<char>>;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// a BinaryType whose value type is not an integer type at all
|
||||
using byte_binary_json = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
@@ -50,4 +66,79 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
{
|
||||
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("dumping a value type that is not an integer")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("hashing and the binary formats")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
const auto j = byte_binary_json::binary(bytes);
|
||||
|
||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
// the same holds for BON8
|
||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// moved from unit-regression3.cpp, so that file does not instantiate a second
|
||||
// basic_json specialization (MinGW section limit, see windows.yml)
|
||||
TEST_CASE("std::vector<std::byte> as binary type")
|
||||
{
|
||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||
{
|
||||
const std::vector<std::uint8_t> data = {0x80};
|
||||
const auto decoded = byte_binary_json::from_cbor(data);
|
||||
CHECK((decoded == byte_binary_json::array()));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||
{
|
||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||
const byte_binary_json j = original;
|
||||
CHECK(j.is_binary());
|
||||
CHECK(!j.is_array());
|
||||
|
||||
// Test round-tripping: extracting the binary value back as the custom container type
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted == original);
|
||||
|
||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||
const nlohmann::json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||
CHECK(default_json.is_array());
|
||||
CHECK(!default_json.is_binary());
|
||||
}
|
||||
|
||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||
{
|
||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||
// (not just from a binary-typed node)
|
||||
const auto j = byte_binary_json::parse("[1,2,3]");
|
||||
CHECK(j.is_array());
|
||||
CHECK(!j.is_binary());
|
||||
|
||||
// Extracting as custom BinaryType should work from arrays
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted.size() == 3);
|
||||
CHECK(extracted[0] == std::byte{1});
|
||||
CHECK(extracted[1] == std::byte{2});
|
||||
CHECK(extracted[2] == std::byte{3});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1,367 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
#include <compare>
|
||||
#endif
|
||||
|
||||
namespace custom_number_types
|
||||
{
|
||||
|
||||
// a signed integer of class type: std::is_signed is only true for arithmetic
|
||||
// types, so the library has to take the signedness from std::numeric_limits,
|
||||
// as it does for 128-bit and multiprecision integer classes such as
|
||||
// absl::int128 or boost::multiprecision::cpp_int
|
||||
class class_int
|
||||
{
|
||||
public:
|
||||
// trivial, like the 128-bit integer classes: the value is stored in a union
|
||||
class_int() = default;
|
||||
|
||||
// implicit from built-in integers and explicit from floats, like absl::int128;
|
||||
// non-template overloads (as absl::int128 has) because clang 3.4 and 3.5
|
||||
// reject basic_json's defaulted data() for a number type with template
|
||||
// constructors
|
||||
class_int(int v) : value(v) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
class_int(unsigned v) : value(v) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
class_int(long v) : value(v) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
class_int(unsigned long v) : value(static_cast<std::int64_t>(v)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
class_int(long long v) : value(v) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
class_int(unsigned long long v) : value(static_cast<std::int64_t>(v)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
explicit class_int(double v) : value(static_cast<std::int64_t>(v)) {}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value, int>::type = 0>
|
||||
explicit operator T() const
|
||||
{
|
||||
return static_cast<T>(value);
|
||||
}
|
||||
|
||||
friend bool operator==(class_int lhs, class_int rhs)
|
||||
{
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
friend bool operator!=(class_int lhs, class_int rhs)
|
||||
{
|
||||
return lhs.value != rhs.value;
|
||||
}
|
||||
friend bool operator<(class_int lhs, class_int rhs)
|
||||
{
|
||||
return lhs.value < rhs.value;
|
||||
}
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
friend std::strong_ordering operator<=>(class_int lhs, class_int rhs) // *NOPAD*
|
||||
{
|
||||
return lhs.value <=> rhs.value; // *NOPAD*
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::int64_t value;
|
||||
};
|
||||
|
||||
} // namespace custom_number_types
|
||||
|
||||
using custom_number_types::class_int;
|
||||
|
||||
namespace std
|
||||
{
|
||||
// only the members the library uses
|
||||
template<>
|
||||
class numeric_limits<class_int>
|
||||
{
|
||||
public:
|
||||
static constexpr bool is_signed = true;
|
||||
static constexpr int digits = std::numeric_limits<std::int64_t>::digits;
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using class_int_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, class_int, std::uint64_t, double>;
|
||||
|
||||
class_int_json make_class_int(std::int64_t v)
|
||||
{
|
||||
class_int_json j(class_int_json::value_t::number_integer);
|
||||
j.get_ref<class_int&>() = class_int(v);
|
||||
return j;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// the serializer cannot print an integer of class type, so doctest must not
|
||||
// try when an assertion fails
|
||||
namespace doctest
|
||||
{
|
||||
template<>
|
||||
struct StringMaker<class_int_json>
|
||||
{
|
||||
static String convert(const class_int_json& j)
|
||||
{
|
||||
return j.type_name();
|
||||
}
|
||||
};
|
||||
} // namespace doctest
|
||||
|
||||
// __int128 as number type needs the std::numeric_limits (for the range checks)
|
||||
// and std::is_integral (for the serializer) specializations, which libc++
|
||||
// always provides and libstdc++ only outside strict ISO modes; the MSVC
|
||||
// standard library has none
|
||||
#if defined(__SIZEOF_INT128__) && (defined(_LIBCPP_VERSION) || defined(__GLIBCXX_TYPE_INT_N_0))
|
||||
#define JSON_TEST_INT128_NUMBER_TYPES 1
|
||||
#else
|
||||
#define JSON_TEST_INT128_NUMBER_TYPES 0
|
||||
#endif
|
||||
|
||||
#if JSON_TEST_INT128_NUMBER_TYPES
|
||||
namespace
|
||||
{
|
||||
|
||||
// __extension__ keeps -Wpedantic from flagging the non-standard type
|
||||
__extension__ typedef __int128 int128; // NOLINT(modernize-use-using)
|
||||
__extension__ typedef unsigned __int128 uint128; // NOLINT(modernize-use-using)
|
||||
|
||||
static_assert(std::numeric_limits<int128>::digits == 127 && std::numeric_limits<uint128>::digits == 128 &&
|
||||
std::is_integral<int128>::value && std::is_integral<uint128>::value, // NOLINT(misc-redundant-expression)
|
||||
"__int128 is not fully supported by the standard library");
|
||||
|
||||
using wide_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, int128, uint128, double>;
|
||||
|
||||
wide_json make_int(int128 v)
|
||||
{
|
||||
wide_json j(wide_json::value_t::number_integer);
|
||||
j.get_ref<int128&>() = v;
|
||||
return j;
|
||||
}
|
||||
|
||||
wide_json make_uint(uint128 v)
|
||||
{
|
||||
wide_json j(wide_json::value_t::number_unsigned);
|
||||
j.get_ref<uint128&>() = v;
|
||||
return j;
|
||||
}
|
||||
|
||||
wide_json wrap(const wide_json& value)
|
||||
{
|
||||
wide_json o(wide_json::value_t::object);
|
||||
o["v"] = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("custom number types: integers beyond 64 bits")
|
||||
{
|
||||
using out_of_range = wide_json::out_of_range;
|
||||
|
||||
const int128 two_64 = static_cast<int128>(1) << 64;
|
||||
const int128 two_100 = static_cast<int128>(1) << 100;
|
||||
const int128 max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||
const int128 min_int64 = (std::numeric_limits<std::int64_t>::min)();
|
||||
const uint128 max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||
|
||||
// before the range checks, these values were silently truncated, e.g.,
|
||||
// 2^100 was written as 0
|
||||
const wide_json int_big = make_int(two_100);
|
||||
const wide_json int_big_negative = make_int(-two_100);
|
||||
const wide_json uint_big = make_uint(static_cast<uint128>(two_100));
|
||||
std::vector<std::uint8_t> _;
|
||||
|
||||
SECTION("CBOR")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(int_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(int_big_negative), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(uint_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit uint64", out_of_range&);
|
||||
// the extremes of the 128-bit types, whose digits are produced without
|
||||
// negating the minimum as a whole
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(make_int((std::numeric_limits<int128>::min)())), "[json.exception.out_of_range.407] integer number -170141183460469231731687303715884105728 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(make_int((std::numeric_limits<int128>::max)())), "[json.exception.out_of_range.407] integer number 170141183460469231731687303715884105727 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_cbor(make_uint((std::numeric_limits<uint128>::max)())), "[json.exception.out_of_range.407] integer number 340282366920938463463374607431768211455 cannot be represented by CBOR as it does not fit uint64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(make_int(two_64)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(make_int(-two_64 - 1)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(make_uint(static_cast<uint128>(max_uint64) + 1)), out_of_range&);
|
||||
|
||||
// CBOR's integers cover [-2^64, 2^64-1]
|
||||
CHECK(wide_json::to_cbor(make_int(two_64 - 1)) == std::vector<std::uint8_t>({0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
CHECK(wide_json::to_cbor(make_int(-two_64)) == std::vector<std::uint8_t>({0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
CHECK(wide_json::to_cbor(make_uint(max_uint64)) == std::vector<std::uint8_t>({0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
}
|
||||
|
||||
SECTION("MessagePack")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_msgpack(int_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_msgpack(int_big_negative), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_msgpack(uint_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by MessagePack as it does not fit uint64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(make_int(two_64)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(make_int(min_int64 - 1)), out_of_range&);
|
||||
|
||||
// MessagePack's integers cover [-2^63, 2^64-1]
|
||||
CHECK(wide_json::to_msgpack(make_int(two_64 - 1)) == std::vector<std::uint8_t>({0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
CHECK(wide_json::to_msgpack(make_int(min_int64)) == std::vector<std::uint8_t>({0xD3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
|
||||
CHECK(wide_json::to_msgpack(make_uint(max_uint64)) == std::vector<std::uint8_t>({0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
}
|
||||
|
||||
SECTION("BSON")
|
||||
{
|
||||
// wrap() stores the value at /v, which JSON_DIAGNOSTICS adds to the message
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big)), "[json.exception.out_of_range.407] (/v) integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big_negative)), "[json.exception.out_of_range.407] (/v) integer number -1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(uint_big)), "[json.exception.out_of_range.407] (/v) integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit uint64", out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big)), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big_negative)), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(uint_big)), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit uint64", out_of_range&);
|
||||
#endif
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(make_int(max_int64 + 1))), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(make_int(min_int64 - 1))), out_of_range&);
|
||||
|
||||
// nested values are checked as well, before anything is written
|
||||
wide_json nested(wide_json::value_t::object);
|
||||
nested["a"] = wide_json::array({wrap(int_big)});
|
||||
std::vector<std::uint8_t> out;
|
||||
CHECK_THROWS_AS(wide_json::to_bson(nested, out), out_of_range&);
|
||||
CHECK(out.empty());
|
||||
|
||||
CHECK(wide_json::from_bson(wide_json::to_bson(wrap(make_int(max_int64)))) == wrap(make_int(max_int64)));
|
||||
CHECK(wide_json::from_bson(wide_json::to_bson(wrap(make_int(min_int64)))) == wrap(make_int(min_int64)));
|
||||
CHECK_NOTHROW(wide_json::to_bson(wrap(make_uint(max_uint64))));
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bon8(int_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BON8 as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bon8(int_big_negative), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by BON8 as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = wide_json::to_bon8(uint_big), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BON8 as it does not fit int64", out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(make_int(max_int64 + 1)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(make_int(min_int64 - 1)), out_of_range&);
|
||||
|
||||
CHECK(wide_json::from_bon8(wide_json::to_bon8(make_int(max_int64))) == make_int(max_int64));
|
||||
CHECK(wide_json::from_bon8(wide_json::to_bon8(make_int(min_int64))) == make_int(min_int64));
|
||||
}
|
||||
|
||||
SECTION("UBJSON and BJData")
|
||||
{
|
||||
// integers beyond 64 bits are written exactly as high-precision numbers
|
||||
const std::string digits = "1267650600228229401496703205376";
|
||||
std::vector<std::uint8_t> expected = {'H', 'i', static_cast<std::uint8_t>(digits.size())};
|
||||
expected.insert(expected.end(), digits.begin(), digits.end());
|
||||
CHECK(wide_json::to_ubjson(int_big) == expected);
|
||||
CHECK(wide_json::to_ubjson(uint_big) == expected);
|
||||
CHECK(wide_json::to_bjdata(int_big) == expected);
|
||||
// BJData's uint64 marker 'M' was used for any unsigned value beyond
|
||||
// int64, which truncated the ones beyond 64 bits
|
||||
CHECK(wide_json::to_bjdata(uint_big) == expected);
|
||||
|
||||
// the uint64 marker is still used where the value fits
|
||||
CHECK(wide_json::to_bjdata(make_uint(max_uint64)) == std::vector<std::uint8_t>({'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}));
|
||||
|
||||
// the elements of an optimized container are written the same way;
|
||||
// BJData does not allow 'H' as the type of an optimized container
|
||||
std::vector<std::uint8_t> expected_ubjson_array = {'[', '$', 'H', '#', 'i', 2};
|
||||
std::vector<std::uint8_t> expected_bjdata_array = {'[', '#', 'i', 2};
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
expected_ubjson_array.insert(expected_ubjson_array.end(), expected.begin() + 1, expected.end());
|
||||
expected_bjdata_array.insert(expected_bjdata_array.end(), expected.begin(), expected.end());
|
||||
}
|
||||
CHECK(wide_json::to_ubjson(wide_json::array({uint_big, uint_big}), true, true) == expected_ubjson_array);
|
||||
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
||||
}
|
||||
|
||||
SECTION("BJData ND-array")
|
||||
{
|
||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||
// annotated object is written as a plain object instead of truncating
|
||||
// the element into range
|
||||
wide_json element(wide_json::value_t::object);
|
||||
element["_ArrayType_"] = "uint8";
|
||||
element["_ArraySize_"] = wide_json::array({make_uint(2), make_uint(1)});
|
||||
element["_ArrayData_"] = wide_json::array({make_uint(1), make_uint(static_cast<uint128>(two_64) + 1)});
|
||||
const auto element_bytes = wide_json::to_bjdata(element, true, true);
|
||||
REQUIRE(!element_bytes.empty());
|
||||
CHECK(element_bytes[0] == '{');
|
||||
|
||||
wide_json signed_element = element;
|
||||
signed_element["_ArrayType_"] = "int8";
|
||||
signed_element["_ArrayData_"] = wide_json::array({make_int(1), make_int(-two_64 + 1)});
|
||||
const auto signed_element_bytes = wide_json::to_bjdata(signed_element, true, true);
|
||||
REQUIRE(!signed_element_bytes.empty());
|
||||
CHECK(signed_element_bytes[0] == '{');
|
||||
|
||||
// the same for a dimension beyond 64 bits, which wrapped into a
|
||||
// dimension matching the size of _ArrayData_ (here: 1)
|
||||
wide_json dimension = element;
|
||||
dimension["_ArraySize_"] = wide_json::array({make_uint(2), make_uint(static_cast<uint128>(two_64) + 1)});
|
||||
dimension["_ArrayData_"] = wide_json::array({make_uint(1), make_uint(2)});
|
||||
const auto dimension_bytes = wide_json::to_bjdata(dimension, true, true);
|
||||
REQUIRE(!dimension_bytes.empty());
|
||||
CHECK(dimension_bytes[0] == '{');
|
||||
|
||||
// in range, the ND-array is still written
|
||||
wide_json fits = element;
|
||||
fits["_ArrayData_"] = wide_json::array({make_uint(1), make_uint(2)});
|
||||
const auto fits_bytes = wide_json::to_bjdata(fits, true, true);
|
||||
REQUIRE(!fits_bytes.empty());
|
||||
CHECK(fits_bytes[0] == '[');
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("custom number types")
|
||||
{
|
||||
SECTION("signed integer of class type compared with a float")
|
||||
{
|
||||
// std::is_signed is false for a class type, which made the comparison
|
||||
// treat any float below zero as less than every integer
|
||||
const class_int_json minus_five = make_class_int(-5);
|
||||
const class_int_json minus_two = make_class_int(-2);
|
||||
const class_int_json five = make_class_int(5);
|
||||
const class_int_json minus_two_and_a_half = -2.5;
|
||||
const class_int_json two_and_a_half = 2.5;
|
||||
const class_int_json huge_negative = -1e30;
|
||||
const class_int_json huge_positive = 1e30;
|
||||
|
||||
CHECK(minus_five < minus_two_and_a_half);
|
||||
CHECK_FALSE(minus_two_and_a_half < minus_five);
|
||||
CHECK(minus_two_and_a_half > minus_five);
|
||||
CHECK(minus_five <= minus_two_and_a_half);
|
||||
CHECK(minus_two_and_a_half >= minus_five);
|
||||
CHECK(minus_five != minus_two_and_a_half);
|
||||
|
||||
CHECK(minus_two_and_a_half < minus_two);
|
||||
CHECK_FALSE(minus_two < minus_two_and_a_half);
|
||||
|
||||
CHECK(two_and_a_half < five);
|
||||
CHECK(minus_five < two_and_a_half);
|
||||
|
||||
// floats beyond the integer's range
|
||||
CHECK(huge_negative < minus_five);
|
||||
CHECK_FALSE(minus_five < huge_negative);
|
||||
CHECK(five < huge_positive);
|
||||
CHECK_FALSE(huge_positive < five);
|
||||
|
||||
// equality
|
||||
const class_int_json minus_two_float = -2.0;
|
||||
CHECK(minus_two == minus_two_float);
|
||||
CHECK(minus_two_float == minus_two);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-deserialization.cpp (char8_t support:
|
||||
// _json with char8_t literals and char8_t input). It is kept in a separate translation
|
||||
// unit so the (much larger) unit-deserialization.cpp is built for C++11 only and not
|
||||
// rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||
namespace
|
||||
{
|
||||
// copy of SaxEventLogger from unit-deserialization.cpp (renamed to avoid clashes in unity builds)
|
||||
struct SaxEventLoggerChar8 : public nlohmann::json_sax<json>
|
||||
{
|
||||
bool null() override
|
||||
{
|
||||
events.emplace_back("null()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool boolean(bool val) override
|
||||
{
|
||||
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val) override
|
||||
{
|
||||
events.push_back("number_integer(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val) override
|
||||
{
|
||||
events.push_back("number_unsigned(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*val*/, const std::string& s) override
|
||||
{
|
||||
events.push_back("number_float(" + s + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool string(std::string& val) override
|
||||
{
|
||||
events.push_back("string(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val) override
|
||||
{
|
||||
std::string binary_contents = "binary(";
|
||||
std::string comma_space;
|
||||
for (auto b : val)
|
||||
{
|
||||
binary_contents.append(comma_space);
|
||||
binary_contents.append(std::to_string(static_cast<int>(b)));
|
||||
comma_space = ", ";
|
||||
}
|
||||
binary_contents.append(")");
|
||||
events.push_back(binary_contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool key(std::string& val) override
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_object() override
|
||||
{
|
||||
events.emplace_back("end_object()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_array() override
|
||||
{
|
||||
events.emplace_back("end_array()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII) (C++20)", T, char8_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
{
|
||||
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
||||
CHECK(json::parse(v) == json(true));
|
||||
CHECK(json::accept(v));
|
||||
|
||||
SaxEventLoggerChar8 l;
|
||||
CHECK(json::sax_parse(v, &l));
|
||||
CHECK(l.events.size() == 1);
|
||||
CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("deserialization (C++20)")
|
||||
{
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json with char8_t literals #4945")
|
||||
{
|
||||
// Regular narrow string literal
|
||||
const auto j1 = R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j1["key"] == "value");
|
||||
CHECK(j1["num"] == 42);
|
||||
|
||||
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
|
||||
// \U escape rather than a raw multibyte character so this does not
|
||||
// depend on the compiler's source-file encoding (e.g., MSVC without
|
||||
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
||||
// string literals as UTF-8 - compare against a \x-escaped expectation
|
||||
// for the same reason)
|
||||
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
||||
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
||||
CHECK(j2["msg"] == "hello");
|
||||
|
||||
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j3["key"] == "value");
|
||||
CHECK(j3["num"] == 42);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1167,9 +1167,44 @@ TEST_CASE("deserialization")
|
||||
CHECK(line == "foo");
|
||||
}
|
||||
}
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json with char8_t literals #4945")
|
||||
{
|
||||
// Regular narrow string literal
|
||||
const auto j1 = R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j1["key"] == "value");
|
||||
CHECK(j1["num"] == 42);
|
||||
|
||||
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
|
||||
// \U escape rather than a raw multibyte character so this does not
|
||||
// depend on the compiler's source-file encoding (e.g., MSVC without
|
||||
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
||||
// string literals as UTF-8 - compare against a \x-escaped expectation
|
||||
// for the same reason)
|
||||
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
||||
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
||||
CHECK(j2["msg"] == "hello");
|
||||
|
||||
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j3["key"] == "value");
|
||||
CHECK(j3["num"] == 42);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, char, wchar_t, char16_t, char32_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
// select the types to test - char8_t is only available since C++20 if and only
|
||||
// if __cpp_char8_t is defined.
|
||||
#define TYPE_LIST(...) __VA_ARGS__
|
||||
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t, char8_t)
|
||||
#else
|
||||
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t)
|
||||
#endif
|
||||
|
||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, ASCII_TYPES) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
{
|
||||
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
||||
CHECK(json::parse(v) == json(true));
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -27,9 +27,6 @@ using nlohmann::json;
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
// the types live in a namespace, because the file may be compiled together with other test files
|
||||
namespace enum_keyed_maps_default
|
||||
{
|
||||
enum class cards {kreuz, pik, herz, karo};
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
||||
@@ -68,7 +65,6 @@ NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
||||
{strict_cards::herz, "herz"},
|
||||
{strict_cards::karo, "karo"}
|
||||
})
|
||||
} // namespace enum_keyed_maps_default
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -86,15 +82,6 @@ struct enum_hash
|
||||
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
|
||||
TEST_CASE("maps with enum keys")
|
||||
{
|
||||
// block-scope using-declarations take precedence over same-named types of other files in the same translation unit
|
||||
using enum_keyed_maps_default::cards;
|
||||
using enum_keyed_maps_default::strict_cards;
|
||||
using enum_keyed_maps_default::TaskState;
|
||||
using enum_keyed_maps_default::TS_COMPLETED;
|
||||
using enum_keyed_maps_default::TS_INVALID;
|
||||
using enum_keyed_maps_default::TS_RUNNING;
|
||||
using enum_keyed_maps_default::TS_STOPPED;
|
||||
|
||||
using task_map = std::map<TaskState, std::string>;
|
||||
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
|
||||
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. The regression below only
|
||||
// showed on C++17, and the whole file is just this explicit instantiation, so it
|
||||
// cannot be split: build this file for every standard like the other regression tests:
|
||||
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove)
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// The regression below only showed on C++17, so build this file for every
|
||||
// standard like the other regression tests:
|
||||
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
// Parsing from a FILE* locks the file once and reads it with the unlocked
|
||||
// stdio functions (see JSON_DISABLE_UNLOCKED_FILE_IO). These tests pin that
|
||||
// this is not observable: the results are the same, the file is unlocked
|
||||
// again when parsing ends - also with an exception - and the same thread can
|
||||
// keep using the file while it is locked, e.g., from a SAX callback.
|
||||
|
||||
namespace
|
||||
{
|
||||
// a file name of its own for every run: the test runs for several C++
|
||||
// standards and configurations at the same time, in the same directory
|
||||
std::string temp_file_name()
|
||||
{
|
||||
std::random_device rd;
|
||||
return "unit-file_input-" + std::to_string(rd()) + ".tmp";
|
||||
}
|
||||
|
||||
using file_ptr = std::unique_ptr<std::FILE, int (*)(std::FILE*)>;
|
||||
|
||||
// write @a content to the file @a name and open it for reading
|
||||
file_ptr open_with(const char* name, const std::string& content)
|
||||
{
|
||||
{
|
||||
std::ofstream file(name, std::ios::binary);
|
||||
file << content;
|
||||
}
|
||||
return {std::fopen(name, "rb"), &std::fclose};
|
||||
}
|
||||
|
||||
// The stdio lock is recursive, so whether parsing released it can only be
|
||||
// seen from another thread: ftrylockfile() fails there while this thread
|
||||
// holds the lock. ftrylockfile() is POSIX; elsewhere, the check is skipped.
|
||||
#if defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
void check_unlocked(std::FILE* f)
|
||||
{
|
||||
int result = -1;
|
||||
std::thread other([f, &result]
|
||||
{
|
||||
result = ftrylockfile(f);
|
||||
if (result == 0)
|
||||
{
|
||||
funlockfile(f);
|
||||
}
|
||||
});
|
||||
other.join();
|
||||
CHECK(result == 0);
|
||||
}
|
||||
#else
|
||||
void check_unlocked(std::FILE* /*f*/) {}
|
||||
#endif
|
||||
|
||||
// SAX consumer that also reads the position of the file it is parsing, which
|
||||
// takes the file's lock again on the same thread
|
||||
class position_reading_sax : public nlohmann::json_sax<json>
|
||||
{
|
||||
public:
|
||||
explicit position_reading_sax(std::FILE* f) : m_file(f) {}
|
||||
position_reading_sax(const position_reading_sax&) = delete;
|
||||
position_reading_sax(position_reading_sax&&) = delete;
|
||||
position_reading_sax& operator=(const position_reading_sax&) = delete;
|
||||
position_reading_sax& operator=(position_reading_sax&&) = delete;
|
||||
~position_reading_sax() override = default;
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool number_integer(number_integer_t /*val*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool string(string_t& /*val*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool binary(binary_t& /*val*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool key(string_t& /*val*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool end_object() override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool end_array() override
|
||||
{
|
||||
return record();
|
||||
}
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const nlohmann::detail::exception& /*ex*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<long>& positions() const
|
||||
{
|
||||
return m_positions;
|
||||
}
|
||||
|
||||
private:
|
||||
bool record()
|
||||
{
|
||||
m_positions.push_back(std::ftell(m_file));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::FILE* m_file;
|
||||
std::vector<long> m_positions{}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("parsing from a FILE*")
|
||||
{
|
||||
const std::string temp_name = temp_file_name();
|
||||
const char* const name = temp_name.c_str();
|
||||
const std::string input = "{\"a\": [1, -2.5, \"three\", true, null], \"b\": {\"c\": \"caf\xc3\xa9\"}}";
|
||||
|
||||
SECTION("values are the same as when parsing from a string")
|
||||
{
|
||||
const file_ptr f = open_with(name, input);
|
||||
REQUIRE(f != nullptr);
|
||||
CHECK(json::parse(f.get()) == json::parse(input));
|
||||
// the whole input was consumed
|
||||
std::array<char, 8> rest{};
|
||||
CHECK(std::fgets(rest.data(), static_cast<int>(rest.size()), f.get()) == nullptr);
|
||||
check_unlocked(f.get());
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("the file is unlocked after a parse error")
|
||||
{
|
||||
const file_ptr f = open_with(name, "[1, 2, x]");
|
||||
REQUIRE(f != nullptr);
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(f.get()),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - invalid literal; last read: '2, x'",
|
||||
json::parse_error&);
|
||||
check_unlocked(f.get());
|
||||
|
||||
// the file is still usable: the rest of the input is there
|
||||
std::array<char, 8> rest{};
|
||||
CHECK(std::fgets(rest.data(), static_cast<int>(rest.size()), f.get()) != nullptr);
|
||||
CHECK(std::string(rest.data()) == "]");
|
||||
}
|
||||
|
||||
#endif // !defined(JSON_NOEXCEPTION)
|
||||
|
||||
SECTION("the file can be parsed again and read with stdio afterwards")
|
||||
{
|
||||
const file_ptr f = open_with(name, "[1] [2] tail");
|
||||
REQUIRE(f != nullptr);
|
||||
|
||||
// sax_parse with strict == false stops after the first value; an array
|
||||
// ends with its closing bracket, so the space after it is not read
|
||||
json j;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax1(j);
|
||||
CHECK(json::sax_parse(f.get(), &sax1, json::input_format_t::json, false));
|
||||
CHECK(j == json({1}));
|
||||
check_unlocked(f.get());
|
||||
|
||||
json k;
|
||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax2(k);
|
||||
CHECK(json::sax_parse(f.get(), &sax2, json::input_format_t::json, false));
|
||||
CHECK(k == json({2}));
|
||||
check_unlocked(f.get());
|
||||
|
||||
std::array<char, 8> rest{};
|
||||
CHECK(std::fgets(rest.data(), static_cast<int>(rest.size()), f.get()) != nullptr);
|
||||
CHECK(std::string(rest.data()) == " tail");
|
||||
}
|
||||
|
||||
SECTION("the same thread can use the file while it is parsed")
|
||||
{
|
||||
const file_ptr f = open_with(name, "[10, 20, 30]");
|
||||
REQUIRE(f != nullptr);
|
||||
position_reading_sax sax(f.get());
|
||||
CHECK(json::sax_parse(f.get(), &sax));
|
||||
// start_array, three numbers, end_array
|
||||
CHECK(sax.positions().size() == 5);
|
||||
CHECK(sax.positions().front() == 1);
|
||||
check_unlocked(f.get());
|
||||
}
|
||||
|
||||
SECTION("binary formats read from a FILE*")
|
||||
{
|
||||
const json j = {{"string", std::string(300, 'x')}, {"array", {1, 2, 3}}, {"number", 1.5}};
|
||||
const std::vector<std::uint8_t> cbor = json::to_cbor(j);
|
||||
const file_ptr f = open_with(name, std::string(reinterpret_cast<const char*>(cbor.data()), cbor.size()));
|
||||
REQUIRE(f != nullptr);
|
||||
CHECK(json::from_cbor(f.get()) == j);
|
||||
check_unlocked(f.get());
|
||||
}
|
||||
|
||||
static_cast<void>(std::remove(name));
|
||||
}
|
||||
@@ -1,503 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// The fast paths for contiguous input (bulk string scanning, SWAR searches,
|
||||
// UTF-8 prefix validation, the 8-digit number loop, the 8-byte indentation
|
||||
// skip, the BSON/BON8 bulk string readers) load several bytes at a time. This
|
||||
// test places every input directly in front of (or directly behind) an
|
||||
// inaccessible page, so reading even one byte outside the input crashes, with
|
||||
// or without sanitizers. ASan alone misses over-reads into the slack of the
|
||||
// same heap allocation.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define JSON_TEST_GUARD_PAGE_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
#define JSON_TEST_GUARD_PAGE_POSIX
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS) || defined(JSON_TEST_GUARD_PAGE_POSIX)
|
||||
|
||||
namespace
|
||||
{
|
||||
// Three pages: an inaccessible one, a readable one, an inaccessible one. An
|
||||
// input is copied either to the end of the middle page (any read past its last
|
||||
// byte faults) or to its start (any read before its first byte faults).
|
||||
class guarded_buffer
|
||||
{
|
||||
public:
|
||||
guarded_buffer()
|
||||
: page(page_size())
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
void* const p = VirtualAlloc(nullptr, 3 * page, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
|
||||
REQUIRE(p != nullptr);
|
||||
base = static_cast<unsigned char*>(p);
|
||||
DWORD old_protect = 0;
|
||||
REQUIRE(VirtualProtect(base + page, page, PAGE_READWRITE, &old_protect) != 0);
|
||||
#else
|
||||
void* const p = mmap(nullptr, 3 * page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
REQUIRE(p != MAP_FAILED);
|
||||
base = static_cast<unsigned char*>(p);
|
||||
REQUIRE(mprotect(base + page, page, PROT_READ | PROT_WRITE) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
~guarded_buffer()
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
VirtualFree(base, 0, MEM_RELEASE);
|
||||
#else
|
||||
munmap(base, 3 * page);
|
||||
#endif
|
||||
}
|
||||
|
||||
guarded_buffer(const guarded_buffer&) = delete;
|
||||
guarded_buffer& operator=(const guarded_buffer&) = delete;
|
||||
guarded_buffer(guarded_buffer&&) = delete;
|
||||
guarded_buffer& operator=(guarded_buffer&&) = delete;
|
||||
|
||||
// copies @a input so its last byte is the last readable byte
|
||||
const std::uint8_t* at_end(const std::string& input)
|
||||
{
|
||||
REQUIRE(input.size() <= page);
|
||||
unsigned char* const first = base + (2 * page) - input.size();
|
||||
std::transform(input.begin(), input.end(), first, [](const char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c);
|
||||
});
|
||||
return first;
|
||||
}
|
||||
|
||||
// copies @a input so its first byte is the first readable byte
|
||||
const std::uint8_t* at_start(const std::string& input)
|
||||
{
|
||||
REQUIRE(input.size() <= page);
|
||||
unsigned char* const first = base + page;
|
||||
std::transform(input.begin(), input.end(), first, [](const char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c);
|
||||
});
|
||||
return first;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t page_size()
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
return static_cast<std::size_t>(info.dwPageSize);
|
||||
#else
|
||||
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t page = 0;
|
||||
unsigned char* base = nullptr;
|
||||
};
|
||||
|
||||
// the serialization of the result of @a f, or the exception message; @a f
|
||||
// takes the allow_exceptions flag, which is false without exceptions, where
|
||||
// an error yields a discarded value instead
|
||||
template<typename F>
|
||||
std::string outcome(F f)
|
||||
{
|
||||
#if defined(JSON_NOEXCEPTION)
|
||||
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
#else
|
||||
try
|
||||
{
|
||||
return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return std::string("error: ") + e.what();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// a SAX handler that only counts events, so sax_parse runs without a DOM
|
||||
struct counting_sax : nlohmann::json_sax<json>
|
||||
{
|
||||
std::size_t events = 0;
|
||||
std::string error{}; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_integer(number_integer_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool string(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool binary(binary_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool key(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_object() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_array() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const nlohmann::detail::exception& ex) override
|
||||
{
|
||||
error = std::to_string(position) + " " + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool count()
|
||||
{
|
||||
++events;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// all prefixes of each seed, so the input ends at every byte of it
|
||||
std::vector<std::string> all_prefixes(const std::vector<std::string>& seeds)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (const auto& seed : seeds)
|
||||
{
|
||||
for (std::size_t n = 0; n <= seed.size(); ++n)
|
||||
{
|
||||
result.push_back(seed.substr(0, n));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> json_seeds()
|
||||
{
|
||||
std::vector<std::string> seeds =
|
||||
{
|
||||
// string runs: ASCII, escapes, \u escapes, surrogate pairs
|
||||
"\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"",
|
||||
R"("ab\ncd\"ef\\gh\u00e4ij\uD83D\uDE00kl\u12")",
|
||||
// 2-, 3- and 4-byte UTF-8 sequences, then invalid UTF-8
|
||||
std::string("\"\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80") + "abcdefgh\"",
|
||||
"\"abcdefgh\xC3\x28 \xED\xA0\x80 \xF4\x90\x80\x80 \xF8 \xC0\xAF\"",
|
||||
std::string("\"abcdefg\x01") + "abcdefgh\"",
|
||||
// numbers, so the 8-digit loop runs up to the end
|
||||
"12345678",
|
||||
"1234567890123456",
|
||||
"12345678901234567890123",
|
||||
"-0.000000001234567890123456789",
|
||||
"3.14159265358979323846264338327950288e+100",
|
||||
"-1.2345678901234567e-12",
|
||||
"1E-5",
|
||||
"[1.5,2e10,-3E-5,0.000000001,123456789012345678]",
|
||||
// literals
|
||||
"[true,false,null]",
|
||||
// comments (only parsed with ignore_comments)
|
||||
"/* comment */ [1, // line comment\n 2] /**/",
|
||||
// a pretty-printed document
|
||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(4),
|
||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(8, '\t'),
|
||||
};
|
||||
|
||||
// indentation runs of every length, with and without a closing bracket
|
||||
for (std::size_t n = 0; n <= 20; ++n)
|
||||
{
|
||||
seeds.push_back("[\n" + std::string(n, ' ') + "1,\n" + std::string(n, ' ') + "2\n]");
|
||||
}
|
||||
|
||||
return all_prefixes(seeds);
|
||||
}
|
||||
|
||||
// a value whose binary serializations contain long strings and keys, numbers
|
||||
// of every width, and nested containers
|
||||
json binary_document()
|
||||
{
|
||||
json j;
|
||||
j["a key that is longer than eight bytes"] = "a string value that is longer than eight bytes";
|
||||
j["\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80"] = "\xF0\x9F\x98\x80\xE2\x82\xAC\xC3\xA4 utf-8 0123456789";
|
||||
j["numbers"] = {0, 1, -1, 255, -256, 65536, -2147483649LL, 4294967296ULL, 1.5, -2.25e100};
|
||||
j["nested"] = {{"x", {{"y", {true, false, nullptr}}}}};
|
||||
j["s"] = "";
|
||||
return j;
|
||||
}
|
||||
|
||||
// readers for check_binary(), callable with a container or an iterator range
|
||||
// followed by the allow_exceptions flag
|
||||
#define JSON_TEST_GUARD_PAGE_READER(name, from) \
|
||||
struct name \
|
||||
{ \
|
||||
template<typename... Args> \
|
||||
json operator()(bool allow_exceptions, Args&& ... args) const \
|
||||
{ \
|
||||
return json::from(std::forward<Args>(args)..., true, allow_exceptions); \
|
||||
} \
|
||||
};
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bson, from_bson)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_cbor, from_cbor)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_msgpack, from_msgpack)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_ubjson, from_ubjson)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bjdata, from_bjdata)
|
||||
#undef JSON_TEST_GUARD_PAGE_READER
|
||||
|
||||
// @a bytes as characters (explicitly converted: the iterator-range constructor
|
||||
// would convert implicitly, which -fsanitize=integer reports)
|
||||
std::string as_chars(const std::vector<std::uint8_t>& bytes)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(bytes.size());
|
||||
for (const std::uint8_t b : bytes)
|
||||
{
|
||||
result.push_back(static_cast<char>(b));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// reads every prefix of @a bytes at both ends of the guarded page and from a
|
||||
// std::string, which must give the same outcome
|
||||
template<typename Reader>
|
||||
void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes, Reader parse)
|
||||
{
|
||||
for (const auto& input : all_prefixes({as_chars(bytes)}))
|
||||
{
|
||||
CAPTURE(input)
|
||||
const std::string expected = outcome([&](bool allow)
|
||||
{
|
||||
return parse(allow, input);
|
||||
});
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return parse(allow, first, first + input.size());
|
||||
}) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse_float_native() at both ends of the guarded page, compared bit for bit
|
||||
// with converting the same token from a std::string
|
||||
template<typename FloatType>
|
||||
std::uint64_t float_bits(FloatType value)
|
||||
{
|
||||
static_assert(sizeof(FloatType) <= sizeof(std::uint64_t), "unsupported floating-point width");
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(value));
|
||||
return bits;
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
void check_float_native(guarded_buffer& guard, const std::string& token)
|
||||
{
|
||||
const auto decimal_point = token.find('.');
|
||||
const auto exponent = token.find_first_of("eE");
|
||||
const auto mantissa_end = exponent == std::string::npos ? token.size() : exponent;
|
||||
const auto expected = nlohmann::detail::parse_float_native<FloatType>(token.data(), token.data() + token.size(), decimal_point, mantissa_end);
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const char* const first = reinterpret_cast<const char*>(placement == 0 ? guard.at_end(token) : guard.at_start(token));
|
||||
const auto value = nlohmann::detail::parse_float_native<FloatType>(first, first + token.size(), decimal_point, mantissa_end);
|
||||
CHECK(float_bits(value) == float_bits(expected));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("guard page")
|
||||
{
|
||||
guarded_buffer guard;
|
||||
|
||||
SECTION("string scanning")
|
||||
{
|
||||
// fills that drive each function to a different stop
|
||||
const std::vector<std::string> fills =
|
||||
{
|
||||
std::string(64, 'a'),
|
||||
std::string(64, '"'),
|
||||
std::string(64, '\\'),
|
||||
std::string(64, '\x01'),
|
||||
std::string(64, '\x7F'),
|
||||
std::string(64, '\xFF'),
|
||||
};
|
||||
std::vector<std::string> patterns = fills;
|
||||
{
|
||||
std::string utf8;
|
||||
while (utf8.size() < 64)
|
||||
{
|
||||
utf8 += "\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80" "ab";
|
||||
}
|
||||
patterns.push_back(utf8);
|
||||
}
|
||||
{
|
||||
std::string bytes;
|
||||
for (unsigned int i = 0; i < 64; ++i)
|
||||
{
|
||||
bytes.push_back(static_cast<char>((i * 37u) & 0xFFu));
|
||||
}
|
||||
patterns.push_back(bytes);
|
||||
}
|
||||
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
for (std::size_t n = 0; n <= 64; ++n)
|
||||
{
|
||||
const std::string input = pattern.substr(0, n);
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const unsigned char* const p = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
|
||||
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
|
||||
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
|
||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
|
||||
#endif
|
||||
CHECK(nlohmann::detail::string_bulk_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::scalar_string_bulk_run(p, n) <= n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
CHECK(nlohmann::detail::validate_one_utf8(p + i, n - i) <= n - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("float conversion")
|
||||
{
|
||||
// the lexer converts floats from its token buffer, so the JSON section
|
||||
// below never places a float token at the page boundary; this calls the
|
||||
// conversion directly: the 8-digit loops of parse_float_significand()
|
||||
// and digit_comparison() read up to the end of the token
|
||||
const std::vector<std::string> tokens = all_prefixes(
|
||||
{
|
||||
"1234567812345678123456781234567812345678",
|
||||
"-1.2345678901234567890123456789e-123",
|
||||
"0.00000000000000000000000000000000000001",
|
||||
"98765432.10987654",
|
||||
"123456789012345678901234567890e-30",
|
||||
// halfway between 1 and the next double, then just above it
|
||||
"1.00000000000000011102230246251565404236316680908203125",
|
||||
"1.000000000000000111022302462515654042363166809082031250000000001",
|
||||
});
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
// only complete number tokens: the conversion expects validated input
|
||||
if (!token.empty() && json::accept(token))
|
||||
{
|
||||
CAPTURE(token)
|
||||
check_float_native<double>(guard, token);
|
||||
check_float_native<float>(guard, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("JSON")
|
||||
{
|
||||
for (const auto& input : json_seeds())
|
||||
{
|
||||
CAPTURE(input)
|
||||
const std::string expected = outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(input, nullptr, allow);
|
||||
});
|
||||
const std::string expected_comments = outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(input, nullptr, allow, true);
|
||||
});
|
||||
const bool expected_accept = json::accept(input);
|
||||
counting_sax expected_sax;
|
||||
const bool expected_sax_result = json::sax_parse(input, &expected_sax);
|
||||
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
const std::uint8_t* const last = first + input.size();
|
||||
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(first, last, nullptr, allow);
|
||||
}) == expected);
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(first, last, nullptr, allow, true);
|
||||
}) == expected_comments);
|
||||
CHECK(json::accept(first, last) == expected_accept);
|
||||
|
||||
counting_sax sax;
|
||||
CHECK(json::sax_parse(first, last, &sax) == expected_sax_result);
|
||||
CHECK(sax.events == expected_sax.events);
|
||||
CHECK(sax.error == expected_sax.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
// every prefix ends inside a value, e.g., inside a BSON key or BON8
|
||||
// string without its terminator
|
||||
const json j = binary_document();
|
||||
check_binary(guard, json::to_bson(j), read_bson());
|
||||
check_binary(guard, json::to_bon8(j), read_bon8());
|
||||
check_binary(guard, json::to_cbor(j), read_cbor());
|
||||
check_binary(guard, json::to_msgpack(j), read_msgpack());
|
||||
check_binary(guard, json::to_ubjson(j), read_ubjson());
|
||||
check_binary(guard, json::to_bjdata(j), read_bjdata());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,122 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-iterators2.cpp (iterators and
|
||||
// std::ranges). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// iterators2.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
TEST_CASE("iterators 2 (C++20)")
|
||||
{
|
||||
#if JSON_HAS_RANGES
|
||||
SECTION("ranges")
|
||||
{
|
||||
SECTION("concepts")
|
||||
{
|
||||
using nlohmann::detail::iteration_proxy_value;
|
||||
CHECK(std::bidirectional_iterator<json::iterator>);
|
||||
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
||||
|
||||
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
||||
CHECK(std::ranges::bidirectional_range<json>);
|
||||
|
||||
using nlohmann::detail::iteration_proxy;
|
||||
using items_type = decltype(std::declval<json&>().items());
|
||||
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
||||
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
||||
CHECK(std::ranges::input_range<items_type>);
|
||||
}
|
||||
|
||||
SECTION("algorithms")
|
||||
{
|
||||
SECTION("copy")
|
||||
{
|
||||
json j{"foo", "bar"};
|
||||
auto j_copied = json::array();
|
||||
|
||||
std::ranges::copy(j, std::back_inserter(j_copied));
|
||||
|
||||
CHECK(j == j_copied);
|
||||
}
|
||||
|
||||
SECTION("find_if")
|
||||
{
|
||||
json j{1, 3, 2, 4};
|
||||
auto j_even = json::array();
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
auto it = std::ranges::find_if(j, [](int v) noexcept
|
||||
{
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#else
|
||||
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
||||
{
|
||||
int v;
|
||||
j.get_to(v);
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
CHECK(*it == 2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("views")
|
||||
{
|
||||
SECTION("reverse")
|
||||
{
|
||||
json j{1, 2, 3, 4, 5};
|
||||
json j_expected{5, 4, 3, 2, 1};
|
||||
|
||||
auto reversed = j | std::views::reverse;
|
||||
CHECK(reversed == j_expected);
|
||||
}
|
||||
|
||||
SECTION("transform")
|
||||
{
|
||||
json j
|
||||
{
|
||||
{ "a_key", "a_value"},
|
||||
{ "b_key", "b_value"},
|
||||
{ "c_key", "c_value"},
|
||||
};
|
||||
json j_expected{"a_key", "b_key", "c_key"};
|
||||
|
||||
// NOLINTNEXTLINE(fuchsia-trailing-return)
|
||||
auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
|
||||
{
|
||||
return item.key();
|
||||
});
|
||||
auto j_transformed = json::array();
|
||||
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
||||
|
||||
CHECK(j_transformed == j_expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,17 +6,23 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-iterators2-cpp<NN>.cpp. This file stays C++11-only.
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
TEST_CASE("iterators 2")
|
||||
{
|
||||
SECTION("iterator comparisons")
|
||||
@@ -866,4 +872,94 @@ TEST_CASE("iterators 2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
SECTION("ranges")
|
||||
{
|
||||
SECTION("concepts")
|
||||
{
|
||||
using nlohmann::detail::iteration_proxy_value;
|
||||
CHECK(std::bidirectional_iterator<json::iterator>);
|
||||
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
||||
|
||||
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
||||
CHECK(std::ranges::bidirectional_range<json>);
|
||||
|
||||
using nlohmann::detail::iteration_proxy;
|
||||
using items_type = decltype(std::declval<json&>().items());
|
||||
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
||||
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
||||
CHECK(std::ranges::input_range<items_type>);
|
||||
}
|
||||
|
||||
SECTION("algorithms")
|
||||
{
|
||||
SECTION("copy")
|
||||
{
|
||||
json j{"foo", "bar"};
|
||||
auto j_copied = json::array();
|
||||
|
||||
std::ranges::copy(j, std::back_inserter(j_copied));
|
||||
|
||||
CHECK(j == j_copied);
|
||||
}
|
||||
|
||||
SECTION("find_if")
|
||||
{
|
||||
json j{1, 3, 2, 4};
|
||||
auto j_even = json::array();
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
auto it = std::ranges::find_if(j, [](int v) noexcept
|
||||
{
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#else
|
||||
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
||||
{
|
||||
int v;
|
||||
j.get_to(v);
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
CHECK(*it == 2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("views")
|
||||
{
|
||||
SECTION("reverse")
|
||||
{
|
||||
json j{1, 2, 3, 4, 5};
|
||||
json j_expected{5, 4, 3, 2, 1};
|
||||
|
||||
auto reversed = j | std::views::reverse;
|
||||
CHECK(reversed == j_expected);
|
||||
}
|
||||
|
||||
SECTION("transform")
|
||||
{
|
||||
json j
|
||||
{
|
||||
{ "a_key", "a_value"},
|
||||
{ "b_key", "b_value"},
|
||||
{ "c_key", "c_value"},
|
||||
};
|
||||
json j_expected{"a_key", "b_key", "c_key"};
|
||||
|
||||
// NOLINTNEXTLINE(fuchsia-trailing-return)
|
||||
auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
|
||||
{
|
||||
return item.key();
|
||||
});
|
||||
auto j_transformed = json::array();
|
||||
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
||||
|
||||
CHECK(j_transformed == j_expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-json_pointer.cpp (JSON pointer comparison
|
||||
// with C++20 rewritten candidates and operator<=>, and char8_t literals). It is kept in a
|
||||
// separate translation unit so the (much larger) unit-json_pointer.cpp is built for C++11
|
||||
// only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
||||
// line *before* including json.hpp, since the library #undefs it once the header
|
||||
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
||||
// tests of deprecated functions are skipped if these functions are deleted
|
||||
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
||||
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
#include <compare>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
TEST_CASE("JSON pointers (C++20)")
|
||||
{
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
std::string ptr_string{"/foo/bar"};
|
||||
auto ptr1 = json::json_pointer(ptr_string);
|
||||
auto ptr2 = json::json_pointer(ptr_string);
|
||||
|
||||
CHECK(ptr1 == ptr2);
|
||||
|
||||
CHECK_FALSE(ptr1 != ptr2);
|
||||
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
const char* ptr_cpstring = "/foo/bar";
|
||||
const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
|
||||
|
||||
CHECK(ptr1 == "/foo/bar");
|
||||
CHECK(ptr1 == ptr_cpstring);
|
||||
CHECK(ptr1 == ptr_castring);
|
||||
CHECK(ptr1 == ptr_string);
|
||||
|
||||
CHECK("/foo/bar" == ptr1);
|
||||
CHECK(ptr_cpstring == ptr1);
|
||||
CHECK(ptr_castring == ptr1);
|
||||
CHECK(ptr_string == ptr1);
|
||||
|
||||
CHECK_FALSE(ptr1 != "/foo/bar");
|
||||
CHECK_FALSE(ptr1 != ptr_cpstring);
|
||||
CHECK_FALSE(ptr1 != ptr_castring);
|
||||
CHECK_FALSE(ptr1 != ptr_string);
|
||||
|
||||
CHECK_FALSE("/foo/bar" != ptr1);
|
||||
CHECK_FALSE(ptr_cpstring != ptr1);
|
||||
CHECK_FALSE(ptr_castring != ptr1);
|
||||
CHECK_FALSE(ptr_string != ptr1);
|
||||
|
||||
SECTION("exceptions")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(ptr1 == "foo",
|
||||
"[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS("foo" == ptr1,
|
||||
"[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(ptr1 == "/~~",
|
||||
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS("/~~" == ptr1,
|
||||
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("less-than comparison")
|
||||
{
|
||||
auto ptr1 = json::json_pointer("/foo/a");
|
||||
auto ptr2 = json::json_pointer("/foo/b");
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
|
||||
CHECK(ptr2 > ptr1);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("backwards compatibility and mixing")
|
||||
{
|
||||
json j = R"(
|
||||
{
|
||||
"foo": ["bar", "baz"]
|
||||
}
|
||||
)"_json;
|
||||
|
||||
using nlohmann::ordered_json;
|
||||
using json_ptr_str = nlohmann::json_pointer<std::string>;
|
||||
using json_ptr_j = nlohmann::json_pointer<json>;
|
||||
using json_ptr_oj = nlohmann::json_pointer<ordered_json>;
|
||||
|
||||
std::string const ptr_string{"/foo/0"};
|
||||
json_ptr_str ptr{ptr_string};
|
||||
json_ptr_j ptr_j{ptr_string};
|
||||
json_ptr_oj ptr_oj{ptr_string};
|
||||
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
CHECK(ptr == ptr_j);
|
||||
CHECK(ptr == ptr_oj);
|
||||
CHECK(ptr_j == ptr);
|
||||
CHECK(ptr_j == ptr_oj);
|
||||
CHECK(ptr_oj == ptr_j);
|
||||
CHECK(ptr_oj == ptr);
|
||||
|
||||
CHECK_FALSE(ptr != ptr_j);
|
||||
CHECK_FALSE(ptr != ptr_oj);
|
||||
CHECK_FALSE(ptr_j != ptr);
|
||||
CHECK_FALSE(ptr_j != ptr_oj);
|
||||
CHECK_FALSE(ptr_oj != ptr_j);
|
||||
CHECK_FALSE(ptr_oj != ptr);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json_pointer with char8_t literals #4945")
|
||||
{
|
||||
const json j = R"({"a": {"b": {"c": 123}}})"_json;
|
||||
const auto p1 = "/a/b/c"_json_pointer;
|
||||
CHECK(j[p1] == 123);
|
||||
|
||||
const auto p2 = u8"/a/b/c"_json_pointer;
|
||||
CHECK(j[p2] == 123);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -779,13 +779,14 @@ TEST_CASE("JSON pointers")
|
||||
CHECK(ptr.to_string() == "/object/~1");
|
||||
}
|
||||
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
std::string ptr_string{"/foo/bar"};
|
||||
auto ptr1 = json::json_pointer(ptr_string);
|
||||
auto ptr2 = json::json_pointer(ptr_string);
|
||||
|
||||
// build with C++20 to test rewritten candidates
|
||||
// JSON_HAS_CPP_20
|
||||
|
||||
CHECK(ptr1 == ptr2);
|
||||
|
||||
@@ -828,7 +829,6 @@ TEST_CASE("JSON pointers")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("less-than comparison")
|
||||
{
|
||||
@@ -838,6 +838,12 @@ TEST_CASE("JSON pointers")
|
||||
CHECK(ptr1 < ptr2);
|
||||
CHECK_FALSE(ptr2 < ptr1);
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
|
||||
CHECK(ptr2 > ptr1);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("usable as map key")
|
||||
@@ -893,9 +899,10 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_FALSE(ptr != ptr_j);
|
||||
CHECK_FALSE(ptr != ptr_oj);
|
||||
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
// build with C++20 to test rewritten candidates
|
||||
// JSON_HAS_CPP_20
|
||||
|
||||
CHECK(ptr == ptr_j);
|
||||
CHECK(ptr == ptr_oj);
|
||||
@@ -911,7 +918,6 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_FALSE(ptr_oj != ptr_j);
|
||||
CHECK_FALSE(ptr_oj != ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("value(json_pointer, default) with ordered_json #5664")
|
||||
@@ -933,6 +939,19 @@ TEST_CASE("JSON pointers")
|
||||
CHECK(j.value(ptr_missing, 42) == 42);
|
||||
}
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json_pointer with char8_t literals #4945")
|
||||
{
|
||||
const json j = R"({"a": {"b": {"c": 123}}})"_json;
|
||||
const auto p1 = "/a/b/c"_json_pointer;
|
||||
CHECK(j[p1] == 123);
|
||||
|
||||
const auto p2 = u8"/a/b/c"_json_pointer;
|
||||
CHECK(j[p2] == 123);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
||||
|
||||
@@ -470,31 +470,27 @@ TEST_CASE("json_view")
|
||||
{
|
||||
using nlohmann::detail::is_detected;
|
||||
|
||||
// a length is not a flag: parse(ptr, len) would convert len to
|
||||
// allow_exceptions and read ptr as a C string, which need not end
|
||||
static_assert(is_detected<parse_call_t, const char*, bool>::value, "parse(ptr, bool) is valid");
|
||||
static_assert(is_detected<parse_call_t, const char*, bool, bool, bool>::value, "parse(ptr, bool, bool, bool) is valid");
|
||||
static_assert(is_detected<parse_call_t, const char*, const char*>::value, "parse(first, last) is valid");
|
||||
static_assert(is_detected<parse_call_t, const char*, const char*, bool>::value, "parse(first, last, bool) is valid");
|
||||
static_assert(is_detected<parse_copy_call_t, const char*, bool>::value, "parse_copy(ptr, bool) is valid");
|
||||
static_assert(is_detected<accept_call_t, const char*, bool>::value, "accept(ptr, bool) is valid");
|
||||
static_assert(is_detected<read_call_t, const char*, bool>::value, "read(ptr, bool) is valid");
|
||||
|
||||
// a length is not a flag: parse(ptr, len) would convert len to
|
||||
// allow_exceptions and read ptr as a C string, which need not end
|
||||
//
|
||||
// MSVC 2015 does not treat selecting a deleted function in decltype as
|
||||
// a substitution failure, so it detects the deleted overloads as
|
||||
// callable; calling them still fails to compile
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910)
|
||||
static_assert(!is_detected<parse_call_t, const char*, std::size_t>::value, "parse(ptr, len) must not compile");
|
||||
static_assert(!is_detected<parse_call_t, const char*, int>::value, "parse(ptr, int) must not compile");
|
||||
static_assert(!is_detected<parse_call_t, const char*, char>::value, "parse(ptr, char) must not compile");
|
||||
static_assert(!is_detected<parse_call_t, const char*, std::size_t, bool>::value, "parse(ptr, len, bool) must not compile");
|
||||
static_assert(!is_detected<parse_call_t, const std::string&, std::size_t>::value, "parse(string, len) must not compile");
|
||||
static_assert(!is_detected<parse_call_t, const std::vector<char>&, std::size_t>::value, "parse(vector, len) must not compile");
|
||||
|
||||
static_assert(is_detected<parse_copy_call_t, const char*, bool>::value, "parse_copy(ptr, bool) is valid");
|
||||
static_assert(!is_detected<parse_copy_call_t, const char*, std::size_t>::value, "parse_copy(ptr, len) must not compile");
|
||||
|
||||
static_assert(is_detected<accept_call_t, const char*, bool>::value, "accept(ptr, bool) is valid");
|
||||
static_assert(!is_detected<accept_call_t, const char*, std::size_t>::value, "accept(ptr, len) must not compile");
|
||||
|
||||
static_assert(is_detected<read_call_t, const char*, bool>::value, "read(ptr, bool) is valid");
|
||||
static_assert(!is_detected<read_call_t, const char*, std::size_t>::value, "read(ptr, len) must not compile");
|
||||
#endif
|
||||
|
||||
// json_view has no conversion to bool: unlike basic_json's, it would
|
||||
// mean "exists", not "is not null"; use is_discarded()
|
||||
@@ -689,7 +685,7 @@ void check_access(const ordered_json_view& v, const ordered_json& j)
|
||||
std::size_t occurrences = 0;
|
||||
for (auto other = v.begin(); other != v.end(); ++other)
|
||||
{
|
||||
occurrences += other.key() == it.key() ? 1u : 0u;
|
||||
occurrences += other.key() == it.key() ? 1 : 0;
|
||||
}
|
||||
CHECK(v.count(key) == occurrences);
|
||||
// lookups find the first member with the key, which is this one if
|
||||
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user