Compare commits

..
Author SHA1 Message Date
Niels Lohmann 3250b5ce46 Rename two more json_view test helpers that clash in the unity build
In one batch, the exception_of() calls of unit-json_view_image.cpp bind
to the exception_of(F) template of unit-json_view.cpp, so GCC reports the
file's own exception_of() as unused (-Werror=unused-function), and a loop
variable counter in unit-json_view_image.cpp hides the counter of
unit-json_view_edit.cpp (MSVC C4459).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 15:17:30 +02:00
Niels Lohmann 52724f5ae5 Rename the json_view test helpers that clash in the unity build
unit-json_view_builder.cpp has its own document generator besides
json_view_test::generator, and unit-json_view_edit.cpp and
unit-json_view_image.cpp define rng() and bits() like unit-json_view.cpp,
each in an anonymous namespace. In one batch these are ambiguous or
redefined, so give the file-local ones their own names.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 11:05:27 +02:00
Niels Lohmann d01e6382fe Merge branch 'develop' into claude/test-compilation-speed-993125
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:45:45 +02:00
Niels Lohmann 589c29d69b Keep the unit-comparison.cpp types out of the anonymous namespace
GCC reports a basic_json specialized with a type of an anonymous
namespace in a file #include-d into a batch (-Wsubobject-linkage), so
only the helper functions go into the anonymous namespace.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 09:49:13 +02:00
Niels Lohmann 80fd9c0fb4 Build files that suppress -Wnoexcept on their own
unit-udt.cpp suppresses GCC's -Wnoexcept around its include of the
library just like unit-noexcept.cpp, so instead of listing the files,
leave every file that suppresses the warning out of the batches.

Move the helpers of unit-comparison.cpp into an anonymous namespace
inside unit_comparison_detail, as clang-tidy's misc-use-internal-linkage
wants internal linkage for them.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 07:45:41 +02:00
Niels Lohmann 169f1fcb2d Fix the Ubuntu CI failures of the unity test build
- build unit-noexcept.cpp on its own: it suppresses GCC's -Wnoexcept
  around its include of the library, which has no effect once another
  file of the batch included the library first
- unit-comparison.cpp: make make_unordered_object inline
  (-Wmissing-prototypes in a named namespace) and replace the
  using-directive by using-declarations, as Clang reports a
  using-directive in an #include-d file (-Wheader-hygiene)
- unit-json_pointer-cpp20.cpp: include <compare> only with
  JSON_HAS_THREE_WAY_COMPARISON; GCC 9 and Clang 9 accept C++20 but
  have no <compare>

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 05:43:28 +02:00
Niels Lohmann 020a834128 Merge branch 'develop' into claude/test-compilation-speed-993125
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 01:29:55 +02:00
Niels Lohmann 7dcff6b8cd Merge branch 'develop' into claude/test-compilation-speed-993125
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 23:45:36 +02:00
Niels Lohmann 769c5cde57 Fix CI failures of the unity test build
- keep JSON_DISABLE_ENUM_SERIALIZATION and
  JSON_DISABLE_TUPLE_REFERENCE_CONVERSION defined with
  JSON_TEST_KEEP_MACROS, so that every file of a batch sees them, not only
  the first one
- mark JSON_TESTS_PRIVATE in the generated batch file as used, as GCC and
  Clang report an unused definition when the first file defines it again
  (-Wunused-macros)
- suppress bugprone-suspicious-include for the #include-d .cpp files
- avoid local json aliases and variables that hide a global json of
  another file in the same batch (MSVC C4459)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 23:23:04 +02:00
Niels Lohmann 993e37e7ec Reduce test compile times (spike)
Two changes roughly halve the CPU time needed to build the unit tests
(clang 21: ~300 s -> ~150 s, GCC 16: ~750-820 s -> ~395-420 s, Debug):

- Move the C++14/17/20-dependent tests into separate
  unit-<name>-cpp<N>.cpp files. A test file is built for every standard
  whose JSON_HAS_CPP_<N> macro it mentions, so far whole large files were
  rebuilt for C++14/17/20 because of a few #ifdef sections. The main files
  are now built for C++11 only; the ci_test_*_cxx<N> jobs still build
  every file for every standard.

- Add the CMake option JSON_TestUnityBuild (ON by default, OFF with
  MinGW): compatible test files are compiled in batches as one
  translation unit so they share the template instantiations of the
  library. Each file keeps its own CTest test, which runs the batch
  executable filtered to that file's test cases. The binary-format tests
  form an explicit group; the rest is batched by JSON_TestUnityBatchSize.

Fix the name clashes that merging files exposed, document the rules for
test files in tests/README.md, and point CONTRIBUTING.md to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-09 16:49:45 +02:00
67 changed files with 4363 additions and 3976 deletions

No files matched your search

+10 -3
View File
@@ -108,9 +108,16 @@ 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.
`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.
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.
#### Exceptions
+3 -3
View File
@@ -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, test-comparison_legacy_cpp20
# - test-comparison-cpp20_cpp20, test-comparison_legacy-cpp20_cpp20
# miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
# - test-constructor1_cpp11
# std::initializer_list lifetime bug -> SIGSEGV
# - test-deserialization_cpp20
# - test-deserialization-cpp20_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\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
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
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
)
+366 -39
View File
@@ -109,6 +109,55 @@ 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})
@@ -143,45 +192,7 @@ function(_json_test_add_test test_name file main cxx_standard)
)
endif()
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()
_json_test_register_test(${test_name} ${test_target} ${cxx_standard})
endfunction()
#############################################################################
@@ -200,6 +211,14 @@ 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".
#############################################################################
@@ -243,6 +262,314 @@ 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>)
+19 -26
View File
@@ -20,38 +20,31 @@ below apply on every supported standard. This adapter exposes two optimizations
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
read, and
- the lexer can scan ordinary string characters, numbers, and indentation directly out of the buffer, several bytes at
a time, rather than one character (and one function call) at a time.
- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one
character (and one function call) at a time.
A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scanning: `input_stream_adapter` scans the
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
locks the file once per parse instead of once per character. See
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
parsing an equivalent `#!cpp std::string`.
A `#!cpp std::istream` (including `#!cpp std::ifstream`) or `#!cpp FILE*`, by contrast, is read through
`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time
and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any
non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g.
`#!cpp std::deque<char>::iterator`) gets the first optimization but not the second, since the byte-scanning fast path
additionally requires contiguous storage.
Inputs that are still read one character at a time: iterator ranges that are not contiguous (e.g.
`#!cpp std::list<char>` or `#!cpp std::istreambuf_iterator<char>`), wide strings, and user-defined adapters. An
iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::deque<char>::iterator`) gets the first
optimization but not the second, since the byte-scanning fast path requires contiguous storage.
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
into a string first is no longer necessary for speed:
Practically: if the JSON text is already in memory, or small enough to read into memory, prefer passing a
`#!cpp std::string`, a `#!cpp std::vector<char>`, or a pointer range to `parse` over a `#!cpp std::istream`. For a
file, that means reading it into a string first and then parsing the string, rather than passing a
`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization:
```cpp
// fast: the stream buffer's get area is scanned in bulk
// gets the contiguous fast paths
std::ifstream f("example.json");
json j = json::parse(f);
```
std::string contents((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
json j = json::parse(contents);
Two macros switch the stream 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 `#!cpp 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.
// does not: input_stream_adapter has no fast path
std::ifstream f2("example.json");
json j2 = json::parse(f2);
```
For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can
additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of
+11
View File
@@ -235,6 +235,17 @@ 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`.
+2 -96
View File
@@ -14,24 +14,11 @@
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
#endif
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
#include <intrin.h> // _BitScanForward, _BitScanReverse
#endif
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
#endif
// Portable bit-level helpers for the number and string scanners. They use
// compiler builtins or platform-specific intrinsics where available, the
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
// of byte order. (The builtins and intrinsics come first: MSVC's
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
// compiler builtins or platform-specific intrinsics where available and plain
// C++ otherwise, so they work regardless of byte order.
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -46,16 +33,6 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanReverse64(&index, x);
return 63 - static_cast<int>(index);
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
{
return 31 - static_cast<int>(index);
}
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
return 63 - static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countl_zero(x);
#else
int n = 0;
for (int shift = 32; shift != 0; shift >>= 1)
@@ -79,16 +56,6 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanForward64(&index, x);
return static_cast<int>(index);
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
{
return static_cast<int>(index);
}
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
return 32 + static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countr_zero(x);
#else
int n = 0;
for (int shift = 32; shift != 0; shift >>= 1)
@@ -103,67 +70,6 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
#endif
}
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
template<typename To, typename From>
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
{
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
#if defined(__cpp_lib_bit_cast)
return std::bit_cast<To>(from);
#else
To to;
std::memcpy(&to, &from, sizeof(To));
return to;
#endif
}
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
/// it to a single bswap/rev/movbe
inline std::uint16_t byteswap(std::uint16_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap16(x);
#elif defined(_MSC_VER)
return _byteswap_ushort(x);
#elif defined(__cpp_lib_byteswap)
return std::byteswap(x);
#else
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
#endif
}
/// x with its bytes in reverse order (std::byteswap in C++23)
inline std::uint32_t byteswap(std::uint32_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#elif defined(_MSC_VER)
return _byteswap_ulong(x);
#elif defined(__cpp_lib_byteswap)
return std::byteswap(x);
#else
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
#endif
}
/// x with its bytes in reverse order (std::byteswap in C++23)
inline std::uint64_t byteswap(std::uint64_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#elif defined(_MSC_VER)
return _byteswap_uint64(x);
#elif defined(__cpp_lib_byteswap)
return std::byteswap(x);
#else
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
return x;
#endif
}
/// the 128-bit product of two 64-bit numbers
struct uint128_parts
{
@@ -18,6 +18,9 @@
#include <limits> // numeric_limits
#include <type_traits> // conditional
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_uint64
#endif
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
// digits of a double at once
@@ -34,7 +37,6 @@
#define JSON_DTOA_NEON 0
#endif
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/conversions/zmij.hpp>
#include <nlohmann/detail/macro_scope.hpp>
@@ -64,6 +66,16 @@ For a detailed description of the algorithm see:
namespace dtoa_impl
{
template<typename Target, typename Source>
Target reinterpret_bits(const Source source)
{
static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
Target target;
std::memcpy(&target, &source, sizeof(Source));
return target;
}
struct diyfp // f * 2^e
{
static constexpr int kPrecision = 64; // = q
@@ -218,7 +230,7 @@ boundaries compute_boundaries(FloatType value)
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));
const std::uint64_t E = bits >> (kPrecision - 1);
const std::uint64_t F = bits & (kHiddenBit - 1);
@@ -1072,11 +1084,14 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
/// the byte stores once this is inlined)
inline void store_msb_first(char* p, std::uint64_t v) noexcept
{
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets)
v = byteswap(v);
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
v = __builtin_bswap64(v);
std::memcpy(p, &v, sizeof(v));
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
std::memcpy(p, &v, sizeof(v));
#elif defined(_MSC_VER) // (little-endian on all its targets)
v = _byteswap_uint64(v);
std::memcpy(p, &v, sizeof(v));
#else
for (unsigned i = 0; i < 8; ++i)
{
@@ -1447,7 +1462,8 @@ char* write_positive_zmij(char* first, const char* last, FloatType value)
{
static_assert(is_binary64<FloatType>::value,
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
const auto bits = bit_cast<std::uint64_t>(value);
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits);
if (JSON_HEDLEY_LIKELY(last - first >= 41))
{
+14 -111
View File
@@ -1625,12 +1625,9 @@ scan_number_done:
const std::size_t avail = rem + 1;
// validate + classify the number extent (mirrors scan_number()'s grammar)
// and accumulate the mantissa digits on the way, so that tokens with up
// to 19 digits need no second pass for their value
std::size_t i = 0;
std::size_t dot_index = std::string::npos;
token_type number_type = token_type::value_unsigned;
std::uint64_t w = 0;
if (data[0] == '-')
{
number_type = token_type::value_integer;
@@ -1640,21 +1637,22 @@ scan_number_done:
return token_type::uninitialized;
}
}
const std::size_t integer_begin = i;
if (data[i] == '0')
{
++i;
}
else if (data[i] >= '1' && data[i] <= '9')
{
i = accumulate_digits(data, i, avail, w);
++i;
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
++i;
}
}
else
{
return token_type::uninitialized;
}
std::size_t digit_count = i - integer_begin;
std::size_t fraction_digits = 0;
if (i < avail && data[i] == '.')
{
number_type = token_type::value_float;
@@ -1664,39 +1662,29 @@ scan_number_done:
{
return token_type::uninitialized;
}
const std::size_t fraction_begin = i;
i = accumulate_digits(data, i, avail, w);
fraction_digits = i - fraction_begin;
digit_count += fraction_digits;
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
++i;
}
}
// the mantissa ends here, whether or not an exponent part follows
const std::size_t mantissa_end = i;
std::int64_t exponent = 0;
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
{
number_type = token_type::value_float;
++i;
bool negative_exponent = false;
if (i < avail && (data[i] == '+' || data[i] == '-'))
{
negative_exponent = data[i] == '-';
++i;
}
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
{
return token_type::uninitialized;
}
// saturated far beyond every range, see parse_float_exponent()
constexpr std::int64_t saturation = 100000000000000000; // 10^17
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
if (exponent < saturation)
{
exponent = (exponent * 10) + (data[i] - '0');
}
++i;
}
exponent = negative_exponent ? -exponent : exponent;
}
const std::size_t len = i;
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
@@ -1717,10 +1705,7 @@ scan_number_done:
#if !JSON_DIAGNOSTIC_POSITIONS
if (number_type != token_type::value_float)
{
// up to 18 digits always fit into std::uint64_t and std::int64_t
const token_type integer_result = (digit_count <= 18)
? store_integer(number_type, w)
: convert_integer(number_type, data, data + len);
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);
@@ -1734,42 +1719,15 @@ scan_number_done:
}
#endif
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
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> {});
// a float whose digits all fit into w converts from the accumulated
// value; longer ones (and other float formats) take the general path
if (number_type == token_type::value_float && digit_count <= 19)
{
float_significand s;
s.w = w;
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
s.negative = data[0] == '-';
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
{
// the token text is only copied into token_buffer when
// get_string() asks for it; the library's own SAX handlers
// ignore it. A window into a stream may be refilled by the
// next read, so there the text is copied right away.
if (bulk_window)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
}
else
{
pending_number_text = data;
pending_number_length = len;
}
return token_type::value_float;
}
}
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
return convert_number(number_type, mantissa_end);
}
@@ -1798,35 +1756,6 @@ scan_number_done:
/// the input is not handed back, so the terminator can stay unread
void read_number_terminator(std::false_type /*window*/) const noexcept {}
/// binary32/binary64: the value of a float token from its accumulated
/// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{
value_float = decimal_to_float<number_float_t>(s);
return true;
}
/// other formats (e.g. x87 long double): not instantiated for them, as
/// decimal_to_float() only supports binary32/binary64
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
{
return false;
}
/// the value of an integer token with at most 18 digits, accumulated in @a w
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
{
if (number_type == token_type::value_unsigned)
{
value_unsigned = static_cast<number_unsigned_t>(w);
// reject values that do not round-trip into a narrower type
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
}
const std::int64_t x = -static_cast<std::int64_t>(w);
value_integer = static_cast<number_integer_t>(x);
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
}
/// contiguous input: try the number fast path, else the byte-path scanner
token_type scan_number_dispatch(std::true_type /*bulk*/)
{
@@ -1869,7 +1798,6 @@ scan_number_done:
void reset() noexcept
{
token_buffer.clear();
pending_number_text = nullptr;
decimal_point_position = std::string::npos;
#if JSON_DIAGNOSTIC_POSITIONS
@@ -2129,30 +2057,10 @@ scan_number_done:
/// return current string value
string_t& get_string()
{
if (pending_number_text != nullptr)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
pending_number_text = nullptr;
}
// a number token holds '.' regardless of the locale (#4084)
return token_buffer;
}
/// the value of a string token (key or string), which never has pending
/// number text, so get_string()'s check is not needed
string_t& get_string_token() noexcept
{
JSON_ASSERT(pending_number_text == nullptr);
return token_buffer;
}
/// the text of a float token for a SAX handler that ignores it (the
/// library's own handlers): empty, without copying the token
const string_t& get_ignored_number_text() const noexcept
{
return token_buffer;
}
/////////////////////
// diagnostics
/////////////////////
@@ -2548,11 +2456,6 @@ scan_number_done:
/// whether the next get() call should just return current
bool next_unget = false;
/// the text of the current float token in the input, not yet copied into
/// token_buffer (see get_string()), or nullptr
const char* pending_number_text = nullptr;
std::size_t pending_number_length = 0;
/// the start position of the current token
position_t position {};
+30 -74
View File
@@ -16,6 +16,7 @@
#include <cstddef> // size_t
#include <cstdint> // int64_t, uint64_t
#include <cstdlib> // strtof, strtod, strtold
#include <cstring> // memcpy
#include <limits> // numeric_limits
#include <string> // string
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
@@ -274,77 +275,13 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
}
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
/// digits (after fast_float)
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
/// whether [first, last) contains a digit other than '0'
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
{
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
}
/*!
@brief skip the ASCII digits starting at data[i] and accumulate their value
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
around after 19 digits; the caller only uses it for shorter digit runs.
@return the index of the first byte at or after @a i that is not a digit
*/
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
{
while (avail - i >= 8)
return std::any_of(first, last, [](char c) noexcept
{
const std::uint64_t v = read_eight_bytes(data + i);
if (!is_eight_digits(v))
{
break;
}
w = (w * 100000000u) + parse_eight_digits(v);
i += 8;
}
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
++i;
}
return i;
}
/// whether [first, last) contains a digit other than '0'; a plain loop, as
/// GCC calls std::any_of out of line even for the usual empty range
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
{
for (; first != last; ++first)
{
if (*first != '0')
{
return true;
}
}
return false;
}
/*!
@brief accumulate the validated digits [p, last) into @a w, as long as
@a remaining digits still fit into it
Eight digits are taken at a time while at least eight remain, and @a remaining
is decreased by the number of digits taken.
@return a pointer past the last digit taken
*/
JSON_HEDLEY_ALWAYS_INLINE const char* accumulate_significant_digits(const char* p, const char* last, int& remaining, std::uint64_t& w) noexcept
{
while (remaining >= 8 && last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
return p;
return c != '0';
});
}
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
@@ -390,7 +327,7 @@ truncated is set.
std::string::npos if there is none
@param[in] mantissa_end index of the 'e'/'E', or the token length
*/
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
inline float_significand parse_float_significand(const char* first, const char* last,
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
{
float_significand s;
@@ -405,7 +342,16 @@ JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char*
int remaining = 19; // digits that still fit into w
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
{
p = accumulate_significant_digits(p, integer_last, remaining, w);
while (remaining >= 8 && integer_last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != integer_last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
s.exponent = integer_last - p;
s.truncated = has_nonzero_digit(p, integer_last);
}
@@ -424,7 +370,16 @@ JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char*
s.exponent -= p - zeros;
}
const char* const digits = p;
p = accumulate_significant_digits(p, mantissa_last, remaining, w);
while (remaining >= 8 && mantissa_last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != mantissa_last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
s.exponent -= p - digits;
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
}
@@ -757,14 +712,15 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
{
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
value = bit_cast<double>(bits);
std::memcpy(&value, &bits, sizeof(value));
}
/// the float with the IEEE-754 bits @a bits (the lower 32)
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
{
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
value = bit_cast<float>(static_cast<std::uint32_t>(bits));
const auto bits32 = static_cast<std::uint32_t>(bits);
std::memcpy(&value, &bits32, sizeof(bits32));
}
/// the powers of ten that are exact in binary64 (up to 10^22)
+4 -35
View File
@@ -51,20 +51,6 @@ template<typename BasicJsonType>
using parser_callback_t =
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
/// whether a SAX handler ignores the text of float tokens (the library's own
/// handlers do), so that the parser need not materialize it
template<typename SAX>
struct sax_ignores_number_text : std::false_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType>
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
/*!
@brief syntax analysis
@@ -185,23 +171,6 @@ class parser
}
private:
/// the text of the current float token for @a sax (see sax_ignores_number_text)
template<typename SAX>
const string_t& number_float_text(SAX* /*sax*/)
{
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
}
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
{
return m_lexer.get_ignored_number_text();
}
string_t& number_float_text(std::false_type /*ignored*/)
{
return m_lexer.get_string();
}
/*!
@brief run a DOM SAX parser to completion and position the lexer
@@ -278,7 +247,7 @@ class parser
{
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
}
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{
return false;
}
@@ -332,7 +301,7 @@ class parser
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
}
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
{
return false;
}
@@ -378,7 +347,7 @@ class parser
case token_type::value_string:
{
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
{
return false;
}
@@ -487,7 +456,7 @@ class parser
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
}
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{
return false;
}
+2 -2
View File
@@ -19,8 +19,6 @@
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
#undef JSON_INLINE_VARIABLE
#undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#undef JSON_UNLOCKED_FILE_IO
#undef JSON_DTOA_SSE2
#undef JSON_DTOA_NEON
@@ -48,6 +46,8 @@
#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
+3 -25
View File
@@ -10,26 +10,17 @@
#pragma once
#include <cstddef> // size_t
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
#include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <algorithm> // clamp
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
#ifdef __cpp_lib_remove_cvref
template<typename T>
using uncvref_t = std::remove_cvref_t<T>;
#else
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#endif
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#ifdef JSON_HAS_CPP_14
@@ -169,18 +160,5 @@ struct static_const
constexpr T static_const<T>::value;
#endif
/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp,
/// but by value, so that static constexpr members can be passed without an
/// out-of-line definition before C++17
template<typename T, enable_if_t<std::is_arithmetic<T>::value, int> = 0>
constexpr T clamp(T v, T lo, T hi) noexcept
{
#ifdef __cpp_lib_clamp
return std::clamp(v, lo, hi);
#else
return v < lo ? lo : (hi < v ? hi : v);
#endif
}
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+2 -16
View File
@@ -313,14 +313,6 @@ struct char_traits<std::byte> : std::char_traits<char>
// is_ functions //
///////////////////
#ifdef __cpp_lib_logical_traits
using std::conjunction;
using std::disjunction;
using std::negation;
#else
// https://en.cppreference.com/w/cpp/types/conjunction
template<class...> struct conjunction : std::true_type { };
template<class B> struct conjunction<B> : B { };
@@ -338,8 +330,6 @@ struct disjunction<B, Bn...>
// https://en.cppreference.com/w/cpp/types/negation
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
#endif
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
@@ -1070,12 +1060,8 @@ constexpr bool value_in_range_of(T val)
return value_in_range_of_impl1<OfType, T>::test(val);
}
#ifdef __cpp_lib_bool_constant
using std::bool_constant;
#else
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
#endif
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
// Stands in for number_unsigned_t in overload sets if it is the same type as
// number_integer_t, so that the unsigned overloads do not collide with the
@@ -19,6 +19,10 @@
#include <utility> // move
#include <vector> // vector
#ifdef _MSC_VER
#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>
@@ -2069,7 +2073,7 @@ class binary_writer
{
out[0] = to_char_type(first_byte);
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
const std::uint64_t tail = is_little_endian ? byteswap(value) >> unused_bits : value << unused_bits;
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;
}
@@ -3513,14 +3517,53 @@ class binary_writer
// Utility functions //
///////////////////////
// single-instruction byte swaps (compilers lower these to bswap/rev/movbe);
// used to emit big-endian numbers without a per-byte std::reverse loop
static std::uint16_t byte_swap(std::uint16_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap16(x);
#elif defined(_MSC_VER)
return _byteswap_ushort(x);
#else
return static_cast<std::uint16_t>((x >> 8) | (x << 8));
#endif
}
static std::uint32_t byte_swap(std::uint32_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#elif defined(_MSC_VER)
return _byteswap_ulong(x);
#else
return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8)
| ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24);
#endif
}
static std::uint64_t byte_swap(std::uint64_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#elif defined(_MSC_VER)
return _byteswap_uint64(x);
#else
x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32);
x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16);
x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8);
return x;
#endif
}
/*!
@brief reverse the bytes of a buffer by byte-swapping it as UIntType
Loading the buffer into an unsigned integer of the same width and swapping
that is what lets the compiler emit a single bswap/rev/movbe; reversing the
buffer element by element does not reliably get there (clang keeps a scalar
shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from
inlining differently in C++20, which made writing slower.)
shuffle). The two memcpy calls are the only portable way to reinterpret the
bytes and are folded away by every optimizer.
*/
template<typename UIntType, std::size_t N>
static void byte_swap_buffer(std::array<CharType, N>& a) noexcept
@@ -3528,11 +3571,11 @@ class binary_writer
static_assert(sizeof(UIntType) == N, "swap width must match the buffer size");
UIntType v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byteswap(v);
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
// reverse the bytes of a fixed-size buffer; a single byteswap() for the
// reverse the bytes of a fixed-size buffer; a single byte_swap() for the
// common 2/4/8-byte number payloads, std::reverse for any other size
static void reverse_bytes(std::array<CharType, 2>& a) noexcept
{
@@ -3657,7 +3700,9 @@ class binary_writer
#endif
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
return bit_cast<CharType>(x);
CharType result;
std::memcpy(&result, &x, sizeof(x));
return result;
}
template<typename C = CharType,
+10 -5
View File
@@ -105,13 +105,17 @@ class output_buffer
}
private:
/// the size of a growth step
static constexpr std::size_t step = 0x10000;
/// the size of a growth step (a function: std::min() takes a reference,
/// which a static constexpr member does not have before C++17)
static constexpr std::size_t step() noexcept
{
return 0x10000;
}
static StringType& sized(StringType& out, std::size_t estimate)
{
out.reserve(estimate);
out.resize(detail::clamp(estimate, std::size_t{64}, step));
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
return out;
}
@@ -120,7 +124,7 @@ class output_buffer
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation)
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
if (size > m_out.capacity())
{
m_out.reserve((std::max)(m_out.capacity() * 2, size));
@@ -790,7 +794,8 @@ class view_serializer
static char* write_double_value_at(char* w, double x)
{
// (from the bits: without the checks of to_chars())
auto bits = bit_cast<std::uint64_t>(x);
std::uint64_t bits = 0;
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{
return write_text_at(w, "null", 4);
+129 -371
View File
@@ -3678,27 +3678,18 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
// #include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <algorithm> // clamp
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
#ifdef __cpp_lib_remove_cvref
template<typename T>
using uncvref_t = std::remove_cvref_t<T>;
#else
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#endif
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
#ifdef JSON_HAS_CPP_14
@@ -3838,19 +3829,6 @@ struct static_const
constexpr T static_const<T>::value;
#endif
/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp,
/// but by value, so that static constexpr members can be passed without an
/// out-of-line definition before C++17
template<typename T, enable_if_t<std::is_arithmetic<T>::value, int> = 0>
constexpr T clamp(T v, T lo, T hi) noexcept
{
#ifdef __cpp_lib_clamp
return std::clamp(v, lo, hi);
#else
return v < lo ? lo : (hi < v ? hi : v);
#endif
}
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
@@ -4448,14 +4426,6 @@ struct char_traits<std::byte> : std::char_traits<char>
// is_ functions //
///////////////////
#ifdef __cpp_lib_logical_traits
using std::conjunction;
using std::disjunction;
using std::negation;
#else
// https://en.cppreference.com/w/cpp/types/conjunction
template<class...> struct conjunction : std::true_type { };
template<class B> struct conjunction<B> : B { };
@@ -4473,8 +4443,6 @@ struct disjunction<B, Bn...>
// https://en.cppreference.com/w/cpp/types/negation
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
#endif
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
@@ -5205,12 +5173,8 @@ constexpr bool value_in_range_of(T val)
return value_in_range_of_impl1<OfType, T>::test(val);
}
#ifdef __cpp_lib_bool_constant
using std::bool_constant;
#else
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
#endif
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
// Stands in for number_unsigned_t in overload sets if it is the same type as
// number_integer_t, so that the unsigned overloads do not collide with the
@@ -9716,25 +9680,12 @@ NLOHMANN_JSON_NAMESPACE_END
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
#endif
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
#include <intrin.h> // _BitScanForward, _BitScanReverse
#endif
// #include <nlohmann/detail/macro_scope.hpp>
// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
#endif
// Portable bit-level helpers for the number and string scanners. They use
// compiler builtins or platform-specific intrinsics where available, the
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
// of byte order. (The builtins and intrinsics come first: MSVC's
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
// compiler builtins or platform-specific intrinsics where available and plain
// C++ otherwise, so they work regardless of byte order.
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -9749,16 +9700,6 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanReverse64(&index, x);
return 63 - static_cast<int>(index);
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
{
return 31 - static_cast<int>(index);
}
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
return 63 - static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countl_zero(x);
#else
int n = 0;
for (int shift = 32; shift != 0; shift >>= 1)
@@ -9782,16 +9723,6 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
_BitScanForward64(&index, x);
return static_cast<int>(index);
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
{
return static_cast<int>(index);
}
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
return 32 + static_cast<int>(index);
#elif defined(__cpp_lib_bitops)
return std::countr_zero(x);
#else
int n = 0;
for (int shift = 32; shift != 0; shift >>= 1)
@@ -9806,67 +9737,6 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
#endif
}
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
template<typename To, typename From>
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
{
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
#if defined(__cpp_lib_bit_cast)
return std::bit_cast<To>(from);
#else
To to;
std::memcpy(&to, &from, sizeof(To));
return to;
#endif
}
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
/// it to a single bswap/rev/movbe
inline std::uint16_t byteswap(std::uint16_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap16(x);
#elif defined(_MSC_VER)
return _byteswap_ushort(x);
#elif defined(__cpp_lib_byteswap)
return std::byteswap(x);
#else
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
#endif
}
/// x with its bytes in reverse order (std::byteswap in C++23)
inline std::uint32_t byteswap(std::uint32_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#elif defined(_MSC_VER)
return _byteswap_ulong(x);
#elif defined(__cpp_lib_byteswap)
return std::byteswap(x);
#else
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
#endif
}
/// x with its bytes in reverse order (std::byteswap in C++23)
inline std::uint64_t byteswap(std::uint64_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#elif defined(_MSC_VER)
return _byteswap_uint64(x);
#elif defined(__cpp_lib_byteswap)
return std::byteswap(x);
#else
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
return x;
#endif
}
/// the 128-bit product of two 64-bit numbers
struct uint128_parts
{
@@ -9947,6 +9817,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t
#include <cstdint> // int64_t, uint64_t
#include <cstdlib> // strtof, strtod, strtold
#include <cstring> // memcpy
#include <limits> // numeric_limits
#include <string> // string
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
@@ -10584,77 +10455,13 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
}
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
/// digits (after fast_float)
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
/// whether [first, last) contains a digit other than '0'
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
{
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
}
/*!
@brief skip the ASCII digits starting at data[i] and accumulate their value
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
around after 19 digits; the caller only uses it for shorter digit runs.
@return the index of the first byte at or after @a i that is not a digit
*/
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
{
while (avail - i >= 8)
return std::any_of(first, last, [](char c) noexcept
{
const std::uint64_t v = read_eight_bytes(data + i);
if (!is_eight_digits(v))
{
break;
}
w = (w * 100000000u) + parse_eight_digits(v);
i += 8;
}
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
++i;
}
return i;
}
/// whether [first, last) contains a digit other than '0'; a plain loop, as
/// GCC calls std::any_of out of line even for the usual empty range
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
{
for (; first != last; ++first)
{
if (*first != '0')
{
return true;
}
}
return false;
}
/*!
@brief accumulate the validated digits [p, last) into @a w, as long as
@a remaining digits still fit into it
Eight digits are taken at a time while at least eight remain, and @a remaining
is decreased by the number of digits taken.
@return a pointer past the last digit taken
*/
JSON_HEDLEY_ALWAYS_INLINE const char* accumulate_significant_digits(const char* p, const char* last, int& remaining, std::uint64_t& w) noexcept
{
while (remaining >= 8 && last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
return p;
return c != '0';
});
}
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
@@ -10700,7 +10507,7 @@ truncated is set.
std::string::npos if there is none
@param[in] mantissa_end index of the 'e'/'E', or the token length
*/
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
inline float_significand parse_float_significand(const char* first, const char* last,
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
{
float_significand s;
@@ -10715,7 +10522,16 @@ JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char*
int remaining = 19; // digits that still fit into w
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
{
p = accumulate_significant_digits(p, integer_last, remaining, w);
while (remaining >= 8 && integer_last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != integer_last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
s.exponent = integer_last - p;
s.truncated = has_nonzero_digit(p, integer_last);
}
@@ -10734,7 +10550,16 @@ JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char*
s.exponent -= p - zeros;
}
const char* const digits = p;
p = accumulate_significant_digits(p, mantissa_last, remaining, w);
while (remaining >= 8 && mantissa_last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != mantissa_last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
s.exponent -= p - digits;
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
}
@@ -11067,14 +10892,15 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
{
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
value = bit_cast<double>(bits);
std::memcpy(&value, &bits, sizeof(value));
}
/// the float with the IEEE-754 bits @a bits (the lower 32)
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
{
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
value = bit_cast<float>(static_cast<std::uint32_t>(bits));
const auto bits32 = static_cast<std::uint32_t>(bits);
std::memcpy(&value, &bits32, sizeof(bits32));
}
/// the powers of ten that are exact in binary64 (up to 10^22)
@@ -13384,12 +13210,9 @@ scan_number_done:
const std::size_t avail = rem + 1;
// validate + classify the number extent (mirrors scan_number()'s grammar)
// and accumulate the mantissa digits on the way, so that tokens with up
// to 19 digits need no second pass for their value
std::size_t i = 0;
std::size_t dot_index = std::string::npos;
token_type number_type = token_type::value_unsigned;
std::uint64_t w = 0;
if (data[0] == '-')
{
number_type = token_type::value_integer;
@@ -13399,21 +13222,22 @@ scan_number_done:
return token_type::uninitialized;
}
}
const std::size_t integer_begin = i;
if (data[i] == '0')
{
++i;
}
else if (data[i] >= '1' && data[i] <= '9')
{
i = accumulate_digits(data, i, avail, w);
++i;
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
++i;
}
}
else
{
return token_type::uninitialized;
}
std::size_t digit_count = i - integer_begin;
std::size_t fraction_digits = 0;
if (i < avail && data[i] == '.')
{
number_type = token_type::value_float;
@@ -13423,39 +13247,29 @@ scan_number_done:
{
return token_type::uninitialized;
}
const std::size_t fraction_begin = i;
i = accumulate_digits(data, i, avail, w);
fraction_digits = i - fraction_begin;
digit_count += fraction_digits;
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
++i;
}
}
// the mantissa ends here, whether or not an exponent part follows
const std::size_t mantissa_end = i;
std::int64_t exponent = 0;
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
{
number_type = token_type::value_float;
++i;
bool negative_exponent = false;
if (i < avail && (data[i] == '+' || data[i] == '-'))
{
negative_exponent = data[i] == '-';
++i;
}
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
{
return token_type::uninitialized;
}
// saturated far beyond every range, see parse_float_exponent()
constexpr std::int64_t saturation = 100000000000000000; // 10^17
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
if (exponent < saturation)
{
exponent = (exponent * 10) + (data[i] - '0');
}
++i;
}
exponent = negative_exponent ? -exponent : exponent;
}
const std::size_t len = i;
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
@@ -13476,10 +13290,7 @@ scan_number_done:
#if !JSON_DIAGNOSTIC_POSITIONS
if (number_type != token_type::value_float)
{
// up to 18 digits always fit into std::uint64_t and std::int64_t
const token_type integer_result = (digit_count <= 18)
? store_integer(number_type, w)
: convert_integer(number_type, data, data + len);
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);
@@ -13493,42 +13304,15 @@ scan_number_done:
}
#endif
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
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> {});
// a float whose digits all fit into w converts from the accumulated
// value; longer ones (and other float formats) take the general path
if (number_type == token_type::value_float && digit_count <= 19)
{
float_significand s;
s.w = w;
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
s.negative = data[0] == '-';
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
{
// the token text is only copied into token_buffer when
// get_string() asks for it; the library's own SAX handlers
// ignore it. A window into a stream may be refilled by the
// next read, so there the text is copied right away.
if (bulk_window)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
}
else
{
pending_number_text = data;
pending_number_length = len;
}
return token_type::value_float;
}
}
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
return convert_number(number_type, mantissa_end);
}
@@ -13557,35 +13341,6 @@ scan_number_done:
/// the input is not handed back, so the terminator can stay unread
void read_number_terminator(std::false_type /*window*/) const noexcept {}
/// binary32/binary64: the value of a float token from its accumulated
/// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{
value_float = decimal_to_float<number_float_t>(s);
return true;
}
/// other formats (e.g. x87 long double): not instantiated for them, as
/// decimal_to_float() only supports binary32/binary64
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
{
return false;
}
/// the value of an integer token with at most 18 digits, accumulated in @a w
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
{
if (number_type == token_type::value_unsigned)
{
value_unsigned = static_cast<number_unsigned_t>(w);
// reject values that do not round-trip into a narrower type
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
}
const std::int64_t x = -static_cast<std::int64_t>(w);
value_integer = static_cast<number_integer_t>(x);
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
}
/// contiguous input: try the number fast path, else the byte-path scanner
token_type scan_number_dispatch(std::true_type /*bulk*/)
{
@@ -13628,7 +13383,6 @@ scan_number_done:
void reset() noexcept
{
token_buffer.clear();
pending_number_text = nullptr;
decimal_point_position = std::string::npos;
#if JSON_DIAGNOSTIC_POSITIONS
@@ -13888,30 +13642,10 @@ scan_number_done:
/// return current string value
string_t& get_string()
{
if (pending_number_text != nullptr)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
pending_number_text = nullptr;
}
// a number token holds '.' regardless of the locale (#4084)
return token_buffer;
}
/// the value of a string token (key or string), which never has pending
/// number text, so get_string()'s check is not needed
string_t& get_string_token() noexcept
{
JSON_ASSERT(pending_number_text == nullptr);
return token_buffer;
}
/// the text of a float token for a SAX handler that ignores it (the
/// library's own handlers): empty, without copying the token
const string_t& get_ignored_number_text() const noexcept
{
return token_buffer;
}
/////////////////////
// diagnostics
/////////////////////
@@ -14307,11 +14041,6 @@ scan_number_done:
/// whether the next get() call should just return current
bool next_unget = false;
/// the text of the current float token in the input, not yet copied into
/// token_buffer (see get_string()), or nullptr
const char* pending_number_text = nullptr;
std::size_t pending_number_length = 0;
/// the start position of the current token
position_t position {};
@@ -20180,20 +19909,6 @@ template<typename BasicJsonType>
using parser_callback_t =
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
/// whether a SAX handler ignores the text of float tokens (the library's own
/// handlers do), so that the parser need not materialize it
template<typename SAX>
struct sax_ignores_number_text : std::false_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType>
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
/*!
@brief syntax analysis
@@ -20314,23 +20029,6 @@ class parser
}
private:
/// the text of the current float token for @a sax (see sax_ignores_number_text)
template<typename SAX>
const string_t& number_float_text(SAX* /*sax*/)
{
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
}
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
{
return m_lexer.get_ignored_number_text();
}
string_t& number_float_text(std::false_type /*ignored*/)
{
return m_lexer.get_string();
}
/*!
@brief run a DOM SAX parser to completion and position the lexer
@@ -20407,7 +20105,7 @@ class parser
{
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
}
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{
return false;
}
@@ -20461,7 +20159,7 @@ class parser
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
}
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
{
return false;
}
@@ -20507,7 +20205,7 @@ class parser
case token_type::value_string:
{
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
{
return false;
}
@@ -20616,7 +20314,7 @@ class parser
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
}
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{
return false;
}
@@ -23530,6 +23228,10 @@ NLOHMANN_JSON_NAMESPACE_END
#include <utility> // move
#include <vector> // vector
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif
// #include <nlohmann/detail/bit_ops.hpp>
// #include <nlohmann/detail/input/binary_reader.hpp>
@@ -26016,7 +25718,7 @@ class binary_writer
{
out[0] = to_char_type(first_byte);
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
const std::uint64_t tail = is_little_endian ? byteswap(value) >> unused_bits : value << unused_bits;
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;
}
@@ -27460,14 +27162,53 @@ class binary_writer
// Utility functions //
///////////////////////
// single-instruction byte swaps (compilers lower these to bswap/rev/movbe);
// used to emit big-endian numbers without a per-byte std::reverse loop
static std::uint16_t byte_swap(std::uint16_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap16(x);
#elif defined(_MSC_VER)
return _byteswap_ushort(x);
#else
return static_cast<std::uint16_t>((x >> 8) | (x << 8));
#endif
}
static std::uint32_t byte_swap(std::uint32_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#elif defined(_MSC_VER)
return _byteswap_ulong(x);
#else
return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8)
| ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24);
#endif
}
static std::uint64_t byte_swap(std::uint64_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#elif defined(_MSC_VER)
return _byteswap_uint64(x);
#else
x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32);
x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16);
x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8);
return x;
#endif
}
/*!
@brief reverse the bytes of a buffer by byte-swapping it as UIntType
Loading the buffer into an unsigned integer of the same width and swapping
that is what lets the compiler emit a single bswap/rev/movbe; reversing the
buffer element by element does not reliably get there (clang keeps a scalar
shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from
inlining differently in C++20, which made writing slower.)
shuffle). The two memcpy calls are the only portable way to reinterpret the
bytes and are folded away by every optimizer.
*/
template<typename UIntType, std::size_t N>
static void byte_swap_buffer(std::array<CharType, N>& a) noexcept
@@ -27475,11 +27216,11 @@ class binary_writer
static_assert(sizeof(UIntType) == N, "swap width must match the buffer size");
UIntType v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byteswap(v);
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
// reverse the bytes of a fixed-size buffer; a single byteswap() for the
// reverse the bytes of a fixed-size buffer; a single byte_swap() for the
// common 2/4/8-byte number payloads, std::reverse for any other size
static void reverse_bytes(std::array<CharType, 2>& a) noexcept
{
@@ -27604,7 +27345,9 @@ class binary_writer
#endif
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
return bit_cast<CharType>(x);
CharType result;
std::memcpy(&result, &x, sizeof(x));
return result;
}
template<typename C = CharType,
@@ -27688,6 +27431,9 @@ NLOHMANN_JSON_NAMESPACE_END
#include <limits> // numeric_limits
#include <type_traits> // conditional
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_uint64
#endif
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
// digits of a double at once
@@ -27704,8 +27450,6 @@ NLOHMANN_JSON_NAMESPACE_END
#define JSON_DTOA_NEON 0
#endif
// #include <nlohmann/detail/bit_ops.hpp>
// #include <nlohmann/detail/conversions/zmij.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
@@ -27963,6 +27707,16 @@ For a detailed description of the algorithm see:
namespace dtoa_impl
{
template<typename Target, typename Source>
Target reinterpret_bits(const Source source)
{
static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
Target target;
std::memcpy(&target, &source, sizeof(Source));
return target;
}
struct diyfp // f * 2^e
{
static constexpr int kPrecision = 64; // = q
@@ -28117,7 +27871,7 @@ boundaries compute_boundaries(FloatType value)
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));
const std::uint64_t E = bits >> (kPrecision - 1);
const std::uint64_t F = bits & (kHiddenBit - 1);
@@ -28971,11 +28725,14 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
/// the byte stores once this is inlined)
inline void store_msb_first(char* p, std::uint64_t v) noexcept
{
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets)
v = byteswap(v);
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
v = __builtin_bswap64(v);
std::memcpy(p, &v, sizeof(v));
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
std::memcpy(p, &v, sizeof(v));
#elif defined(_MSC_VER) // (little-endian on all its targets)
v = _byteswap_uint64(v);
std::memcpy(p, &v, sizeof(v));
#else
for (unsigned i = 0; i < 8; ++i)
{
@@ -29346,7 +29103,8 @@ char* write_positive_zmij(char* first, const char* last, FloatType value)
{
static_assert(is_binary64<FloatType>::value,
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
const auto bits = bit_cast<std::uint64_t>(value);
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits);
if (JSON_HEDLEY_LIKELY(last - first >= 41))
{
@@ -39690,8 +39448,6 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
#undef JSON_INLINE_VARIABLE
#undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#undef JSON_UNLOCKED_FILE_IO
#undef JSON_DTOA_SSE2
#undef JSON_DTOA_NEON
@@ -39719,6 +39475,8 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#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
+10 -5
View File
@@ -6166,13 +6166,17 @@ class output_buffer
}
private:
/// the size of a growth step
static constexpr std::size_t step = 0x10000;
/// the size of a growth step (a function: std::min() takes a reference,
/// which a static constexpr member does not have before C++17)
static constexpr std::size_t step() noexcept
{
return 0x10000;
}
static StringType& sized(StringType& out, std::size_t estimate)
{
out.reserve(estimate);
out.resize(detail::clamp(estimate, std::size_t{64}, step));
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
return out;
}
@@ -6181,7 +6185,7 @@ class output_buffer
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation)
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
if (size > m_out.capacity())
{
m_out.reserve((std::max)(m_out.capacity() * 2, size));
@@ -6851,7 +6855,8 @@ class view_serializer
static char* write_double_value_at(char* w, double x)
{
// (from the bits: without the checks of to_chars())
auto bits = bit_cast<std::uint64_t>(x);
std::uint64_t bits = 0;
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{
return write_text_at(w, "null", 4);
+36 -3
View File
@@ -2,12 +2,14 @@ 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")
@@ -20,6 +22,11 @@ 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
#############################################################################
@@ -326,9 +333,28 @@ if(NOT "${JSON_TestShard}" STREQUAL "")
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
endif()
foreach(file ${files})
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
endforeach()
# 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()
# 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.
@@ -354,6 +380,13 @@ 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
+92
View File
@@ -0,0 +1,92 @@
# 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.
-9
View File
@@ -22,15 +22,6 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
| `SaxParse` | parse with a SAX handler that only counts events, so no values are built |
| `Copy`, `CopyAssign`, `CopyRing` | copy a parsed document and destroy a copy, both timed: a temporary copy, an assignment over an earlier copy, and replacing the oldest of eight live copies |
| `CopyOnly`, `DestroyOnly` | only the copy or only the destruction, the other half untimed; a fast destructor can leave work to the next allocations, so these tell the halves apart but do not compare versions |
| `Equal`, `Iterate` | compare a parsed document with a copy, or visit every value with range-based `for` loops |
| `Flatten`, `FlattenOrdered`, `Unflatten` | `flatten()` for `json` and `ordered_json`, and `unflatten()` |
| `Diff`, `Patch` | `diff()` against the document with every eighth scalar changed, and applying that patch |
| `JsonPointerParse`, `JsonPointerToString`, `JsonPointerAt`, `JsonPointerContains` | the JSON pointers of every scalar in a document: parse them from strings, turn them back into strings, look them up with `at()` and `contains()` |
| `GetVectorInt`, `GetVectorDouble`, `GetMap` | `get<std::vector<std::int64_t>>()`, `get<std::vector<double>>()` and `get<std::map<std::string, json>>()` on large arrays and a large flat object |
| `ToJsonStrings` | create a `json` array from a `std::vector` of 100,000 strings, copied or moved, and destroy it again |
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
-581
View File
@@ -9,15 +9,12 @@
#include <benchmark/benchmark.h>
#include <nlohmann/json.hpp>
#include <array>
#include <cstdint>
#include <cstdio>
#include <deque>
#include <fstream>
#include <map>
#include <numeric>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include <test_data.hpp>
@@ -713,584 +710,6 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
//////////////////////////////////////////////////////////////////////////////
// SAX-parse JSON from string
//
// sax_parse() with a handler that does next to nothing runs the lexer and
// parser without building values, so the gap to ParseString above is the cost
// of building the DOM.
//////////////////////////////////////////////////////////////////////////////
static std::string read_file(const char* filename)
{
std::ifstream f(filename);
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
}
static json load_json(const char* filename)
{
return json::parse(read_file(filename));
}
/// SAX handler that only counts the events, so that the handler costs next to nothing
class counting_sax : public nlohmann::json_sax<json>
{
public:
std::size_t events = 0;
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 json::exception& /*ex*/) override
{
return false;
}
private:
bool count()
{
++events;
return true;
}
};
static void SaxParse(benchmark::State& state, const char* filename)
{
const std::string str = read_file(filename);
for (auto _ : state)
{
counting_sax sax;
bool result = json::sax_parse(str, &sax);
benchmark::DoNotOptimize(result);
benchmark::DoNotOptimize(sax.events);
}
state.SetBytesProcessed(state.iterations() * str.size());
}
BENCHMARK_CAPTURE(SaxParse, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(SaxParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(SaxParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// work with values: copy, destroy, compare, iterate
//
// The parsed test files stand in for a document an application keeps around.
// Each benchmark times one whole-document operation; setup and cleanup run
// outside the timed section.
//
// A copy is always destroyed again, and how a document is freed shapes the
// heap the next copy allocates from: a destructor that frees quickly can leave
// work to the allocator's next calls. Copy, CopyAssign and CopyRing therefore
// time the copy together with the destruction of a copy; CopyOnly and
// DestroyOnly time one half each and are meant for telling the halves apart,
// not for comparing versions.
//////////////////////////////////////////////////////////////////////////////
/// copy a document and destroy the copy again, as a temporary copy is used
static void Copy(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
json copy = j;
benchmark::DoNotOptimize(copy);
}
}
BENCHMARK_CAPTURE(Copy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Copy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Copy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// copy-assign a document over an earlier copy of it, which is freed in the process
static void CopyAssign(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
json target = j;
for (auto _ : state)
{
target = j;
benchmark::DoNotOptimize(target);
}
}
BENCHMARK_CAPTURE(CopyAssign, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(CopyAssign, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(CopyAssign, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// keep eight copies alive and replace the oldest one, as a cache or an undo
/// history does, so that the copy is not made from memory just freed by an
/// identical document
static void CopyRing(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
std::vector<json> ring(8, j);
std::size_t oldest = 0;
for (auto _ : state)
{
ring[oldest] = j;
benchmark::DoNotOptimize(ring[oldest]);
oldest = (oldest + 1) % ring.size();
}
}
BENCHMARK_CAPTURE(CopyRing, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(CopyRing, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(CopyRing, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// only the copy; the copy is destroyed with the timer paused (diagnostic)
static void CopyOnly(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
auto* copy = new json(j);
state.PauseTiming();
delete copy;
state.ResumeTiming();
}
}
BENCHMARK_CAPTURE(CopyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(CopyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(CopyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// only the destruction; the copy is made with the timer paused (diagnostic)
static void DestroyOnly(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
state.PauseTiming();
auto* copy = new json(j);
state.ResumeTiming();
delete copy;
}
}
BENCHMARK_CAPTURE(DestroyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(DestroyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(DestroyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void Equal(benchmark::State& state, const char* filename)
{
// two separate values, so that the comparison cannot stop at identical addresses
const json j = load_json(filename);
const json copy = j;
for (auto _ : state)
{
bool result = (j == copy);
benchmark::DoNotOptimize(result);
}
}
BENCHMARK_CAPTURE(Equal, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Equal, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Equal, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// visit every value with range-based for loops, as client code walks a document
static std::size_t count_values(const json& j)
{
std::size_t result = 1;
if (j.is_structured())
{
for (const auto& element : j)
{
result += count_values(element);
}
}
return result;
}
static void Iterate(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
std::size_t result = count_values(j);
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(count_values(j)));
}
BENCHMARK_CAPTURE(Iterate, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Iterate, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Iterate, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// flatten and unflatten
//////////////////////////////////////////////////////////////////////////////
static void Flatten(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
json flat = j.flatten();
benchmark::DoNotOptimize(flat);
}
}
BENCHMARK_CAPTURE(Flatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Flatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Flatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
// ordered_json objects look keys up linearly, which flatten() used to do for
// every value; canada (330,000 values in one flat object) is left out, as it
// would take minutes with such versions
static void FlattenOrdered(benchmark::State& state, const char* filename)
{
const nlohmann::ordered_json j = nlohmann::ordered_json::parse(read_file(filename));
for (auto _ : state)
{
nlohmann::ordered_json flat = j.flatten();
benchmark::DoNotOptimize(flat);
}
}
BENCHMARK_CAPTURE(FlattenOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(FlattenOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void Unflatten(benchmark::State& state, const char* filename)
{
const json flat = load_json(filename).flatten();
for (auto _ : state)
{
json j = flat.unflatten();
benchmark::DoNotOptimize(j);
}
}
BENCHMARK_CAPTURE(Unflatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Unflatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Unflatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// diff and patch
//
// The target is the document with every eighth scalar changed, so the patch
// has one "replace" operation per changed value and the structure stays the
// same.
//////////////////////////////////////////////////////////////////////////////
static void change_every_eighth_scalar(json& j, std::size_t& counter)
{
if (j.is_structured())
{
for (auto& element : j)
{
change_every_eighth_scalar(element, counter);
}
return;
}
if (++counter % 8 != 0)
{
return;
}
switch (j.type())
{
case json::value_t::number_integer:
j = j.get<json::number_integer_t>() + 1;
break;
case json::value_t::number_unsigned:
j = j.get<json::number_unsigned_t>() + 1;
break;
case json::value_t::number_float:
j = j.get<json::number_float_t>() + 0.5;
break;
case json::value_t::string:
j = j.get<std::string>() + "!";
break;
case json::value_t::boolean:
j = !j.get<bool>();
break;
case json::value_t::null:
case json::value_t::object:
case json::value_t::array:
case json::value_t::binary:
case json::value_t::discarded:
default:
j = 0;
break;
}
}
static json changed_copy(const json& j)
{
json result = j;
std::size_t counter = 0;
change_every_eighth_scalar(result, counter);
return result;
}
static void Diff(benchmark::State& state, const char* filename)
{
const json source = load_json(filename);
const json target = changed_copy(source);
for (auto _ : state)
{
json patch = json::diff(source, target);
benchmark::DoNotOptimize(patch);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(json::diff(source, target).size()));
}
BENCHMARK_CAPTURE(Diff, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Diff, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Diff, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void Patch(benchmark::State& state, const char* filename)
{
const json source = load_json(filename);
const json patch = json::diff(source, changed_copy(source));
for (auto _ : state)
{
json result = source.patch(patch);
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(patch.size()));
}
BENCHMARK_CAPTURE(Patch, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Patch, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Patch, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// JSON Pointer
//
// The pointers are those of every scalar in the document, i.e., the keys of
// its flattened form.
//////////////////////////////////////////////////////////////////////////////
static std::vector<std::string> pointer_strings(const json& j)
{
std::vector<std::string> result;
const json flat = j.flatten();
result.reserve(flat.size());
for (auto it = flat.begin(); it != flat.end(); ++it)
{
result.push_back(it.key());
}
return result;
}
static std::vector<json::json_pointer> pointers(const json& j)
{
const std::vector<std::string> strings = pointer_strings(j);
std::vector<json::json_pointer> result;
result.reserve(strings.size());
for (const auto& s : strings)
{
result.emplace_back(s);
}
return result;
}
static void JsonPointerParse(benchmark::State& state, const char* filename)
{
const std::vector<std::string> strings = pointer_strings(load_json(filename));
for (auto _ : state)
{
for (const auto& s : strings)
{
json::json_pointer ptr(s);
benchmark::DoNotOptimize(ptr);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
}
BENCHMARK_CAPTURE(JsonPointerParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void JsonPointerToString(benchmark::State& state, const char* filename)
{
const std::vector<json::json_pointer> ptrs = pointers(load_json(filename));
for (auto _ : state)
{
for (const auto& ptr : ptrs)
{
std::string s = ptr.to_string();
benchmark::DoNotOptimize(s);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
}
BENCHMARK_CAPTURE(JsonPointerToString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerToString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void JsonPointerAt(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
const std::vector<json::json_pointer> ptrs = pointers(j);
for (auto _ : state)
{
for (const auto& ptr : ptrs)
{
const json& value = j.at(ptr);
benchmark::DoNotOptimize(&value);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
}
BENCHMARK_CAPTURE(JsonPointerAt, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerAt, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void JsonPointerContains(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
const std::vector<json::json_pointer> ptrs = pointers(j);
for (auto _ : state)
{
for (const auto& ptr : ptrs)
{
bool result = j.contains(ptr);
benchmark::DoNotOptimize(result);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
}
BENCHMARK_CAPTURE(JsonPointerContains, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerContains, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// conversions from and to C++ containers
//////////////////////////////////////////////////////////////////////////////
static void GetVectorInt(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
auto v = j.get<std::vector<std::int64_t>>();
benchmark::DoNotOptimize(v);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
}
BENCHMARK_CAPTURE(GetVectorInt, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json");
static void GetVectorDouble(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
auto v = j.get<std::vector<double>>();
benchmark::DoNotOptimize(v);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
}
BENCHMARK_CAPTURE(GetVectorDouble, floats, TEST_DATA_DIRECTORY "/regression/floats.json");
// a large flat object: the flattened document, one member per scalar
static void GetMap(benchmark::State& state, const char* filename)
{
const json j = load_json(filename).flatten();
for (auto _ : state)
{
auto m = j.get<std::map<std::string, json>>();
benchmark::DoNotOptimize(m);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
}
BENCHMARK_CAPTURE(GetMap, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
/// 100,000 strings, each too long for the small string optimization
static std::vector<std::string> make_strings()
{
std::vector<std::string> result;
result.reserve(100000);
for (std::size_t i = 0; i < 100000; ++i)
{
result.push_back("a string that does not fit in place: " + std::to_string(i));
}
return result;
}
static void ToJsonStrings(benchmark::State& state, const bool move)
{
const std::vector<std::string> strings = make_strings();
for (auto _ : state)
{
state.PauseTiming();
std::vector<std::string> input = strings;
state.ResumeTiming();
// destroyed inside the timed section, like the copies above
auto* j = move ? new json(std::move(input)) : new json(input);
delete j;
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
}
BENCHMARK_CAPTURE(ToJsonStrings, copy, false);
BENCHMARK_CAPTURE(ToJsonStrings, move, true);
// conversion of a JSON array into standard containers
template<typename Container>
static void GetArray(benchmark::State& state)
+98
View File
@@ -0,0 +1,98 @@
// __ _____ _____ _____
// __| | __| | | | 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
+115
View File
@@ -0,0 +1,115 @@
// __ _____ _____ _____
// __| | __| | | | 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
-19
View File
@@ -13,9 +13,6 @@
using nlohmann::json;
#include <valarray>
#if JSON_HAS_RANGES
#include <ranges>
#endif
namespace
{
@@ -684,12 +681,6 @@ 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());
@@ -787,16 +778,6 @@ 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}));
+60
View File
@@ -0,0 +1,60 @@
// __ _____ _____ _____
// __| | __| | | | 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
-38
View File
@@ -1099,41 +1099,3 @@ 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
-109
View File
@@ -15,12 +15,9 @@ using nlohmann::json;
#include <array> // array
#include <cstdint> // uint32_t, uint64_t
#include <cstdio> // snprintf
#include <cmath> // isfinite
#include <cstdlib> // strtod
#include <cstring> // memcpy
#include <limits> // numeric_limits
#include <initializer_list> // initializer_list
#include <list> // list
#include <map> // map
#include <random> // mt19937
#include <sstream> // stringstream
@@ -335,112 +332,6 @@ TEST_CASE("lexer number fast path")
}
}
#if !defined(JSON_NOEXCEPTION)
// this section reports parse errors, which abort when exceptions are off
SECTION("digits accumulated while scanning")
{
// The fast path accumulates the digits while it validates them, eight
// at a time where possible, and converts tokens with at most 19
// digits (floats) or 18 digits (integers) from that value. Every digit
// count from 1 to 25, in the integer and in the fraction part, with
// and without sign and exponent, must give the byte path's result. A
// std::list has no contiguous storage, so parsing it takes the byte
// path.
// type and value, or the error (values beyond the range of double
// are rejected with out_of_range.406 on both paths)
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
{
JSON_TRY
{
const std::list<char> chars(doc.begin(), doc.end());
const json j = byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc);
return std::string(j[0].type_name()) + '|' + j.dump();
}
JSON_CATCH (const json::exception & e)
{
return e.what();
}
};
// the concatenation of @a parts
const auto join = [](std::initializer_list<std::string> parts)
{
std::string result;
for (const auto& part : parts)
{
result += part;
}
return result;
};
std::vector<std::string> numbers;
for (std::size_t digits = 1; digits <= 25; ++digits)
{
std::string run;
for (std::size_t i = 0; i < digits; ++i)
{
run += static_cast<char>('1' + static_cast<char>((i * 7) % 9));
}
const std::string nines(digits, '9');
for (const char* sign_text :
{"", "-"
})
{
const std::string sign = sign_text;
numbers.push_back(join({sign, run}));
numbers.push_back(join({sign, nines}));
numbers.push_back(join({sign, "1", std::string(digits - 1, '0')}));
numbers.push_back(join({sign, "0.", run}));
numbers.push_back(join({sign, "0.", std::string(digits, '0'), "1"}));
numbers.push_back(join({sign, run, ".", run}));
numbers.push_back(join({sign, run, "e-", std::to_string(digits)}));
numbers.push_back(join({sign, "1.", run, "E+", std::to_string(digits * 13)}));
}
}
// exponents beyond every range, and zeros
numbers.insert(numbers.end(), {"1e400", "1e-400", "-1e99999999999999999999", "1e-99999999999999999999",
"0e99999999999999999999", "0.0e-5", "-0.000", "123456789e-0000000000000000000000330"
});
for (const auto& n : numbers)
{
CAPTURE(n)
const std::string doc = "[" + n + "]";
const std::string a = outcome(doc, false);
CHECK(a == outcome(doc, true));
const double expected = std::strtod(n.c_str(), nullptr);
if (a.compare(0, 6, "number") == 0 && std::isfinite(expected) && n.find_first_of(".eE") != std::string::npos)
{
CHECK(json::parse(doc)[0].get<double>() == expected);
}
}
}
#endif
SECTION("integers that do not fit a narrower type become floats")
{
// up to 18 digits, the fast path stores the accumulated value without
// an overflow check; a narrower number_integer_t/number_unsigned_t
// must still see the value rejected and converted as a float
using json32 = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
for (const char* n :
{"4294967295", "4294967296", "2147483647", "-2147483648", "-2147483649", "99999999999", "-99999999999"
})
{
CAPTURE(n)
const std::string doc = std::string("[") + n + "]";
const std::list<char> chars(doc.begin(), doc.end());
const json32 a = json32::parse(doc);
const json32 b = json32::parse(chars.begin(), chars.end());
CHECK(a[0].type() == b[0].type());
CHECK(a == b);
}
CHECK(json32::parse("[4294967295]")[0].is_number_unsigned());
CHECK(json32::parse("[4294967296]")[0].is_number_float());
CHECK(json32::parse("[-2147483648]")[0].is_number_integer());
CHECK(json32::parse("[-2147483649]")[0].is_number_float());
}
SECTION("token type classification")
{
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
-75
View File
@@ -30,9 +30,7 @@ using nlohmann::json;
#include <valarray>
#include <algorithm>
#include <cstdio>
#include <cstdint>
#include <fstream>
#include <iterator>
#include <list>
#include <sstream>
#include <string>
@@ -2452,79 +2450,6 @@ std::string parse_error_message_range(IteratorType first, IteratorType last)
}
} // namespace
TEST_CASE("text of float tokens")
{
// For contiguous input, the lexer copies the text of a float token into
// its buffer only when a SAX handler asks for it (the library's own
// handlers do not). A user handler must still see the exact text, for
// every input adapter and in every position of a document.
const std::string doc = R"([1.5, -0.0, 0.1, 1e5, -2.5E-4, 12345678901234567890.5, 0.12345678901234567890123,
{"a": 3.25, "b": [6.02e23, 1]}, "x", true, 7e-1])";
const std::vector<std::string> expected_floats =
{
"number_float(1.5)", "number_float(-0.0)", "number_float(0.1)", "number_float(1e5)", "number_float(-2.5E-4)",
"number_float(12345678901234567890.5)", "number_float(0.12345678901234567890123)", "number_float(3.25)",
"number_float(6.02e23)", "number_float(7e-1)"
};
const auto floats = [](const std::vector<std::string>& events)
{
std::vector<std::string> result;
std::copy_if(events.begin(), events.end(), std::back_inserter(result), [](const std::string & e)
{
return e.compare(0, 13, "number_float(") == 0;
});
return result;
};
SECTION("contiguous input")
{
SaxEventLogger l;
CHECK(json::sax_parse(doc, &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("byte input")
{
const std::vector<std::uint8_t> bytes(doc.begin(), doc.end());
SaxEventLogger l;
CHECK(json::sax_parse(bytes, &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("stream input")
{
std::istringstream is(doc);
SaxEventLogger l;
CHECK(json::sax_parse(is, &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("non-contiguous iterators")
{
const std::list<char> chars(doc.begin(), doc.end());
SaxEventLogger l;
CHECK(json::sax_parse(chars.begin(), chars.end(), &l));
CHECK(floats(l.events) == expected_floats);
}
SECTION("the library's own handlers do not need the text")
{
using lexer_adapter = nlohmann::detail::iterator_input_adapter<const char*>;
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_parser<json, lexer_adapter>>::value);
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_callback_parser<json, lexer_adapter>>::value);
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_acceptor<json>>::value);
CHECK_FALSE(nlohmann::detail::sax_ignores_number_text<SaxEventLogger>::value);
// and the values are the same as when the text is read
const json j = json::parse(doc);
CHECK(j[0] == 1.5);
CHECK(j[5].get<double>() == 12345678901234567890.5);
CHECK(j[7]["b"][0] == 6.02e23);
CHECK(json::accept(doc));
}
}
TEST_CASE("last-read diagnostics are identical across input adapters")
{
// The lexer reconstructs the "last read" token lazily for seekable adapters
+526
View File
@@ -0,0 +1,526 @@
// __ _____ _____ _____
// __| | __| | | | 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
+59 -356
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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.
#include "doctest_compatibility.h"
@@ -29,37 +28,6 @@
#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>
@@ -76,16 +44,6 @@ 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");
@@ -136,62 +94,13 @@ TEST_CASE("lexicographical comparison operators")
CAPTURE(i)
CAPTURE(j)
// check precomputed values
#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
#if !JSON_HAS_THREE_WAY_COMPARISON
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")
@@ -320,18 +229,6 @@ 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")
@@ -382,16 +279,6 @@ 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")
@@ -618,72 +505,6 @@ 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
@@ -722,60 +543,7 @@ TEST_CASE("lexicographical comparison operators")
#endif
}
#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
namespace unit_comparison_detail
{
// orders keys ascending or descending, as chosen when a map is created
template<class Key>
@@ -827,29 +595,6 @@ 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).
@@ -878,18 +623,6 @@ 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
@@ -907,7 +640,58 @@ 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")
{
@@ -1050,12 +834,6 @@ 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
@@ -1073,11 +851,6 @@ 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
@@ -1088,13 +861,7 @@ TEST_CASE("containers are compared element by element")
CHECK_FALSE(lhs == lhs);
CHECK_FALSE(rhs < lhs);
#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
#if !JSON_HAS_THREE_WAY_COMPARISON
// operator< skips a pair of elements that cannot be ordered, as
// std::lexicographical_compare does, and the next pair decides
CHECK(lhs < rhs);
@@ -1103,54 +870,6 @@ 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;
@@ -1249,17 +968,6 @@ 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")
@@ -1275,11 +983,6 @@ 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")
+95
View File
@@ -0,0 +1,95 @@
// __ _____ _____ _____
// __| | __| | | | 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
+5 -76
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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.
#include "doctest_compatibility.h"
@@ -31,25 +30,6 @@ 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)")
@@ -519,13 +499,6 @@ 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")
{
@@ -551,26 +524,6 @@ 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)")
@@ -591,15 +544,6 @@ 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)")
@@ -638,14 +582,6 @@ 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;
@@ -1256,11 +1192,4 @@ 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
+225
View File
@@ -0,0 +1,225 @@
// __ _____ _____ _____
// __| | __| | | | 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
+5 -215
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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.
#include "doctest_compatibility.h"
@@ -36,25 +35,6 @@ 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)")
@@ -680,194 +660,4 @@ 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
+62 -62
View File
@@ -44,10 +44,10 @@ TEST_CASE("JSON Node Metadata")
{
SECTION("type int")
{
using json = json_with_metadata<int>;
json null;
auto obj = json::object();
auto array = json::array();
using json_t = json_with_metadata<int>;
json_t null;
auto obj = json_t::object();
auto array = json_t::array();
null.metadata() = 1;
obj.metadata() = 2;
@@ -61,8 +61,8 @@ TEST_CASE("JSON Node Metadata")
}
SECTION("type vector<int>")
{
using json = json_with_metadata<std::vector<int>>;
json value;
using json_t = json_with_metadata<std::vector<int>>;
json_t 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 = json_with_metadata<std::vector<int>>;
json value;
using json_t = json_with_metadata<std::vector<int>>;
json_t value;
value.metadata().emplace_back(1);
value.metadata().emplace_back(2);
json copy = value;
json_t 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 = json_with_metadata<std::vector<int>>;
json value;
using json_t = json_with_metadata<std::vector<int>>;
json_t value;
value.metadata().emplace_back(1);
value.metadata().emplace_back(2);
const json moved = std::move(value);
const json_t 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 = json_with_metadata<std::vector<int>>;
json value;
using json_t = json_with_metadata<std::vector<int>>;
json_t value;
value.metadata().emplace_back(1);
value.metadata().emplace_back(2);
json moved;
json_t moved;
moved = std::move(value);
CHECK(moved.metadata().size() == 2);
@@ -122,12 +122,12 @@ TEST_CASE("JSON Node Metadata")
}
SECTION("copy assign")
{
using json = json_with_metadata<std::vector<int>>;
json value;
using json_t = json_with_metadata<std::vector<int>>;
json_t value;
value.metadata().emplace_back(1);
value.metadata().emplace_back(2);
json copy;
json_t copy;
copy = value;
CHECK(copy.metadata().size() == 2);
@@ -143,8 +143,8 @@ TEST_CASE("JSON Node Metadata")
}
SECTION("type unique_ptr<int>")
{
using json = json_with_metadata<std::unique_ptr<int>>;
json value;
using json_t = json_with_metadata<std::unique_ptr<int>>;
json_t 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 = json_with_metadata<std::vector<int>>;
json value;
using json_t = json_with_metadata<std::vector<int>>;
json_t value;
value.metadata().emplace_back(1);
value.metadata().emplace_back(2);
json const array(10, value);
json_t 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 = json_with_metadata<int>;
json a = 1;
using json_t = json_with_metadata<int>;
json_t a = 1;
a.metadata() = 100;
json b = 2;
json_t b = 2;
b.metadata() = 200;
a.swap(b);
@@ -188,10 +188,10 @@ TEST_CASE("JSON Node Metadata")
}
SECTION("nonmember swap")
{
using json = json_with_metadata<int>;
json a = 1;
using json_t = json_with_metadata<int>;
json_t a = 1;
a.metadata() = 100;
json b = 2;
json_t b = 2;
b.metadata() = 200;
using std::swap;
@@ -204,10 +204,10 @@ TEST_CASE("JSON Node Metadata")
}
SECTION("std::swap")
{
using json = json_with_metadata<int>;
json a = 1;
using json_t = json_with_metadata<int>;
json_t a = 1;
a.metadata() = 100;
json b = 2;
json_t 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 = json_with_metadata<int>;
std::vector<json> values;
using json_t = json_with_metadata<int>;
std::vector<json_t> 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 value = v;
json_t 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& json = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
switch (json.type())
const json_with_visitor_t& self = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
switch (self.type())
{
case value_t::object:
for (const auto& entry : json.items())
for (const auto& entry : self.items())
{
entry.value().do_visit(ptr / entry.key(), fnc);
}
break;
case value_t::array:
for (std::size_t i = 0; i < json.size(); ++i)
for (std::size_t i = 0; i < self.size(); ++i)
{
json.at(i).do_visit(ptr / std::to_string(i), fnc);
self.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, json);
fnc(ptr, self);
}
}
TEST_CASE("JSON Visit Node")
{
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);
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);
std::set<std::string> expected
{
@@ -330,7 +330,7 @@ TEST_CASE("JSON Visit Node")
"/array/2/array/1 - number_integer - 1"
};
json.visit(
root.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(json.at(p) == j);
CHECK(root.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 = json_with_hidden_base_members;
using json_t = json_with_hidden_base_members;
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()), "");
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()), "");
SECTION("non-const")
{
json j = {1, 2, 3};
json_t 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::json_base_class_t&>(j));
CHECK(&j.as_base_class() == &static_cast<json_t::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 j = {1, 2, 3};
const json_t 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::json_base_class_t&>(j));
CHECK(&j.as_base_class() == &static_cast<const json_t::json_base_class_t&>(j));
}
}
@@ -0,0 +1,56 @@
// __ _____ _____ _____
// __| | __| | | | 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"
#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}));
}
}
#endif
-34
View File
@@ -24,10 +24,6 @@
#include <string>
#include <vector>
#ifdef JSON_HAS_CPP_17
#include <cstddef>
#endif
namespace
{
@@ -35,11 +31,6 @@ 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")
@@ -66,31 +57,6 @@ 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
+177
View File
@@ -0,0 +1,177 @@
// __ _____ _____ _____
// __| | __| | | | 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
+1 -36
View File
@@ -1167,44 +1167,9 @@ 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
}
// 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)
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)
{
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,6 +27,9 @@ 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
@@ -65,6 +68,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
{strict_cards::herz, "herz"},
{strict_cards::karo, "karo"}
})
} // namespace enum_keyed_maps_default
namespace
{
@@ -82,6 +86,15 @@ 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>>;
+5 -5
View File
@@ -6,11 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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)
#include "doctest_compatibility.h"
+122
View File
@@ -0,0 +1,122 @@
// __ _____ _____ _____
// __| | __| | | | 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
+5 -101
View File
@@ -6,23 +6,17 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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.
#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")
@@ -872,94 +866,4 @@ 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
}
+147
View File
@@ -0,0 +1,147 @@
// __ _____ _____ _____
// __| | __| | | | 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
+4 -23
View File
@@ -779,14 +779,13 @@ 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);
@@ -829,6 +828,7 @@ TEST_CASE("JSON pointers")
}
#endif
}
#endif
SECTION("less-than comparison")
{
@@ -838,12 +838,6 @@ 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")
@@ -899,10 +893,9 @@ 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);
@@ -918,6 +911,7 @@ 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")
@@ -939,19 +933,6 @@ 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")
+2 -2
View File
@@ -173,7 +173,7 @@ void check_string(const std::string& content)
}
// a small deterministic generator of documents
struct generator
struct builder_generator
{
std::mt19937 rng{5295}; // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
@@ -407,7 +407,7 @@ TEST_CASE("json_view builder")
SECTION("generated documents and damaged copies")
{
generator g;
builder_generator g;
for (int i = 0; i < 3000; ++i)
{
std::string text;
+10 -10
View File
@@ -39,7 +39,7 @@ using ptr_t = ordered_json::json_pointer;
namespace
{
std::uint32_t rng()
std::uint32_t edit_rng()
{
static std::mt19937 generator(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
// result_type is std::uint_fast32_t, which may be wider than 32 bits
@@ -49,12 +49,12 @@ std::uint32_t rng()
int r(int n)
{
return static_cast<int>(rng() % static_cast<unsigned>(n));
return static_cast<int>(edit_rng() % static_cast<unsigned>(n));
}
int counter = 0;
int edit_counter = 0;
std::uint64_t bits(double x)
std::uint64_t edit_bits(double x)
{
std::uint64_t b = 0;
std::memcpy(&b, &x, sizeof(b));
@@ -81,11 +81,11 @@ ordered_json random_scalar()
case 1:
return r(2) == 0;
case 2:
return static_cast<std::int64_t>(rng()) - 2147483648LL;
return static_cast<std::int64_t>(edit_rng()) - 2147483648LL;
case 3:
return (static_cast<std::uint64_t>(rng()) * 4294967296ULL) + rng();
return (static_cast<std::uint64_t>(edit_rng()) * 4294967296ULL) + edit_rng();
case 4:
return static_cast<double>(static_cast<std::int32_t>(rng())) / (1 + r(1000));
return static_cast<double>(static_cast<std::int32_t>(edit_rng())) / (1 + r(1000));
case 5:
return r(2) == 0 ? 1e300 * (r(2) == 0 ? 1 : -1) : 5e-324;
case 6:
@@ -103,7 +103,7 @@ ordered_json random_value(int depth)
ordered_json o = ordered_json::object();
for (int i = r(5); i > 0; --i)
{
o[random_string() + "#" + std::to_string(counter++)] = random_value(depth + 1);
o[random_string() + "#" + std::to_string(edit_counter++)] = random_value(depth + 1);
}
return o;
}
@@ -178,7 +178,7 @@ void compare(const ordered_json_editable_view& v, const ordered_json& j)
}
else if (j.is_number_float())
{
CHECK(bits(v.get<double>()) == bits(j.get<double>()));
CHECK(edit_bits(v.get<double>()) == edit_bits(j.get<double>()));
}
else if (j.is_number_integer())
{
@@ -435,7 +435,7 @@ TEST_CASE("json_view edits: differential")
}
else if ((op == 4 || op == 5) && target.is_object()) // set a member (new or existing)
{
std::string key = random_string() + "#" + std::to_string(counter++);
std::string key = random_string() + "#" + std::to_string(edit_counter++);
if (op == 5 && !target.empty())
{
key = std::next(target.begin(), r(static_cast<int>(target.size()))).key();
+10 -10
View File
@@ -38,7 +38,7 @@ using nlohmann::detail::view::node;
namespace
{
#if !defined(JSON_NOEXCEPTION)
std::string exception_of(const std::function<void()>& f)
std::string image_exception_of(const std::function<void()>& f)
{
try
{
@@ -119,7 +119,7 @@ void set_node(std::vector<std::uint8_t>& image, std::size_t i, const node& n)
/// the result of loading an image with a check: "" or the exception message
std::string load_result(const std::vector<std::uint8_t>& image, image_check check)
{
return exception_of([&]
return image_exception_of([&]
{
const json_document d = json_document::load(image, check);
static_cast<void>(d);
@@ -160,7 +160,7 @@ void check_round_trip(const Document& d)
CHECK(e.root().dump() == d.root().dump());
}
std::uint32_t rng()
std::uint32_t image_rng()
{
static std::mt19937 generator(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
// result_type is std::uint_fast32_t, which may be wider than 32 bits
@@ -701,9 +701,9 @@ TEST_CASE("json_view images: errors")
SECTION("saving a discarded document")
{
const json_document empty{};
CHECK(exception_of([&] { static_cast<void>(empty.save()); }) == "[json.exception.type_error.320] cannot save a discarded json_document");
CHECK(image_exception_of([&] { static_cast<void>(empty.save()); }) == "[json.exception.type_error.320] cannot save a discarded json_document");
const json_document failed = json_document::parse("[1,", false);
CHECK(exception_of([&] { static_cast<void>(failed.save()); }) == "[json.exception.type_error.320] cannot save a discarded json_document");
CHECK(image_exception_of([&] { static_cast<void>(failed.save()); }) == "[json.exception.type_error.320] cannot save a discarded json_document");
}
const std::vector<std::uint8_t> image = json_document::parse(R"({"a": [1, "\u00e9"]})").save();
@@ -711,12 +711,12 @@ TEST_CASE("json_view images: errors")
SECTION("header and sizes")
{
CHECK(exception_of([]
CHECK(image_exception_of([]
{
const json_document d = json_document::load(nullptr, 0);
static_cast<void>(d);
}) == prefix + "too short");
CHECK(exception_of([&]
CHECK(image_exception_of([&]
{
const json_document d = json_document::load(image.data(), 63);
static_cast<void>(d);
@@ -1203,12 +1203,12 @@ TEST_CASE("json_view images: damaged images")
for (int round = 0; round < 3000; ++round)
{
std::vector<std::uint8_t> b = image;
const std::uint32_t flips = 1 + (rng() % 3);
const std::uint32_t flips = 1 + (image_rng() % 3);
for (std::uint32_t k = 0; k < flips; ++k)
{
// mostly the nodes, where the damage matters most
const std::size_t at = rng() % 4 != 0 ? header_size + (rng() % (b.size() - header_size)) : rng() % b.size();
b[at] = static_cast<std::uint8_t>(rng() % 3 == 0 ? rng() : b[at] ^ (1u << (rng() % 8)));
const std::size_t at = image_rng() % 4 != 0 ? header_size + (image_rng() % (b.size() - header_size)) : image_rng() % b.size();
b[at] = static_cast<std::uint8_t>(image_rng() % 3 == 0 ? image_rng() : b[at] ^ (1u << (image_rng() % 8)));
}
for (const image_check check :
{
+6 -6
View File
@@ -46,7 +46,7 @@ void reference_merge_patch(json& target, const json& patch)
// objects nested `depth` levels deep under the key "a", with members that
// differ by `variant` on the way down
std::string nested_objects(const std::size_t depth, const int variant)
std::string nested_objects_merge_patch(const std::size_t depth, const int variant)
{
std::string text;
for (std::size_t i = 0; i < depth; ++i)
@@ -309,9 +309,9 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
for (int variant = 0; variant < 3; ++variant)
{
CAPTURE(variant)
const json patch = json::parse(nested_objects(depth, variant));
const json patch = json::parse(nested_objects_merge_patch(depth, variant));
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
json result = json::parse(nested_objects_merge_patch(depth, (variant + 1) % 3));
json expected = result;
result.merge_patch(patch);
reference_merge_patch(expected, patch);
@@ -332,8 +332,8 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
// applying a patch used to recurse once per nesting level. The result
// is only walked, never copied or compared, since those recurse too.
const std::size_t depth = 100000;
json target = json::parse(nested_objects(depth, 0));
target.merge_patch(json::parse(nested_objects(depth, 1)));
json target = json::parse(nested_objects_merge_patch(depth, 0));
target.merge_patch(json::parse(nested_objects_merge_patch(depth, 1)));
const json* p = &target;
for (std::size_t i = 0; i < depth; ++i)
@@ -404,7 +404,7 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
})
{
CAPTURE(depth)
json j = json::parse(nested_objects(depth, 0));
json j = json::parse(nested_objects_merge_patch(depth, 0));
const json expected = j;
j.merge_patch(j);
CHECK(j == expected);
+6 -6
View File
@@ -35,7 +35,7 @@ void reference_update(json& target, const json& source)
// objects nested `depth` levels deep under the key "a", with members that
// differ by `variant` on the way down
std::string nested_objects(const std::size_t depth, const int variant)
std::string nested_objects_update(const std::size_t depth, const int variant)
{
std::string text;
for (std::size_t i = 0; i < depth; ++i)
@@ -1131,8 +1131,8 @@ TEST_CASE("update() on deeply nested values")
for (int variant = 0; variant < 3; ++variant)
{
CAPTURE(variant)
const json source = json::parse(nested_objects(depth, variant));
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
const json source = json::parse(nested_objects_update(depth, variant));
json result = json::parse(nested_objects_update(depth, (variant + 1) % 3));
json expected = result;
result.update(source, true);
reference_update(expected, source);
@@ -1146,8 +1146,8 @@ TEST_CASE("update() on deeply nested values")
// merging used to recurse once per nesting level. The result is only
// walked, never copied or compared, since those recurse too.
const std::size_t depth = 100000;
json target = json::parse(nested_objects(depth, 0));
target.update(json::parse(nested_objects(depth, 1)), true);
json target = json::parse(nested_objects_update(depth, 0));
target.update(json::parse(nested_objects_update(depth, 1)), true);
const json* p = &target;
for (std::size_t i = 0; i < depth; ++i)
@@ -1223,7 +1223,7 @@ TEST_CASE("update() with an argument that aliases *this (#5641)")
})
{
CAPTURE(depth)
json j = json::parse(nested_objects(depth, 0));
json j = json::parse(nested_objects_update(depth, 0));
const json expected = j;
j.update(j, true);
CHECK(j == expected);
+18 -18
View File
@@ -35,7 +35,7 @@ using nlohmann::json;
#if SIZE_MAX > UINT32_MAX
template<typename T, typename A = std::allocator<T>>
struct huge_array : std::vector<T, A>
struct msgpack_huge_array : std::vector<T, A>
{
using base = std::vector<T, A>;
using base::base;
@@ -53,24 +53,24 @@ struct huge_array : std::vector<T, A>
}
};
using huge_array_json = nlohmann::json::with_array_t<huge_array>;
using msgpack_huge_array_json = nlohmann::json::with_array_t<msgpack_huge_array>;
TEST_CASE("MessagePack Size above uint32 for array")
{
huge_array_json j = huge_array_json::array();
msgpack_huge_array_json j = msgpack_huge_array_json::array();
j.push_back(1);
j.push_back(2);
j.push_back(3);
auto& array = j.get_ref<huge_array_json::array_t&>();
auto& array = j.get_ref<msgpack_huge_array_json::array_t&>();
array.fake_size = true;
// write into a caller-owned vector: to_msgpack(j) reserves space based on
// the (faked) element count, which fails with bad_alloc on Windows
std::vector<std::uint8_t> result;
CHECK_THROWS_WITH_AS(
huge_array_json::to_msgpack(j, result),
msgpack_huge_array_json::to_msgpack(j, result),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
@@ -80,7 +80,7 @@ TEST_CASE("MessagePack Size above uint32 for array")
template<typename K, typename V,
typename C = std::less<K>,
typename A = std::allocator<std::pair<const K, V>>>
struct huge_map : std::map<K, V, C, A>
struct msgpack_huge_map : std::map<K, V, C, A>
{
using base = std::map<K, V, C, A>;
using base::base;
@@ -98,24 +98,24 @@ template<typename K, typename V,
}
};
using huge_object_json = nlohmann::json::with_object_t<huge_map>;
using msgpack_huge_object_json = nlohmann::json::with_object_t<msgpack_huge_map>;
TEST_CASE("MessagePack Size above uint32 for object")
{
huge_object_json j = huge_object_json::object();
msgpack_huge_object_json j = msgpack_huge_object_json::object();
j["one"] = 1;
j["two"] = 2;
auto& object = j.get_ref<huge_object_json::object_t&>();
auto& object = j.get_ref<msgpack_huge_object_json::object_t&>();
object.fake_size = true;
// write into a caller-owned vector: to_msgpack(j) reserves space based on
// the (faked) element count, which fails with bad_alloc on Windows
std::vector<std::uint8_t> result;
CHECK_THROWS_WITH_AS(
huge_object_json::to_msgpack(j, result),
msgpack_huge_object_json::to_msgpack(j, result),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
@@ -123,7 +123,7 @@ TEST_CASE("MessagePack Size above uint32 for object")
}
#ifdef JSON_TEST_BEYOND_UINT32_STRING
struct huge_string : std::string
struct msgpack_huge_string : std::string
{
using std::string::string;
@@ -133,20 +133,20 @@ struct huge_string : std::string
}
};
using huge_string_json = nlohmann::json::with_string_t<huge_string>;
using msgpack_huge_string_json = nlohmann::json::with_string_t<msgpack_huge_string>;
TEST_CASE("MessagePack Size above uint32 for string")
{
const huge_string_json j = "hello";
const msgpack_huge_string_json j = "hello";
CHECK_THROWS_WITH_AS(
huge_string_json::to_msgpack(j),
msgpack_huge_string_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
}
#endif
struct huge_binary : std::vector<std::uint8_t>
struct msgpack_huge_binary : std::vector<std::uint8_t>
{
using std::vector<std::uint8_t>::vector;
@@ -156,18 +156,18 @@ struct huge_binary : std::vector<std::uint8_t>
}
};
using huge_binary_json = nlohmann::json::with_binary_t<huge_binary>;
using msgpack_huge_binary_json = nlohmann::json::with_binary_t<msgpack_huge_binary>;
TEST_CASE("MessagePack Size above uint32 for binary")
{
huge_binary_json j = huge_binary_json::binary(huge_binary{});
msgpack_huge_binary_json j = msgpack_huge_binary_json::binary(msgpack_huge_binary{});
j.get_binary().push_back(0x01);
j.get_binary().push_back(0x02);
CHECK_THROWS_WITH_AS(
huge_binary_json::to_msgpack(j),
msgpack_huge_binary_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
}
+22 -17
View File
@@ -44,6 +44,8 @@ using nlohmann::ordered_json;
// this type, which is presumably why the gap was never noticed.
/////////////////////////////////////////////////////////////////////////////
namespace unit_ordered_json2_detail
{
class alt_string;
bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage)
void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-internal-linkage)
@@ -217,12 +219,15 @@ void int_to_string(alt_string& target, std::size_t value)
target = std::to_string(value).c_str();
}
using alt_json = nlohmann::json::with_string_t<alt_string>;
using ordered_json2_alt_json = nlohmann::json::with_string_t<alt_string>;
bool operator<(const char* op1, const alt_string& op2) noexcept
{
return op1 < op2.str_impl;
}
} // namespace unit_ordered_json2_detail
using unit_ordered_json2_detail::ordered_json2_alt_json;
namespace
{
@@ -253,11 +258,11 @@ ordered_json make_rich_ordered_json()
return j;
}
alt_json make_rich_alt_json()
ordered_json2_alt_json make_rich_alt_json()
{
alt_json j;
ordered_json2_alt_json j;
j["zebra"] = 1;
j["apple"] = alt_json::array({1, 2, 3});
j["apple"] = ordered_json2_alt_json::array({1, 2, 3});
j["mango"]["z_nested"] = true;
j["mango"]["a_nested"] = nullptr;
j["banana"] = "some text";
@@ -330,47 +335,47 @@ TEST_CASE("ordered_json across binary formats")
TEST_CASE("alt_json (custom string_t) across binary formats")
{
const alt_json original = make_rich_alt_json();
const ordered_json2_alt_json original = make_rich_alt_json();
SECTION("CBOR")
{
const auto bytes = alt_json::to_cbor(original);
const auto restored = alt_json::from_cbor(bytes);
const auto bytes = ordered_json2_alt_json::to_cbor(original);
const auto restored = ordered_json2_alt_json::from_cbor(bytes);
CHECK(restored == original);
}
SECTION("MessagePack")
{
const auto bytes = alt_json::to_msgpack(original);
const auto restored = alt_json::from_msgpack(bytes);
const auto bytes = ordered_json2_alt_json::to_msgpack(original);
const auto restored = ordered_json2_alt_json::from_msgpack(bytes);
CHECK(restored == original);
}
SECTION("UBJSON")
{
const auto bytes = alt_json::to_ubjson(original);
const auto restored = alt_json::from_ubjson(bytes);
const auto bytes = ordered_json2_alt_json::to_ubjson(original);
const auto restored = ordered_json2_alt_json::from_ubjson(bytes);
CHECK(restored == original);
}
SECTION("BON8")
{
const auto bytes = alt_json::to_bon8(original);
const auto restored = alt_json::from_bon8(bytes);
const auto bytes = ordered_json2_alt_json::to_bon8(original);
const auto restored = ordered_json2_alt_json::from_bon8(bytes);
CHECK(restored == original);
}
SECTION("BSON")
{
const auto bytes = alt_json::to_bson(original);
const auto restored = alt_json::from_bson(bytes);
const auto bytes = ordered_json2_alt_json::to_bson(original);
const auto restored = ordered_json2_alt_json::from_bson(bytes);
CHECK(restored == original);
}
SECTION("BJData")
{
const auto bytes = alt_json::to_bjdata(original);
const auto restored = alt_json::from_bjdata(bytes);
const auto bytes = ordered_json2_alt_json::to_bjdata(original);
const auto restored = ordered_json2_alt_json::from_bjdata(bytes);
CHECK(restored == original);
}
}
+40
View File
@@ -0,0 +1,40 @@
// __ _____ _____ _____
// __| | __| | | | 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-ordered_map.cpp (ordered_map::find with
// std::string_view keys). It is kept in a separate translation unit so the (much larger)
// unit-ordered_map.cpp is built for C++11 only and not rebuilt for every C++ standard.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::ordered_map;
#ifdef JSON_HAS_CPP_17
#include <string>
#include <string_view>
TEST_CASE("ordered_map (C++17)")
{
SECTION("find")
{
ordered_map<std::string, std::string> om;
om["eins"] = "one";
om["zwei"] = "two";
om["drei"] = "three";
const auto com = om;
const std::string eins("eins");
const std::string vier("vier");
CHECK(om.find(std::string_view("eins")) == om.begin());
CHECK(com.find(std::string_view("eins")) == com.begin());
}
}
#endif
-5
View File
@@ -360,11 +360,6 @@ TEST_CASE("ordered_map")
CHECK(com.find("vier") == com.end());
CHECK(com.find(std::string("vier")) == com.end());
CHECK(com.find(vier) == com.end());
#ifdef JSON_HAS_CPP_17
CHECK(om.find(std::string_view("eins")) == om.begin());
CHECK(com.find(std::string_view("eins")) == com.begin());
#endif
}
SECTION("insert")
+67
View File
@@ -0,0 +1,67 @@
// __ _____ _____ _____
// __| | __| | | | 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-regression2.cpp (std::variant, std::any
// and std::optional regression tests). It is kept in a separate translation unit so the
// (much larger) unit-regression2.cpp is built for C++11 only and not rebuilt for every C++
// standard.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#ifdef JSON_HAS_CPP_17
#include <string>
#include <type_traits>
#include <vector>
#include <any>
#include <variant>
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
/////////////////////////////////////////////////////////////////////
// for #4804
/////////////////////////////////////////////////////////////////////
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
TEST_CASE("regression tests 2 (C++17)")
{
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
{
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
}
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
{
// std::variant<json> must not be retrievable via get<>(), because otherwise the
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
// converting constructor, routing a number through the string from_json overload
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
"std::variant<json> must not be retrievable via get<>()");
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
#if !(defined(__clang__) && __clang_major__ < 7)
// push_back, not emplace_back: #5066 needs the implicit conversion
// from json to the vector's value type
std::vector<std::variant<json>> v;
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
CHECK(std::get<0>(v[0]) == 1);
#endif
}
}
#endif
+57
View File
@@ -0,0 +1,57 @@
// __ _____ _____ _____
// __| | __| | | | 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-regression2.cpp (std::span and ranges
// regression tests). It is kept in a separate translation unit so the (much larger) unit-
// regression2.cpp is built for C++11 only and not rebuilt for every C++ standard.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using json = 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>
#if __has_include(<span>)
#include <span>
#endif
/////////////////////////////////////////////////////////////////////
// for #4440
/////////////////////////////////////////////////////////////////////
#if JSON_HAS_RANGES == 1
#include <ranges>
#endif
TEST_CASE("regression tests 2 (C++20)")
{
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
// classic Intel ICC reports <span> as includable but cannot actually compile
// std::span/std::as_bytes usage below
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
SECTION("issue #2546 - parsing containers of std::byte")
{
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
// exclude the trailing '\0' that string-literal initialization adds to
// DATA: std::span(DATA) would span the full array extent (including
// that NUL), which is only silently accepted as end-of-input by default
// and would fail under JSON_STRICT_NUL_HANDLING
const auto s = std::as_bytes(std::span(DATA, sizeof(DATA) - 1));
const json j = json::parse(s);
CHECK(j.dump() == "\"Hello, world!\"");
}
#endif
#endif
}
#endif
+10 -89
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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-regression2-cpp<NN>.cpp. This file stays C++11-only.
#include "doctest_compatibility.h"
@@ -49,30 +48,6 @@ using ordered_json = nlohmann::ordered_json;
#include "test_utils.hpp"
#ifdef JSON_HAS_CPP_17
#include <any>
#include <variant>
#endif
#ifdef JSON_HAS_CPP_17
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
/////////////////////////////////////////////////////////////////////
// for #4804
/////////////////////////////////////////////////////////////////////
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
#endif
#ifdef JSON_HAS_CPP_20
#if __has_include(<span>)
#include <span>
#endif
#endif
/////////////////////////////////////////////////////////////////////
// for #4825 - explicitly instantiating basic_json must compile; this
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
@@ -81,13 +56,6 @@ using ordered_json = nlohmann::ordered_json;
/////////////////////////////////////////////////////////////////////
template class nlohmann::basic_json<>;
/////////////////////////////////////////////////////////////////////
// for #4440
/////////////////////////////////////////////////////////////////////
#if JSON_HAS_RANGES == 1
#include <ranges>
#endif
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
@@ -285,18 +253,18 @@ struct adl_serializer<NonDefaultConstructible>
/////////////////////////////////////////////////////////////////////
template<class T>
class my_allocator : public std::allocator<T>
class my_allocator_2982 : public std::allocator<T>
{
public:
using std::allocator<T>::allocator;
my_allocator() = default;
template<class U> my_allocator(const my_allocator<U>& /*unused*/) { }
my_allocator_2982() = default;
template<class U> my_allocator_2982(const my_allocator_2982<U>& /*unused*/) { }
template <class U>
struct rebind
{
using other = my_allocator<U>;
using other = my_allocator_2982<U>;
};
};
@@ -468,13 +436,6 @@ TEST_CASE("regression tests 2")
CHECK(diffs.size() == 1); // Note the change here, was 2
}
#ifdef JSON_HAS_CPP_17
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
{
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
}
#endif
SECTION("issue #1299 - compile error in from_json converting to container "
"with std::pair")
{
@@ -718,26 +679,6 @@ TEST_CASE("regression tests 2")
CHECK(j.dump() == "{}");
}
#ifdef JSON_HAS_CPP_20
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
// classic Intel ICC reports <span> as includable but cannot actually compile
// std::span/std::as_bytes usage below
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
SECTION("issue #2546 - parsing containers of std::byte")
{
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
// exclude the trailing '\0' that string-literal initialization adds to
// DATA: std::span(DATA) would span the full array extent (including
// that NUL), which is only silently accepted as end-of-input by default
// and would fail under JSON_STRICT_NUL_HANDLING
const auto s = std::as_bytes(std::span(DATA, sizeof(DATA) - 1));
const json j = json::parse(s);
CHECK(j.dump() == "\"Hello, world!\"");
}
#endif
#endif
#endif
SECTION("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
{
SECTION("std::array")
@@ -868,7 +809,7 @@ TEST_CASE("regression tests 2")
SECTION("issue #2982 - to_{binary format} does not provide a mechanism for specifying a custom allocator for the returned type")
{
std::vector<std::uint8_t, my_allocator<std::uint8_t>> my_vector;
std::vector<std::uint8_t, my_allocator_2982<std::uint8_t>> my_vector;
const json j = {1, 2, 3, 4};
json::to_cbor(j, my_vector);
json k = json::from_cbor(my_vector);
@@ -884,26 +825,6 @@ TEST_CASE("regression tests 2")
CHECK(node.dump(-1, ' ', true, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}");
}
#ifdef JSON_HAS_CPP_17
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
{
// std::variant<json> must not be retrievable via get<>(), because otherwise the
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
// converting constructor, routing a number through the string from_json overload
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
"std::variant<json> must not be retrievable via get<>()");
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
#if !(defined(__clang__) && __clang_major__ < 7)
// push_back, not emplace_back: #5066 needs the implicit conversion
// from json to the vector's value type
std::vector<std::variant<json>> v;
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
CHECK(std::get<0>(v[0]) == 1);
#endif
}
#endif
SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
{
const Issue3669Holder h{};
+115
View File
@@ -0,0 +1,115 @@
// __ _____ _____ _____
// __| | __| | | | 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-regression3.cpp (std::any, std::optional,
// std::variant and std::filesystem regression tests). It is kept in a separate translation
// unit so the (much larger) unit-regression3.cpp is built for C++11 only and not rebuilt
// for every C++ standard.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using ordered_json = nlohmann::ordered_json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#ifdef JSON_HAS_CPP_17
#include <cstdint>
#include <string>
#include <typeinfo>
#include <vector>
#include <any>
#include <variant>
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#endif
/////////////////////////////////////////////////////////////////////
// for #4740
/////////////////////////////////////////////////////////////////////
struct Example_4740
{
std::optional<std::string> host = std::nullopt;
std::optional<int> port = std::nullopt;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
};
TEST_CASE("regression tests 3 (C++17)")
{
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
{
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
const json j(text_path);
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
CHECK(j_path == text_path);
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
// only known to work on Clang and GCC >=8.4
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
#endif
}
#endif
#if JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
{
const json j;
const std::any a1 = j;
std::any&& a2 = j;
CHECK(a1.type() == typeid(j));
CHECK(a2.type() == typeid(j));
}
#endif
SECTION("issue #4740 - build issue with std::optional")
{
const auto t1 = Example_4740();
const auto j1 = nlohmann::json(t1);
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
const auto t2 = j1.get<Example_4740>();
CHECK(!t2.host.has_value());
CHECK(!t2.port.has_value());
// improve coverage
auto t3 = Example_4740();
t3.port = 80;
t3.host = "example.com";
const auto j2 = nlohmann::json(t3);
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
const auto t4 = j2.get<Example_4740>();
CHECK(t4.host.has_value());
CHECK(t4.port.has_value());
}
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
{
const json jval{};
auto GetValue = [](const json & valRoot) -> std::optional<json>
{
if (valRoot.contains("default"))
{
return valRoot.at("default");
}
return std::nullopt;
};
auto result = GetValue(jval);
CHECK(!result.has_value());
}
}
#endif
+127
View File
@@ -0,0 +1,127 @@
// __ _____ _____ _____
// __| | __| | | | 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-regression3.cpp (C++20 regression tests
// (operator<=> related aggregates, ranges)). It is kept in a separate translation unit so
// the (much larger) unit-regression3.cpp is built for C++11 only and not rebuilt for every
// C++ standard.
#include "doctest_compatibility.h"
// skip tests if JSON_DisableEnumSerialization=ON (#4384): std::byte is a
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
// from a plain JSON array, not just from an already-binary value) relies on
// enum serialization being enabled
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
#endif
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using ordered_json = nlohmann::ordered_json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#ifdef JSON_HAS_CPP_20
#include <string>
#if __has_include(<span>)
#include <span>
#endif
/////////////////////////////////////////////////////////////////////
// for #4440
/////////////////////////////////////////////////////////////////////
#if JSON_HAS_RANGES == 1
#include <ranges>
#endif
/////////////////////////////////////////////////////////////////////
// for #3312
/////////////////////////////////////////////////////////////////////
struct for_3312
{
std::string name;
};
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
{
j.at("name").get_to(obj.name);
}
TEST_CASE("regression tests 3 (C++20)")
{
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
{
// see test for #3171
const ordered_json j = {{"name", "class"}};
for_3312 obj{};
j.get_to(obj);
CHECK(obj.name == "class");
}
#if JSON_HAS_RANGES == 1
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
{
auto noOpFilter = std::views::filter([](auto&&) noexcept
{
return true;
});
json j = {1, 2, 3};
auto filtered = j | noOpFilter;
CHECK(*filtered.begin() == 1);
}
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
{
std::vector<int> nums{1, 2, 37, 42, 21};
auto filteredNums = nums | std::views::filter([](int i)
{
return i > 10;
});
json const j(filteredNums);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
// owning_view is not available in libstdc++ < 12
#if JSON_HAS_RANGE_VIEW_CONVERSION && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
{
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
{
return i > 10;
}));
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
{
std::vector<int> nums{1, 2, 3};
auto t = nums | std::views::transform([](int i) noexcept
{
return i * 2;
});
json const j(t);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({2, 4, 6}));
}
#endif
}
#endif
+5 -192
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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-regression3-cpp<NN>.cpp. This file stays C++11-only.
#include "doctest_compatibility.h"
@@ -47,33 +46,8 @@ using ordered_json = nlohmann::ordered_json;
#include <type_traits>
#include <utility>
#ifdef JSON_HAS_CPP_17
#include <any>
#include <variant>
#endif
#ifdef JSON_HAS_CPP_17
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#endif
#endif
#ifdef JSON_HAS_CPP_20
#if __has_include(<span>)
#include <span>
#endif
#endif
// the explicit instantiation for #4825 is in unit-explicit_instantiation.cpp
/////////////////////////////////////////////////////////////////////
// for #4440
/////////////////////////////////////////////////////////////////////
#if JSON_HAS_RANGES == 1
#include <ranges>
#endif
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
@@ -188,22 +162,6 @@ inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-inter
tb._from_json(j);
}
/////////////////////////////////////////////////////////////////////
// for #3312
/////////////////////////////////////////////////////////////////////
#ifdef JSON_HAS_CPP_20
struct for_3312
{
std::string name;
};
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
{
j.at("name").get_to(obj.name);
}
#endif
/////////////////////////////////////////////////////////////////////
// for #3204
/////////////////////////////////////////////////////////////////////
@@ -267,37 +225,8 @@ struct Example_3810
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage)
/////////////////////////////////////////////////////////////////////
// for #4740
/////////////////////////////////////////////////////////////////////
#ifdef JSON_HAS_CPP_17
struct Example_4740
{
std::optional<std::string> host = std::nullopt;
std::optional<int> port = std::nullopt;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
};
#endif
TEST_CASE("regression tests 3")
{
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
// JSON_HAS_CPP_17 (do not remove; see note at top of file)
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
{
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
const json j(text_path);
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
CHECK(j_path == text_path);
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
// only known to work on Clang and GCC >=8.4
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
#endif
}
#endif
SECTION("issue #3077 - explicit constructor with default does not compile")
{
@@ -348,31 +277,6 @@ TEST_CASE("regression tests 3")
CHECK(td.str == "value");
}
#ifdef JSON_HAS_CPP_20
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
{
// see test for #3171
const ordered_json j = {{"name", "class"}};
for_3312 obj{};
j.get_to(obj);
CHECK(obj.name == "class");
}
#endif
#if defined(JSON_HAS_CPP_17) && JSON_USE_IMPLICIT_CONVERSIONS
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
{
const json j;
const std::any a1 = j;
std::any&& a2 = j;
CHECK(a1.type() == typeid(j));
CHECK(a2.type() == typeid(j));
}
#endif
SECTION("issue #3204 - ambiguous regression")
{
const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
@@ -415,28 +319,6 @@ TEST_CASE("regression tests 3")
CHECK(oj["test"].dump() == expected);
}
#ifdef JSON_HAS_CPP_17
SECTION("issue #4740 - build issue with std::optional")
{
const auto t1 = Example_4740();
const auto j1 = nlohmann::json(t1);
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
const auto t2 = j1.get<Example_4740>();
CHECK(!t2.host.has_value());
CHECK(!t2.port.has_value());
// improve coverage
auto t3 = Example_4740();
t3.port = 80;
t3.host = "example.com";
const auto j2 = nlohmann::json(t3);
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
const auto t4 = j2.get<Example_4740>();
CHECK(t4.host.has_value());
CHECK(t4.port.has_value());
}
#endif
#if !defined(_MSVC_LANG)
// MSVC returns garbage on invalid enum values, so this test is excluded
// there.
@@ -451,76 +333,7 @@ TEST_CASE("regression tests 3")
}
#endif
#ifdef JSON_HAS_CPP_17
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
{
const json jval{};
auto GetValue = [](const json & valRoot) -> std::optional<json>
{
if (valRoot.contains("default"))
{
return valRoot.at("default");
}
return std::nullopt;
};
auto result = GetValue(jval);
CHECK(!result.has_value());
}
#endif
#if JSON_HAS_RANGES == 1
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
{
auto noOpFilter = std::views::filter([](auto&&) noexcept
{
return true;
});
json j = {1, 2, 3};
auto filtered = j | noOpFilter;
CHECK(*filtered.begin() == 1);
}
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
{
std::vector<int> nums{1, 2, 37, 42, 21};
auto filteredNums = nums | std::views::filter([](int i)
{
return i > 10;
});
json const j(filteredNums);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
// owning_view is not available in libstdc++ < 12
#if JSON_HAS_RANGE_VIEW_CONVERSION && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
{
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
{
return i > 10;
}));
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
#if JSON_HAS_RANGE_VIEW_CONVERSION
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
{
std::vector<int> nums{1, 2, 3};
auto t = nums | std::views::transform([](int i) noexcept
{
return i * 2;
});
json const j(t);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({2, 4, 6}));
}
#endif
}
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
+4 -7
View File
@@ -6,12 +6,10 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// source file mentions the corresponding version macro. This whole file tests
// std::format (C++20), so it cannot be split: it is built for C++20 as well:
// JSON_HAS_CPP_20 (do not remove)
#include "doctest_compatibility.h"
@@ -19,7 +17,6 @@
using json = nlohmann::json;
using ordered_json = nlohmann::ordered_json;
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
#if JSON_HAS_STD_FORMAT
#include <iterator>
-129
View File
@@ -1,129 +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 <array>
#include <cstdint>
#include <limits>
#include <type_traits>
// The detail helpers that stand in for standard library functions of newer
// standards: each uses the standard function where the library has it and a
// C++11 implementation otherwise, so this file is also built with
// JSON_HAS_CPP_17, JSON_HAS_CPP_20, and JSON_HAS_CPP_23 to test both.
namespace
{
struct with_constexpr_member
{
static constexpr std::size_t lower = 64; // no out-of-line definition
};
} // namespace
TEST_CASE("detail::clamp")
{
using nlohmann::detail::clamp;
SECTION("integers")
{
CHECK(clamp(5, 1, 10) == 5);
CHECK(clamp(0, 1, 10) == 1);
CHECK(clamp(11, 1, 10) == 10);
CHECK(clamp(1, 1, 10) == 1);
CHECK(clamp(10, 1, 10) == 10);
CHECK(clamp(7, 7, 7) == 7);
CHECK(clamp(-5, -3, 3) == -3);
CHECK(clamp(std::size_t{0}, std::size_t{64}, std::size_t{0x10000}) == 64);
CHECK(clamp(std::size_t{0x20000}, std::size_t{64}, std::size_t{0x10000}) == 0x10000);
}
SECTION("floating-point numbers")
{
CHECK(clamp(0.5, 0.0, 1.0) == 0.5);
CHECK(clamp(-0.5, 0.0, 1.0) == 0.0);
CHECK(clamp(1.5, 0.0, 1.0) == 1.0);
CHECK(clamp(-std::numeric_limits<double>::infinity(), 0.0, 1.0) == 0.0);
}
SECTION("by value: a static constexpr member needs no definition")
{
CHECK(clamp(std::size_t{1}, with_constexpr_member::lower, std::size_t{100}) == 64);
}
SECTION("constexpr and noexcept")
{
static_assert(clamp(3, 1, 2) == 2, "clamp must be constexpr");
static_assert(noexcept(clamp(3, 1, 2)), "clamp must be noexcept");
}
}
TEST_CASE("detail::bit_cast")
{
using nlohmann::detail::bit_cast;
CHECK(bit_cast<std::uint64_t>(1.0) == 0x3FF0000000000000u);
CHECK(bit_cast<std::uint64_t>(-2.0) == 0xC000000000000000u);
CHECK(bit_cast<std::uint32_t>(1.0f) == 0x3F800000u);
CHECK(bit_cast<double>(std::uint64_t{0x4009000000000000u}) == 3.125);
CHECK(bit_cast<float>(std::uint32_t{0xC0490000u}) == -3.140625f);
CHECK(bit_cast<std::int8_t>(std::uint8_t{0xFF}) == -1);
// to and from byte arrays
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
}
TEST_CASE("detail::byteswap")
{
using nlohmann::detail::byteswap;
CHECK(byteswap(std::uint16_t{0x0102u}) == 0x0201u);
CHECK(byteswap(std::uint16_t{0xFF00u}) == 0x00FFu);
CHECK(byteswap(std::uint32_t{0x01020304u}) == 0x04030201u);
CHECK(byteswap(std::uint32_t{0xFF000080u}) == 0x800000FFu);
CHECK(byteswap(std::uint64_t{0x0102030405060708u}) == 0x0807060504030201u);
CHECK(byteswap(std::uint64_t{0xFF00000000000080u}) == 0x80000000000000FFu);
// an involution
for (std::uint64_t x :
{
std::uint64_t{0}, std::uint64_t{1}, std::uint64_t{0x123456789ABCDEF0u}, (std::numeric_limits<std::uint64_t>::max)()
})
{
CHECK(byteswap(byteswap(x)) == x);
CHECK(byteswap(byteswap(static_cast<std::uint32_t>(x))) == static_cast<std::uint32_t>(x));
CHECK(byteswap(byteswap(static_cast<std::uint16_t>(x))) == static_cast<std::uint16_t>(x));
}
static_assert(std::is_same < decltype(byteswap(std::uint16_t{})), std::uint16_t >::value, "byteswap keeps the type");
static_assert(std::is_same < decltype(byteswap(std::uint32_t{})), std::uint32_t >::value, "byteswap keeps the type");
static_assert(std::is_same < decltype(byteswap(std::uint64_t{})), std::uint64_t >::value, "byteswap keeps the type");
}
TEST_CASE("detail::count_leading_zeros and count_trailing_zeros")
{
using nlohmann::detail::count_leading_zeros;
using nlohmann::detail::count_trailing_zeros;
// a single bit, and that bit with all lower (resp. higher) bits set: both
// halves of the 32-bit fallbacks
for (int k = 0; k < 64; ++k)
{
const std::uint64_t bit = std::uint64_t{1} << k;
CAPTURE(k)
CHECK(count_leading_zeros(bit) == 63 - k);
CHECK(count_leading_zeros(bit | (bit - 1)) == 63 - k);
CHECK(count_trailing_zeros(bit) == k);
CHECK(count_trailing_zeros(bit | ~(bit | (bit - 1))) == k);
}
}
+8 -8
View File
@@ -13,7 +13,7 @@
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::detail::bit_cast;
using nlohmann::detail::dtoa_impl::reinterpret_bits;
#include <algorithm>
#include <array>
@@ -44,7 +44,7 @@ float make_float(uint32_t sign_bit, uint32_t biased_exponent, uint32_t significa
bits |= biased_exponent << 23;
bits |= significand;
return bit_cast<float>(bits);
return reinterpret_bits<float>(bits);
}
// ldexp -- convert f * 2^e to IEEE single precision
@@ -81,7 +81,7 @@ float make_float(uint64_t f, int e)
: static_cast<uint64_t>(e + kExponentBias);
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
return bit_cast<float>(static_cast<uint32_t>(bits));
return reinterpret_bits<float>(static_cast<uint32_t>(bits));
}
double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand)
@@ -96,7 +96,7 @@ double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t signifi
bits |= biased_exponent << 52;
bits |= significand;
return bit_cast<double>(bits);
return reinterpret_bits<double>(bits);
}
// ldexp -- convert f * 2^e to IEEE double precision
@@ -133,7 +133,7 @@ double make_double(uint64_t f, int e)
: static_cast<uint64_t>(e + kExponentBias);
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
return bit_cast<double>(bits);
return reinterpret_bits<double>(bits);
}
} // namespace
@@ -652,7 +652,7 @@ bool reads_back(const std::string& digits, int e, double v)
{
const std::string text = digits + "e" + std::to_string(e);
// (compared bit for bit: v is positive and finite, and -Wfloat-equal)
return bit_cast<std::uint64_t>(parse_double(text)) == bit_cast<std::uint64_t>(v);
return reinterpret_bits<std::uint64_t>(parse_double(text)) == reinterpret_bits<std::uint64_t>(v);
}
/// Check the representation of a positive finite double: it reads back as
@@ -665,7 +665,7 @@ void check_shortest(double v)
CAPTURE(text)
CHECK(parse_double(text) == v);
// the layout is that of format_buffer() for the digits of Zmij
const auto sd = nlohmann::detail::zmij::to_shortest(bit_cast<std::uint64_t>(v));
const auto sd = nlohmann::detail::zmij::to_shortest(reinterpret_bits<std::uint64_t>(v));
const std::uint64_t significand = sd.has_digit ? (sd.integral * 10) + sd.digit : sd.integral;
int exponent = sd.has_digit ? sd.exponent : sd.exponent + 1;
std::string significand_digits = std::to_string(significand);
@@ -779,7 +779,7 @@ TEST_CASE("shortest digits of doubles")
for (int i = 0; i < 100000; ++i)
{
const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu;
const auto v = bit_cast<double>(bits);
const auto v = reinterpret_bits<double>(bits);
if (std::isfinite(v) && bits != 0)
{
check_shortest(v);
+219
View File
@@ -0,0 +1,219 @@
// __ _____ _____ _____
// __| | __| | | | 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-user_defined_input.cpp (parsing with
// std::counted_iterator and std::default_sentinel_t). It is kept in a separate translation
// unit so the (much larger) unit-user_defined_input.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 <cstddef>
#include <string>
#include <vector>
#if defined(__cpp_lib_concepts)
#include <iterator>
#endif
#if defined(__cpp_lib_concepts)
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
{
using iterator_type = std::string::const_iterator;
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
{
// A sized sentinel makes the remaining element count computable in O(1), so
// std::counted_iterator over a contiguous iterator must reach the same bulk
// string/number scanners as a plain pointer - not just the byte-at-a-time
// fallback (see #5268 for the equivalent memcpy fast path).
#if JSON_HAS_RANGES
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
// to the byte-at-a-time scanner; everything below still has to work there.
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
CHECK(adapter_type::supports_bulk_scan);
CHECK(adapter_type::supports_seek);
#endif
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
// integer/floating-point numbers
const std::string json_str =
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
const std::counted_iterator<const char*> first(json_str.data(), len);
const json j = json::parse(first, std::default_sentinel);
// parsing through the pointer adapter must give exactly the same result
CHECK(j == json::parse(json_str));
#if !defined(JSON_NOEXCEPTION)
// Diagnostics that quote the offending token are reconstructed from the
// already-consumed input (supports_seek), a path a sized sentinel only
// reaches now; check a few that include the "last read" text. Parsing
// invalid input aborts when exceptions are off, hence the guard.
// Raw strings and explicit bytes: an escaped literal and two literals
// written next to each other both read as mistakes to static analysis.
const auto byte = [](int value)
{
return std::string(1, static_cast<char>(value));
};
const std::vector<std::string> diagnostic_docs =
{
"1\nx",
"truX",
"[tru]",
R"("abc)",
R"(["\ud834"])",
R"(["a)" + byte(0x01) + R"(b"])",
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
"[1e]",
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
};
for (const auto& text : diagnostic_docs)
{
CAPTURE(text)
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
std::string counted_message;
std::string string_message;
try
{
const json counted_result = json::parse(it, std::default_sentinel);
static_cast<void>(counted_result);
}
catch (const json::parse_error& e)
{
counted_message = e.what();
}
try
{
const json string_result = json::parse(text);
static_cast<void>(string_result);
}
catch (const json::parse_error& e)
{
string_message = e.what();
}
CHECK_FALSE(counted_message.empty());
CHECK(counted_message == string_message);
}
// and errors must still be reported identically
const std::string bad = "[01\n]";
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
std::string counted_what;
std::string string_what;
try
{
const json counted_result = json::parse(bad_first, std::default_sentinel);
static_cast<void>(counted_result);
}
catch (const json::parse_error& e)
{
counted_what = e.what();
}
try
{
const json string_result = json::parse(bad);
static_cast<void>(string_result);
}
catch (const json::parse_error& e)
{
string_what = e.what();
}
CHECK_FALSE(counted_what.empty());
CHECK(counted_what == string_what);
#endif
}
#if !defined(JSON_NOEXCEPTION)
// several cases below are truncated on purpose, and parsing invalid input
// aborts when exceptions are off
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
{
// The count, not the size of the underlying buffer, is the end of the
// input: the bulk scanners must never look at the bytes behind it, even
// though they are readable. Each case is compared against parsing the
// equivalent prefix as a std::string.
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
{
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
try
{
const json j = json::parse(first, std::default_sentinel);
return "OK|" + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
{
try
{
const json j = json::parse(buf.substr(0, n));
return "OK|" + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
{
const char* buffer;
std::size_t count;
};
const std::vector<testcase> cases =
{
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
{"[\"abc\"]____", 6}, // cut just before the closing quote
{"[12345]xxxxx", 4}, // cut inside a number
{"[123]999999", 5}, // number ends exactly at the count
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
};
for (const auto& tc : cases)
{
CAPTURE(tc.buffer)
CAPTURE(tc.count)
const std::string buffer = tc.buffer;
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
}
}
#endif
#endif
#endif
+5 -202
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// 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)
// 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-user_defined_input-cpp<NN>.cpp. This file stays C++11-only.
#include "doctest_compatibility.h"
@@ -25,10 +24,6 @@ using nlohmann::json;
#include <string> // string
#include <vector> // vector
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
#include <iterator>
#endif
namespace
{
TEST_CASE("Use arbitrary stdlib container")
@@ -277,196 +272,4 @@ TEST_CASE("Contiguous byte containers take the pointer adapter")
CHECK(json::parse(buffer) == json({1, 2, 3, 4, 5}));
}
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
{
using iterator_type = std::string::const_iterator;
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
{
// A sized sentinel makes the remaining element count computable in O(1), so
// std::counted_iterator over a contiguous iterator must reach the same bulk
// string/number scanners as a plain pointer - not just the byte-at-a-time
// fallback (see #5268 for the equivalent memcpy fast path).
#if JSON_HAS_RANGES
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
// to the byte-at-a-time scanner; everything below still has to work there.
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
CHECK(adapter_type::supports_bulk_scan);
CHECK(adapter_type::supports_seek);
#endif
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
// integer/floating-point numbers
const std::string json_str =
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
const std::counted_iterator<const char*> first(json_str.data(), len);
const json j = json::parse(first, std::default_sentinel);
// parsing through the pointer adapter must give exactly the same result
CHECK(j == json::parse(json_str));
#if !defined(JSON_NOEXCEPTION)
// Diagnostics that quote the offending token are reconstructed from the
// already-consumed input (supports_seek), a path a sized sentinel only
// reaches now; check a few that include the "last read" text. Parsing
// invalid input aborts when exceptions are off, hence the guard.
// Raw strings and explicit bytes: an escaped literal and two literals
// written next to each other both read as mistakes to static analysis.
const auto byte = [](int value)
{
return std::string(1, static_cast<char>(value));
};
const std::vector<std::string> diagnostic_docs =
{
"1\nx",
"truX",
"[tru]",
R"("abc)",
R"(["\ud834"])",
R"(["a)" + byte(0x01) + R"(b"])",
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
"[1e]",
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
};
for (const auto& text : diagnostic_docs)
{
CAPTURE(text)
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
std::string counted_message;
std::string string_message;
try
{
const json counted_result = json::parse(it, std::default_sentinel);
static_cast<void>(counted_result);
}
catch (const json::parse_error& e)
{
counted_message = e.what();
}
try
{
const json string_result = json::parse(text);
static_cast<void>(string_result);
}
catch (const json::parse_error& e)
{
string_message = e.what();
}
CHECK_FALSE(counted_message.empty());
CHECK(counted_message == string_message);
}
// and errors must still be reported identically
const std::string bad = "[01\n]";
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
std::string counted_what;
std::string string_what;
try
{
const json counted_result = json::parse(bad_first, std::default_sentinel);
static_cast<void>(counted_result);
}
catch (const json::parse_error& e)
{
counted_what = e.what();
}
try
{
const json string_result = json::parse(bad);
static_cast<void>(string_result);
}
catch (const json::parse_error& e)
{
string_what = e.what();
}
CHECK_FALSE(counted_what.empty());
CHECK(counted_what == string_what);
#endif
}
#if !defined(JSON_NOEXCEPTION)
// several cases below are truncated on purpose, and parsing invalid input
// aborts when exceptions are off
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
{
// The count, not the size of the underlying buffer, is the end of the
// input: the bulk scanners must never look at the bytes behind it, even
// though they are readable. Each case is compared against parsing the
// equivalent prefix as a std::string.
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
{
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
try
{
const json j = json::parse(first, std::default_sentinel);
return "OK|" + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
{
try
{
const json j = json::parse(buf.substr(0, n));
return "OK|" + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
{
const char* buffer;
std::size_t count;
};
const std::vector<testcase> cases =
{
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
{"[\"abc\"]____", 6}, // cut just before the closing quote
{"[12345]xxxxx", 4}, // cut inside a number
{"[123]999999", 5}, // number ends exactly at the count
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
};
for (const auto& tc : cases)
{
CAPTURE(tc.buffer)
CAPTURE(tc.count)
const std::string buffer = tc.buffer;
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
}
}
#endif
#endif
} // namespace