Compare commits

...
Author SHA1 Message Date
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
Niels Lohmann d33068da73 Address review comments on the API stability docs and a test comment (#5784)
* Address review comments on #5775 and #5779

Allow new defaulted parameters and new default arguments in the API
stability rules, mention the macro opt-in, and drop the redundant
recompile advice. Describe test-diagnostics-optimized as the regression
test for the fixed #5742.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document what counts as a breaking change in the API stability rules

Spell out the 3.x compatibility rules in the roadmap: new defaulted
parameters, new default arguments, noexcept/constexpr, template
parameters, parse and dump results, accepted input, key iteration order,
iterator invalidation, implicit conversions, to_json/from_json lookup,
json_sax, value_t enumerators, and documented macros, CMake options and
headers. Also list std::hash values as not part of the public API, and
link the macro overview from the section.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 17:44:34 +02:00
Niels Lohmann d8dfc0d0f9 Fix unused-result warnings in the contains and parse_error examples (#5788)
contains(json_pointer) is marked JSON_HEDLEY_WARN_UNUSED_RESULT since #5477,
and parse() has been for longer. The contains example ignored the result in
two try blocks waiting for a parse_error that contains() never throws, so
they printed nothing; print the result for those pointers instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:21:59 +02:00
Niels Lohmann 3d7f554927 Use the with_*_t aliases in tests, examples, and docs (#5787)
* Use the with_*_t aliases in tests, examples, and docs

Replace spelled-out basic_json<...> instantiations that only change one
or two template parameters with nlohmann::json::with_*_t (or
ordered_json::with_*_t when the object type is ordered_map). Types that
change all three number types chain with_integers_t and with_float_t.

The raw basic_json<...> spelling stays where the template parameter
list itself is the subject: the alias tests in unit-udt.cpp, explicit
instantiations, and the ordered_json/compile-time docs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix unit-large_json for clang and JSON_DIAGNOSTICS

Two test problems from #5781 broke CI on develop: CAPTURE(depth); trips
clang's -Wextra-semi-stmt, and the type_error.321 messages did not
account for the diagnostics path prefix.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Use static_cast in unit-hash for clang-tidy

#5772 added functional casts that clang-tidy reports as C-style casts
(google-readability-casting). Also append a char instead of a
one-character string in unit-large_json.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Declare the expected message prefix const in unit-large_json

Without JSON_DIAGNOSTICS the prefix was never modified, which
clang-tidy reports (misc-const-correctness).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-08 16:11:16 +02:00
Suyog Verma a269794db7 Use MSVC intrinsics for full multiplication (#5782)
* Use MSVC intrinsics for full multiplication

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>

* Fix formatting in unit-class_lexer

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>

* Address review feedback

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>

---------

Signed-off-by: Suyog Verma <suyogverma0057@gmail.com>
2026-10-08 08:49:32 +02:00
67 changed files with 4088 additions and 2362 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 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. 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. When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue. Tests that need C++17 or
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the C++20 go into `unit-regression3-cpp17.cpp` or `unit-regression3-cpp20.cpp` instead, so the large file is not rebuilt
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one. 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 #### Exceptions
+3 -3
View File
@@ -871,11 +871,11 @@ add_custom_target(ci_icpx
# The following tests are excluded as they trigger known nvc++ 25.5 defects (not # 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 # library bugs); see https://github.com/nlohmann/json for tracking. Only the
# affected language-standard variants are excluded so coverage is otherwise kept: # 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`) # miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
# - test-constructor1_cpp11 # - test-constructor1_cpp11
# std::initializer_list lifetime bug -> SIGSEGV # 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 # mangles the UTF-8 u8"" string literal in the char8_t (C++20) section
add_custom_target(ci_nvhpc add_custom_target(ci_nvhpc
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
@@ -887,7 +887,7 @@ add_custom_target(ci_nvhpc
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
# the pipes are escaped so the surrounding shell passes them to ctest verbatim # the pipes are escaped so the surrounding shell passes them to ctest verbatim
# instead of treating them as shell pipe operators # 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++)" COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
) )
+352 -39
View File
@@ -109,6 +109,55 @@ function(_json_test_apply_test_properties test_target properties_target)
endif() endif()
endfunction() 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() # for internal use by json_test_add_test_for()
function(_json_test_add_test test_name file main cxx_standard) function(_json_test_add_test test_name file main cxx_standard)
set(test_target ${test_name}_cpp${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() endif()
if (JSON_FastTests) _json_test_register_test(${test_name} ${test_target} ${cxx_standard})
add_test(NAME ${test_target}
COMMAND ${test_target} ${DOCTEST_TEST_FILTER}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
else()
add_test(NAME ${test_target}
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} --no-skip
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
endif()
set_tests_properties(${test_target} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
# apply standard-specific test properties
if(TARGET _json_test_interface__cpp_${cxx_standard})
_json_test_apply_test_properties(${test_target} _json_test_interface__cpp_${cxx_standard})
endif()
# apply test-specific test properties
if(TARGET _json_test_interface_${test_name})
_json_test_apply_test_properties(${test_target} _json_test_interface_${test_name})
endif()
# apply test- and standard-specific test properties
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
_json_test_apply_test_properties(${test_target}
_json_test_interface_${test_name}_cpp_${cxx_standard}
)
endif()
if(JSON_Valgrind)
add_test(NAME ${test_target}_valgrind
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
set_tests_properties(${test_target}_valgrind PROPERTIES
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
)
endif()
endfunction() endfunction()
############################################################################# #############################################################################
@@ -200,6 +211,14 @@ endfunction()
# Use NAME <name> to override the filename-derived test name. # Use NAME <name> to override the filename-derived test name.
# Use FORCE to create the test regardless of the file containing # Use FORCE to create the test regardless of the file containing
# JSON_HAS_CPP_<version_number>. # 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>. # Test targets are linked against <main>.
# CXX_STANDARDS defaults to "11". # CXX_STANDARDS defaults to "11".
############################################################################# #############################################################################
@@ -243,6 +262,300 @@ function(json_test_add_test_for file)
endforeach() endforeach()
endfunction() 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")
endif()
foreach(file ${ARGN})
string(APPEND batch_content "#include \"${file}\"\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()
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( # json_test_should_build_32bit_test(
# <build_32bit_var> <build_32bit_only_var> <input>) # <build_32bit_var> <build_32bit_only_var> <input>)
@@ -67,7 +67,7 @@ By default, implicit conversions are enabled.
`JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`:
```cpp ```cpp
using wjson = nlohmann::basic_json<std::map, std::vector, std::wstring>; using wjson = nlohmann::json::with_string_t<std::wstring>;
void load(const nlohmann::json& j); void load(const nlohmann::json& j);
+23 -9
View File
@@ -36,13 +36,26 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
## API stability ## API stability
Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code
that uses the public API. In particular, a 3.x release does not: that uses the public API, unless that code opts in to a change with a macro as described [below](#version-40). In
particular, a 3.x release does not:
- change the signature of a function (its parameter types, return type, number of parameters, or the const-ness of a - make breaking changes to the signature of a function: the types or order of its existing parameters, its return type,
member function); its `noexcept` or `constexpr` specifier, or the const-ness of a member function. New parameters may be added if they
- remove or rename a function or class; have a default value;
- remove or rename a function or class, or change the template parameters of a public class template;
- change which exceptions a function throws, or the [exception ids](../home/exceptions.md); - change which exceptions a function throws, or the [exception ids](../home/exceptions.md);
- change access specifiers or default arguments. - change access specifiers, or change or remove existing default arguments. New default arguments may be added;
- change the JSON type that a valid input parses to, or the text that `dump()` produces for a valid value;
- accept input that was rejected before, or reject input that was accepted before;
- change the order in which the keys of an object are iterated. The default type sorts keys, and
[`ordered_json`](../api/ordered_json.md) keeps insertion order;
- change when iterators, pointers, or references are invalidated, or the state of a moved-from `basic_json`;
- add or remove implicit conversions from `basic_json`;
- change how `to_json` and `from_json` functions are found, or the behavior of
[`adl_serializer`](../api/adl_serializer/index.md);
- add pure virtual functions to the [`json_sax`](../api/json_sax/index.md) interface;
- remove, rename, renumber, or add enumerators of `value_t`;
- remove or rename a documented macro, CMake option, CMake target, or header, or change what a documented macro does.
Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are
documented in the [release notes](../home/releases.md). documented in the [release notes](../home/releases.md).
@@ -51,13 +64,14 @@ The following are **not** part of the public API and may change in any release,
- The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors - The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors
apart. apart.
- The ABI, including `sizeof(basic_json)` and the memory layout of its values. Recompile your code when you upgrade the - The ABI, including `sizeof(basic_json)` and the memory layout of its values. The
library. The [versioned inline namespace](../features/namespace.md) turns mixing versions into a link error. [versioned inline namespace](../features/namespace.md) turns mixing versions into a link error.
- The hash values returned by `std::hash` for `basic_json`. Numbers that compare equal still hash equally.
- Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the - Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the
[API reference](../api/basic_json/index.md). [API reference](../api/basic_json/index.md).
Changes that would break the public API are only added behind a macro whose default keeps the 3.x behavior, see Breaking changes are only added behind a macro whose default keeps the 3.x behavior. See [Version 4.0](#version-40) and
[Version 4.0](#version-40). the [macro overview](../features/macros.md).
## Version 4.0 ## Version 4.0
@@ -19,25 +19,9 @@ int main()
<< j.contains("/array/1"_json_pointer) << '\n' << j.contains("/array/1"_json_pointer) << '\n'
<< j.contains("/array/-"_json_pointer) << '\n' << j.contains("/array/-"_json_pointer) << '\n'
<< j.contains("/array/4"_json_pointer) << '\n' << j.contains("/array/4"_json_pointer) << '\n'
<< j.contains("/baz"_json_pointer) << std::endl; << j.contains("/baz"_json_pointer) << '\n'
// an array index with a leading '0' is not found
try << j.contains("/array/01"_json_pointer) << '\n'
{ // an array index that is not a number is not found
// try to use an array index with leading '0' << j.contains("/array/one"_json_pointer) << std::endl;
j.contains("/array/01"_json_pointer);
}
catch (const json::parse_error& e)
{
std::cout << e.what() << '\n';
}
try
{
// try to use an array index that is not a number
j.contains("/array/one"_json_pointer);
}
catch (const json::parse_error& e)
{
std::cout << e.what() << '\n';
}
} }
@@ -5,3 +5,5 @@ true
false false
false false
false false
false
false
@@ -1,11 +1,10 @@
#include <iostream> #include <iostream>
#include <map>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "custom_array_type.hpp" #include "custom_array_type.hpp"
using custom_json = nlohmann::basic_json<std::map, custom_array_type>; using custom_json = nlohmann::json::with_array_t<custom_array_type>;
int main() int main()
{ {
@@ -1,16 +1,10 @@
#include <cstdint>
#include <iostream> #include <iostream>
#include <map>
#include <string>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "custom_binary_type.hpp" #include "custom_binary_type.hpp"
using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, using custom_json = nlohmann::json::with_binary_t<custom_binary_type>;
std::int64_t, std::uint64_t, double, std::allocator,
nlohmann::adl_serializer, custom_binary_type>;
int main() int main()
{ {
@@ -1,12 +1,11 @@
#include <iostream> #include <iostream>
#include <type_traits> #include <type_traits>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "custom_object_type.hpp" #include "custom_object_type.hpp"
using custom_json = nlohmann::basic_json<custom_object_type, std::vector>; using custom_json = nlohmann::json::with_object_t<custom_object_type>;
int main() int main()
{ {
@@ -1,12 +1,10 @@
#include <iostream> #include <iostream>
#include <map>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "custom_string_type.hpp" #include "custom_string_type.hpp"
using custom_json = nlohmann::basic_json<std::map, std::vector, custom_string_type>; using custom_json = nlohmann::json::with_string_t<custom_string_type>;
int main() int main()
{ {
+1 -1
View File
@@ -8,7 +8,7 @@ int main()
try try
{ {
// parsing input with a syntax error // parsing input with a syntax error
json::parse("[1,2,3,]"); json j = json::parse("[1,2,3,]");
} }
catch (const json::parse_error& e) catch (const json::parse_error& e)
{ {
@@ -351,9 +351,8 @@ The number types can be changed with template parameters.
A `basic_json` type that uses `#!c long double` as floating-point type. A `basic_json` type that uses `#!c long double` as floating-point type.
```cpp hl_lines="2" ```cpp hl_lines="1"
using json_ld = nlohmann::basic_json<std::map, std::vector, std::string, bool, using json_ld = nlohmann::json::with_float_t<long double>;
std::int64_t, std::uint64_t, long double>;
``` ```
Note values should then be parsed with `json_ld::parse` rather than `json::parse` as the latter would parse Note values should then be parsed with `json_ld::parse` rather than `json::parse` as the latter would parse
@@ -8,6 +8,10 @@ these requirements so they do not have to be discovered by trial and error. Each
that are known to work for that parameter and the ones that do not, checked against Boost 1.83, Abseil 20250127.0, that are known to work for that parameter and the ones that do not, checked against Boost 1.83, Abseil 20250127.0,
Folly, EASTL 3.21, `ankerl::unordered_dense`, `phmap`, `gtl`, `robin_hood`, `tsl::ordered_map`, and Qt 6. Folly, EASTL 3.21, `ankerl::unordered_dense`, `phmap`, `gtl`, `robin_hood`, `tsl::ordered_map`, and Qt 6.
To change a single template parameter and keep the others, use the member alias templates
[`with_*_t`](../../api/basic_json/with_t.md); for instance, `nlohmann::json::with_float_t<long double>` is `json` with
`#!cpp long double` as [`number_float_t`](../../api/basic_json/number_float_t.md).
## How to read this page ## How to read this page
Requirements are split into two groups: Requirements are split into two groups:
@@ -143,7 +147,7 @@ struct unordered_map_object
using base_t::base_t; using base_t::base_t;
}; };
using unordered_json = nlohmann::basic_json<unordered_map_object>; using unordered_json = nlohmann::json::with_object_t<unordered_map_object>;
``` ```
Whether `#!cpp std::unordered_map` can be instantiated at all depends on the standard library: `object_t` is formed Whether `#!cpp std::unordered_map` can be instantiated at all depends on the standard library: `object_t` is formed
@@ -176,7 +180,7 @@ struct flat_hash_object
using base_t::base_t; using base_t::base_t;
}; };
using flat_hash_json = nlohmann::basic_json<flat_hash_object>; using flat_hash_json = nlohmann::json::with_object_t<flat_hash_object>;
``` ```
`absl::node_hash_map` keeps references to the mapped values valid across insertions; `absl::flat_hash_map` does not, `absl::node_hash_map` keeps references to the mapped values valid across insertions; `absl::flat_hash_map` does not,
+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 its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on
`JSON_BuildTests`. `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` ### `JSON_Valgrind`
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`. Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
@@ -116,7 +116,7 @@ function to use instead.
=== "Deprecated" === "Deprecated"
```cpp ```cpp
using my_json = nlohmann::basic_json<std::map, std::vector, my_string_type>; using my_json = nlohmann::json::with_string_t<my_string_type>;
nlohmann::json_pointer<my_json> ptr("/foo/bar/1"); nlohmann::json_pointer<my_json> ptr("/foo/bar/1");
``` ```
+11 -2
View File
@@ -9,12 +9,15 @@
#pragma once #pragma once
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
#include <intrin0.h> // __umulh, _umul128
#endif
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
// Portable bit-level helpers for the number and string scanners. They use // Portable bit-level helpers for the number and string scanners. They use
// compiler builtins where available and plain C++ otherwise, so they need no // compiler builtins or platform-specific intrinsics where available and plain
// platform headers and work regardless of byte order. // C++ otherwise, so they work regardless of byte order.
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -52,6 +55,12 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
__extension__ using uint128 = unsigned __int128; __extension__ using uint128 = unsigned __int128;
const uint128 r = static_cast<uint128>(a) * b; const uint128 r = static_cast<uint128>(a) * b;
return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)}; return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)};
#elif defined(_MSC_VER) && defined(_M_X64)
std::uint64_t high = 0;
const std::uint64_t low = _umul128(a, b, &high);
return {low, high};
#elif defined(_MSC_VER) && defined(_M_ARM64)
return {a * b, __umulh(a, b)};
#else #else
const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_lo = a & 0xFFFFFFFFu;
const std::uint64_t a_hi = a >> 32u; const std::uint64_t a_hi = a >> 32u;
+11 -2
View File
@@ -8791,13 +8791,16 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
#include <intrin0.h> // __umulh, _umul128
#endif
// #include <nlohmann/detail/abi_macros.hpp> // #include <nlohmann/detail/abi_macros.hpp>
// Portable bit-level helpers for the number and string scanners. They use // Portable bit-level helpers for the number and string scanners. They use
// compiler builtins where available and plain C++ otherwise, so they need no // compiler builtins or platform-specific intrinsics where available and plain
// platform headers and work regardless of byte order. // C++ otherwise, so they work regardless of byte order.
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -8835,6 +8838,12 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
__extension__ using uint128 = unsigned __int128; __extension__ using uint128 = unsigned __int128;
const uint128 r = static_cast<uint128>(a) * b; const uint128 r = static_cast<uint128>(a) * b;
return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)}; return {static_cast<std::uint64_t>(r), static_cast<std::uint64_t>(r >> 64u)};
#elif defined(_MSC_VER) && defined(_M_X64)
std::uint64_t high = 0;
const std::uint64_t low = _umul128(a, b, &high);
return {low, high};
#elif defined(_MSC_VER) && defined(_M_ARM64)
return {a * b, __umulh(a, b)};
#else #else
const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_lo = a & 0xFFFFFFFFu;
const std::uint64_t a_hi = a >> 32u; const std::uint64_t a_hi = a >> 32u;
+35 -1
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_Valgrind "Execute test suite with Valgrind." OFF)
option(JSON_FastTests "Skip expensive/slow tests." 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) 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_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_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_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.") 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") # 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) 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 # override standard support
############################################################################# #############################################################################
@@ -138,7 +145,8 @@ json_test_set_test_options(test-disabled_exceptions
# only the #972 regression test needs thirdparty/fifo_map on its include path # only the #972 regression test needs thirdparty/fifo_map on its include path
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include) json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742). # Regression test for GCC's false -Warray-bounds error with JSON_DIAGNOSTICS (#5742, fixed in #5585). It only
# showed up when optimizing, so build this test with -O3 and the warning as an error.
# -O3 makes the optimizer-driven warnings of the ci_test_gcc flag set (-Winline, # -O3 makes the optimizer-driven warnings of the ci_test_gcc flag set (-Winline,
# -Wsuggest-attribute=...) fire on the library's inline functions; they are not # -Wsuggest-attribute=...) fire on the library's inline functions; they are not
# what this test checks, so turn them off for it. # what this test checks, so turn them off for it.
@@ -321,9 +329,28 @@ if(NOT "${JSON_TestShard}" STREQUAL "")
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files") message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
endif() endif()
# Explicit groups of test files (file names without "unit-" and ".cpp") for
# JSON_TestUnityBuild: all files of a group are compiled into one translation
# unit per C++ standard, so that related tests share their template
# instantiations (all binary formats instantiate the same readers and
# writers). Only add a group where this was measured to pay off: grouping
# files that share little just creates one slow translation unit. Files in no
# group are batched alphabetically by JSON_TestUnityBatchSize.
set(json_test_unity_groups binary)
set(json_test_unity_group_binary binary_formats binary_utf8_error_handler binary_writer_sinks bjdata bon8 bon8-cpp17 bson byte_container_with_subtype cbor msgpack msgpack-cpp17 ubjson)
if(JSON_TestUnityBuild)
message(STATUS "Building the unit tests in batches of ${JSON_TestUnityBatchSize} files (JSON_TestUnityBuild)")
json_test_add_unity_tests(FILES ${files} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
BATCH_SIZE ${JSON_TestUnityBatchSize}
GROUPS ${json_test_unity_groups}
VARIANT_FILES src/unit-comparison.cpp src/unit-comparison-cpp20.cpp src/unit-class_parser.cpp src/unit-diagnostic-positions.cpp
)
else()
foreach(file ${files}) foreach(file ${files})
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
endforeach() endforeach()
endif()
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file, # 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. # so its test targets must be built after generate_hedley_undef_checks.
@@ -349,6 +376,13 @@ json_test_add_test_for(src/unit-comparison.cpp
NAME test-comparison_legacy NAME test-comparison_legacy
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} 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 # test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
json_test_set_test_options(test-class_parser_diagnostic_positions 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.
+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
+6 -67
View File
@@ -13,9 +13,6 @@
using nlohmann::json; using nlohmann::json;
#include <valarray> #include <valarray>
#if JSON_HAS_RANGES
#include <ranges>
#endif
namespace namespace
{ {
@@ -370,14 +367,7 @@ TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)")
#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) #if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
SECTION("std::map-backed object_t") SECTION("std::map-backed object_t")
{ {
using bad_alloc_json = nlohmann::basic_json<std::map, using bad_alloc_json = nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>;
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
nth_alloc_fails_allocator>;
check_deep_copy_survives_failing_allocation<bad_alloc_json>(false); check_deep_copy_survives_failing_allocation<bad_alloc_json>(false);
check_deep_copy_survives_failing_allocation<bad_alloc_json>(true); check_deep_copy_survives_failing_allocation<bad_alloc_json>(true);
@@ -385,14 +375,7 @@ TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)")
SECTION("ordered_map-backed object_t") SECTION("ordered_map-backed object_t")
{ {
using bad_alloc_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, using bad_alloc_ordered_json = nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>;
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
nth_alloc_fails_allocator>;
check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(false); check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(false);
check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(true); check_deep_copy_survives_failing_allocation<bad_alloc_ordered_json>(true);
@@ -450,14 +433,7 @@ struct scratch_counting_allocator : std::allocator<T>
TEST_CASE("deep copy uses the provided allocator") TEST_CASE("deep copy uses the provided allocator")
{ {
using counting_json = nlohmann::basic_json<std::map, using counting_json = nlohmann::json::with_allocator_t<scratch_counting_allocator>;
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
scratch_counting_allocator>;
// deeper than the 128 levels the copy constructor descends into, so the // deeper than the 128 levels the copy constructor descends into, so the
// innermost objects are copied by the iterative deep copy // innermost objects are copied by the iterative deep copy
@@ -516,14 +492,7 @@ TEST_CASE("converting a deeply nested value from another specialization fails cl
// the allocator in noexcept constructors, so a failing construction crashes // the allocator in noexcept constructors, so a failing construction crashes
// the program there instead of throwing std::bad_alloc. Nothing to check. // the program there instead of throwing std::bad_alloc. Nothing to check.
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) #if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
using countdown_json = nlohmann::basic_json<std::map, using countdown_json = nlohmann::json::with_allocator_t<countdown_allocator>;
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
countdown_allocator>;
// deeper than the 128 levels the converting constructor descends into, so // deeper than the 128 levels the converting constructor descends into, so
// that failures land on both sides of the bound - or, built with // that failures land on both sides of the bound - or, built with
@@ -631,14 +600,7 @@ TEST_CASE("destructor performs no allocation, only deallocation")
// Since that stack could itself throw bad_alloc from inside the // Since that stack could itself throw bad_alloc from inside the
// noexcept destructor (#5135), destroy() no longer allocates anything: // noexcept destructor (#5135), destroy() no longer allocates anything:
// it only ever frees what is already there. // it only ever frees what is already there.
using counting_json = nlohmann::basic_json<std::map, using counting_json = nlohmann::json::with_allocator_t<counting_allocator>;
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
counting_allocator>;
SECTION("array") SECTION("array")
{ {
@@ -683,14 +645,7 @@ TEST_CASE("destructor performs no allocation, only deallocation")
TEST_CASE("a failed allocation leaves the value unchanged") TEST_CASE("a failed allocation leaves the value unchanged")
{ {
// create JSON type using the throwing allocator // create JSON type using the throwing allocator
using my_json = nlohmann::basic_json<std::map, using my_json = nlohmann::json::with_allocator_t<my_allocator>;
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
my_allocator>;
// Each of these creates a string, array, object, or binary value. The // Each of these creates a string, array, object, or binary value. The
// value must be created before the type is changed: otherwise, a failed // value must be created before the type is changed: otherwise, a failed
@@ -726,12 +681,6 @@ TEST_CASE("a failed allocation leaves the value unchanged")
CHECK_THROWS_AS(j["key"], std::bad_alloc&); CHECK_THROWS_AS(j["key"], std::bad_alloc&);
CHECK(j.is_null()); 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; next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&); CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
CHECK(j.is_null()); CHECK(j.is_null());
@@ -829,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_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
CHECK(j == "old"); 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; next_construct_fails = false;
nlohmann::to_json(j, std::vector<int> {1, 2}); nlohmann::to_json(j, std::vector<int> {1, 2});
CHECK(j == my_json({1, 2})); CHECK(j == my_json({1, 2}));
+1 -1
View File
@@ -237,7 +237,7 @@ namespace
// the binary formats as function pointers for "Binary formats with narrow number types"; // the binary formats as function pointers for "Binary formats with narrow number types";
// named functions rather than lambdas, because clang 3.5 cannot convert a lambda // named functions rather than lambdas, because clang 3.5 cannot convert a lambda
// to a function pointer in the braced initializer of the format table // to a function pointer in the braced initializer of the format table
using narrow_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>; using narrow_json = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>;
using bytes = std::vector<std::uint8_t>; using bytes = std::vector<std::uint8_t>;
bytes encode_cbor(const json& j) bytes encode_cbor(const json& j)
+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
@@ -1052,41 +1052,3 @@ TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr
#endif #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
+34 -3
View File
@@ -17,6 +17,7 @@ using nlohmann::json;
#include <cstdint> // uint32_t, uint64_t #include <cstdint> // uint32_t, uint64_t
#include <cstdlib> // strtod #include <cstdlib> // strtod
#include <cstring> // memcpy #include <cstring> // memcpy
#include <limits> // numeric_limits
#include <sstream> // stringstream #include <sstream> // stringstream
#include <string> // string #include <string> // string
#include <utility> // pair #include <utility> // pair
@@ -891,8 +892,39 @@ TEST_CASE("Eisel-Lemire float conversion")
SECTION("128-bit products and leading zeros") SECTION("128-bit products and leading zeros")
{ {
const auto check_product = [](std::uint64_t a, std::uint64_t b)
{
const auto product = nlohmann::detail::full_multiplication(a, b);
CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b)));
};
const std::uint64_t max = (std::numeric_limits<std::uint64_t>::max)();
const std::array<std::pair<std::uint64_t, std::uint64_t>, 13> edge_cases =
{
{
{0, 0},
{0, 1},
{1, 1},
{1, max},
{0xFFFFFFFFu, 0x100000000u},
{0x100000000u, 0x100000000u},
{0x100000001u, 0x100000001u},
{max, max},
{max, 2},
{0xFFFFFFFF00000000u, 0x100000001u},
{0x100000001u, 0xFFFFFFFF00000000u},
{max, 1},
{2, max},
}
};
for (const auto& test : edge_cases)
{
check_product(test.first, test.second);
}
// whichever implementation the compiler gets (with or without a // whichever implementation the compiler gets (with or without a
// 128-bit integer type or a builtin) // 128-bit integer type or a builtin / intrinsic)
std::uint64_t state = 42; std::uint64_t state = 42;
for (int i = 0; i < 10000; ++i) for (int i = 0; i < 10000; ++i)
{ {
@@ -901,8 +933,7 @@ TEST_CASE("Eisel-Lemire float conversion")
state ^= state << 17u; state ^= state << 17u;
const std::uint64_t a = state; const std::uint64_t a = state;
const std::uint64_t b = (state * 0x9E3779B97F4A7C15u) >> (i % 64); const std::uint64_t b = (state * 0x9E3779B97F4A7C15u) >> (i % 64);
const auto product = nlohmann::detail::full_multiplication(a, b); check_product(a, b);
CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b)));
const int k = i % 64; const int k = i % 64;
const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1)); const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1));
+496
View File
@@ -0,0 +1,496 @@
// __ _____ _____ _____
// __| | __| | | | 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);
}
}
#endif // JSON_HAS_THREE_WAY_COMPARISON
#endif
+14 -308
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-comparison-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -28,37 +27,6 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; 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 namespace
{ {
// helper function to check std::less<json::value_t> // helper function to check std::less<json::value_t>
@@ -75,16 +43,6 @@ TEST_CASE("lexicographical comparison operators")
constexpr auto f_ = false; constexpr auto f_ = false;
constexpr auto _t = true; constexpr auto _t = true;
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN(); 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 #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
INFO("using legacy comparison"); INFO("using legacy comparison");
@@ -135,62 +93,13 @@ TEST_CASE("lexicographical comparison operators")
CAPTURE(i) CAPTURE(i)
CAPTURE(j) CAPTURE(j)
// check precomputed values // check precomputed values
#if JSON_HAS_THREE_WAY_COMPARISON #if !JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
#else
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]); CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
#endif #endif
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]); 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") SECTION("values")
@@ -319,18 +228,6 @@ TEST_CASE("lexicographical comparison operators")
CHECK_FALSE(above_int64_max <= max_int64); CHECK_FALSE(above_int64_max <= max_int64);
CHECK(above_int64_max > max_int64); CHECK(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") SECTION("integer/float mixed comparison is exact")
@@ -381,16 +278,6 @@ TEST_CASE("lexicographical comparison operators")
CHECK_FALSE(json(1) < json(nan)); CHECK_FALSE(json(1) < json(nan));
CHECK_FALSE(json(nan) < json(1)); CHECK_FALSE(json(nan) < json(1));
CHECK_FALSE(json(1u) == json(nan)); 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") SECTION("compares unordered")
@@ -617,72 +504,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 #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
@@ -721,60 +542,7 @@ TEST_CASE("lexicographical comparison operators")
#endif #endif
} }
#if JSON_HAS_THREE_WAY_COMPARISON namespace unit_comparison_detail
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
{
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
// Verify that heterogeneous comparisons now work.
SECTION("string vs json")
{
std::string s = "string";
json j = "string";
CHECK(s == j);
CHECK(j == s);
CHECK_FALSE(s != j);
CHECK_FALSE(j != s);
}
SECTION("other heterogeneous types")
{
int i = 42;
json j = 42;
CHECK(i == j);
CHECK(j == i);
CHECK_FALSE(i != j);
CHECK_FALSE(j != i);
}
}
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
{
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
// candidate rewritten from operator<=>, which does not emulate the legacy
// discarded-value behavior. Check that scalar-on-the-left now matches the
// other three operand orders.
const json discarded(json::value_t::discarded);
const json one = 1;
CHECK(discarded <= 1);
CHECK(discarded >= 1);
CHECK(one <= discarded);
CHECK(one >= discarded);
CHECK(1 <= discarded);
CHECK(1 >= discarded);
CHECK(1.5 <= discarded);
CHECK(1.5 >= discarded);
}
#endif
#endif
namespace
{ {
// orders keys ascending or descending, as chosen when a map is created // orders keys ascending or descending, as chosen when a map is created
template<class Key> template<class Key>
@@ -824,7 +592,7 @@ struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
return !(lhs == rhs); return !(lhs == rhs);
} }
}; };
using unordered_json = nlohmann::basic_json<unordered_object_t>; using unordered_json = nlohmann::json::with_object_t<unordered_object_t>;
// the entries "0" to "9", enumerated in ascending or in descending order // the entries "0" to "9", enumerated in ascending or in descending order
unordered_json make_unordered_object(const bool descending) unordered_json make_unordered_object(const bool descending)
@@ -875,7 +643,7 @@ struct key_case_less
template<class Key, class Value, class /*Compare*/, class Allocator> template<class Key, class Value, class /*Compare*/, class Allocator>
using key_case_map = std::map<Key, Value, key_case_less, Allocator>; using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
using key_case_json = nlohmann::basic_json<key_case_map>; using key_case_json = nlohmann::json::with_object_t<key_case_map>;
// the innermost value of a chain of single-element arrays // the innermost value of a chain of single-element arrays
template<typename Json> template<typename Json>
@@ -905,8 +673,10 @@ struct case_insensitive_less
template<class Key, class Value, class /*Compare*/, class Allocator> template<class Key, class Value, class /*Compare*/, class Allocator>
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>; using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
using ci_json = nlohmann::basic_json<case_insensitive_map>; using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
} // namespace } // namespace unit_comparison_detail
using namespace unit_comparison_detail; // NOLINT(google-build-using-namespace)
TEST_CASE("equality of objects whose entries have no fixed order") TEST_CASE("equality of objects whose entries have no fixed order")
{ {
@@ -1049,12 +819,6 @@ TEST_CASE("containers are compared element by element")
CHECK(a < b); CHECK(a < b);
CHECK(b > a); CHECK(b > a);
CHECK_FALSE(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 // a container that is a prefix of the other one
@@ -1072,11 +836,6 @@ TEST_CASE("containers are compared element by element")
CHECK(smaller_object < larger_object); CHECK(smaller_object < larger_object);
CHECK(larger_object > smaller_object); CHECK(larger_object > smaller_object);
CHECK_FALSE(smaller_object == larger_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 // elements that cannot be ordered
@@ -1087,13 +846,7 @@ TEST_CASE("containers are compared element by element")
CHECK_FALSE(lhs == lhs); CHECK_FALSE(lhs == lhs);
CHECK_FALSE(rhs < lhs); CHECK_FALSE(rhs < lhs);
#if JSON_HAS_THREE_WAY_COMPARISON #if !JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
// operator<=> stops there, as std::lexicographical_compare_three_way
// does, and operator< is derived from it
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
CHECK_FALSE(lhs < rhs);
#else
// operator< skips a pair of elements that cannot be ordered, as // operator< skips a pair of elements that cannot be ordered, as
// std::lexicographical_compare does, and the next pair decides // std::lexicographical_compare does, and the next pair decides
CHECK(lhs < rhs); CHECK(lhs < rhs);
@@ -1102,50 +855,3 @@ 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
+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-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-conversions1-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -31,25 +30,6 @@ using nlohmann::json;
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") 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") TEST_CASE("value conversion")
{ {
SECTION("get an object (explicit)") SECTION("get an object (explicit)")
@@ -519,13 +499,6 @@ TEST_CASE("value conversion")
const std::string s = j.get<std::string>(); const std::string s = j.get<std::string>();
CHECK(json(s) == j); 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") 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(json::value_t::number_float).get<json::string_t>(),
"[json.exception.type_error.302] type must be string, but is number", json::type_error&); "[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)") SECTION("get a string (explicit, get_to)")
@@ -591,15 +544,6 @@ TEST_CASE("value conversion")
j.get_to(s); j.get_to(s);
CHECK(json(s) == j); 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)") SECTION("get null (explicit)")
@@ -638,14 +582,6 @@ TEST_CASE("value conversion")
CHECK(json(s) == j); 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") SECTION("std::string")
{ {
const std::string s = j; const std::string s = j;
@@ -1256,11 +1192,4 @@ TEST_CASE("value conversion")
#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 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-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-conversions2-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -36,25 +35,6 @@ using nlohmann::json;
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") 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") TEST_CASE("value conversion")
{ {
SECTION("get a binary value (explicit)") 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 DOCTEST_CLANG_SUPPRESS_WARNING_POP
+2 -2
View File
@@ -22,7 +22,7 @@ namespace
// std::deque has no capacity() member function, which the library only needs // std::deque has no capacity() member function, which the library only needs
// to detect a reallocation for JSON_DIAGNOSTICS // to detect a reallocation for JSON_DIAGNOSTICS
using deque_json = nlohmann::basic_json<std::map, std::deque>; using deque_json = nlohmann::json::with_array_t<std::deque>;
// a std::vector whose at() is hidden: the library performs its own bounds // a std::vector whose at() is hidden: the library performs its own bounds
// check and must not fall back to the container's checked accessor // check and must not fall back to the container's checked accessor
@@ -39,7 +39,7 @@ class vector_without_at : public std::vector<T, Allocator>
void at() = delete; void at() = delete;
}; };
using no_at_json = nlohmann::basic_json<std::map, vector_without_at>; using no_at_json = nlohmann::json::with_array_t<vector_without_at>;
} // namespace } // namespace
@@ -0,0 +1,58 @@
// __ _____ _____ _____
// __| | __| | | | 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::basic_json <
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
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
-36
View File
@@ -17,10 +17,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#ifdef JSON_HAS_CPP_17
#include <cstddef>
#endif
namespace namespace
{ {
@@ -30,13 +26,6 @@ using char_binary_json = nlohmann::basic_json <
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >; double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >;
#ifdef JSON_HAS_CPP_17
// a BinaryType whose value type is not an integer type at all
using byte_binary_json = nlohmann::basic_json <
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
#endif
} // namespace } // namespace
TEST_CASE("binary type whose value type is not std::uint8_t") TEST_CASE("binary type whose value type is not std::uint8_t")
@@ -63,29 +52,4 @@ 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})"); 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
} }
+3 -3
View File
@@ -179,7 +179,7 @@ class no_key_compare_map
} }
}; };
using no_key_compare_json = nlohmann::basic_json<no_key_compare_map>; using no_key_compare_json = nlohmann::json::with_object_t<no_key_compare_map>;
// An ObjectType whose erase(iterator) returns void rather than the following // An ObjectType whose erase(iterator) returns void rather than the following
// iterator, as for instance Abseil's hash maps do // iterator, as for instance Abseil's hash maps do
@@ -196,7 +196,7 @@ struct void_erase_map : std::map<Key, T, Compare, Allocator>
} }
}; };
using void_erase_json = nlohmann::basic_json<void_erase_map>; using void_erase_json = nlohmann::json::with_object_t<void_erase_map>;
// wraps an iterator, but only offers the LegacyForwardIterator operations, // wraps an iterator, but only offers the LegacyForwardIterator operations,
// like the iterators of std::unordered_map and other hash maps // like the iterators of std::unordered_map and other hash maps
@@ -388,7 +388,7 @@ class forward_only_map
} }
}; };
using forward_only_json = nlohmann::basic_json<forward_only_map>; using forward_only_json = nlohmann::json::with_object_t<forward_only_map>;
} // namespace } // namespace
+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
@@ -1306,44 +1306,9 @@ TEST_CASE("deserialization")
CHECK(line == "foo"); 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 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)
// if __cpp_char8_t is defined.
#define TYPE_LIST(...) __VA_ARGS__
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t, char8_t)
#else
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t)
#endif
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, ASCII_TYPES) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
{ {
std::vector<T> const v = {'t', 'r', 'u', 'e'}; std::vector<T> const v = {'t', 'r', 'u', 'e'};
CHECK(json::parse(v) == json(true)); 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_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") 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}; 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 // 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::herz, "herz"},
{strict_cards::karo, "karo"} {strict_cards::karo, "karo"}
}) })
} // namespace enum_keyed_maps_default
namespace namespace
{ {
@@ -82,6 +86,15 @@ struct enum_hash
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1 // see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
TEST_CASE("maps with enum keys") 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_map = std::map<TaskState, std::string>;
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>; using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>; 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-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. The regression below only
// The regression below only showed on C++17, so build this file for every // showed on C++17, and the whole file is just this explicit instantiation, so it
// standard like the other regression tests: // 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; see note at top of file) // JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
+3 -3
View File
@@ -157,13 +157,13 @@ TEST_CASE("hash<nlohmann::json>")
// the ends of the integer ranges, which equal floats exactly // the ends of the integer ranges, which equal floats exactly
const auto int_min = (std::numeric_limits<json::number_integer_t>::min)(); const auto int_min = (std::numeric_limits<json::number_integer_t>::min)();
const auto int_max = (std::numeric_limits<json::number_integer_t>::max)(); const auto int_max = (std::numeric_limits<json::number_integer_t>::max)();
const auto two_63 = json::number_unsigned_t(1) << 63U; const auto two_63 = static_cast<json::number_unsigned_t>(1) << 63U;
CHECK(json(int_min) == json(-9223372036854775808.0)); CHECK(json(int_min) == json(-9223372036854775808.0));
CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0))); CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0)));
CHECK(json(two_63) == json(9223372036854775808.0)); CHECK(json(two_63) == json(9223372036854775808.0));
CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0))); CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0)));
CHECK(json(json::number_unsigned_t(int_max)) == json(int_max)); CHECK(json(static_cast<json::number_unsigned_t>(int_max)) == json(int_max));
CHECK(std::hash<json> {}(json(json::number_unsigned_t(int_max))) == std::hash<json> {}(json(int_max))); CHECK(std::hash<json> {}(json(static_cast<json::number_unsigned_t>(int_max))) == std::hash<json> {}(json(int_max)));
} }
TEST_CASE("hash<nlohmann::ordered_json>") TEST_CASE("hash<nlohmann::ordered_json>")
+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-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-iterators2-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
#if JSON_HAS_RANGES
#include <algorithm>
#include <ranges>
#endif
TEST_CASE("iterators 2") TEST_CASE("iterators 2")
{ {
SECTION("iterator comparisons") 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
} }
+145
View File
@@ -0,0 +1,145 @@
// __ _____ _____ _____
// __| | __| | | | 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
#include <compare>
#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
@@ -752,14 +752,13 @@ TEST_CASE("JSON pointers")
CHECK(ptr.to_string() == "/object/~1"); 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") SECTION("equality comparison")
{ {
std::string ptr_string{"/foo/bar"}; std::string ptr_string{"/foo/bar"};
auto ptr1 = json::json_pointer(ptr_string); auto ptr1 = json::json_pointer(ptr_string);
auto ptr2 = 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); CHECK(ptr1 == ptr2);
@@ -802,6 +801,7 @@ TEST_CASE("JSON pointers")
} }
#endif #endif
} }
#endif
SECTION("less-than comparison") SECTION("less-than comparison")
{ {
@@ -811,12 +811,6 @@ TEST_CASE("JSON pointers")
CHECK(ptr1 < ptr2); CHECK(ptr1 < ptr2);
CHECK_FALSE(ptr2 < ptr1); 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") SECTION("usable as map key")
@@ -872,10 +866,9 @@ TEST_CASE("JSON pointers")
CHECK_FALSE(ptr != ptr_j); CHECK_FALSE(ptr != ptr_j);
CHECK_FALSE(ptr != ptr_oj); CHECK_FALSE(ptr != ptr_oj);
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
SECTION("equality comparison") SECTION("equality comparison")
{ {
// build with C++20 to test rewritten candidates
// JSON_HAS_CPP_20
CHECK(ptr == ptr_j); CHECK(ptr == ptr_j);
CHECK(ptr == ptr_oj); CHECK(ptr == ptr_oj);
@@ -891,6 +884,7 @@ TEST_CASE("JSON pointers")
CHECK_FALSE(ptr_oj != ptr_j); CHECK_FALSE(ptr_oj != ptr_j);
CHECK_FALSE(ptr_oj != ptr); CHECK_FALSE(ptr_oj != ptr);
} }
#endif
} }
SECTION("value(json_pointer, default) with ordered_json #5664") SECTION("value(json_pointer, default) with ordered_json #5664")
@@ -912,19 +906,6 @@ TEST_CASE("JSON pointers")
CHECK(j.value(ptr_missing, 42) == 42); 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") TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
+18 -6
View File
@@ -426,7 +426,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
{ {
for (std::size_t depth = 120; depth <= 140; ++depth) for (std::size_t depth = 120; depth <= 140; ++depth)
{ {
CAPTURE(depth); CAPTURE(depth)
const json array = nested_array(depth, json(7)); const json array = nested_array(depth, json(7));
CHECK(json::from_cbor(json::to_cbor(array)) == array); CHECK(json::from_cbor(json::to_cbor(array)) == array);
@@ -464,7 +464,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2 nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2
}) })
{ {
CAPTURE(depth); CAPTURE(depth)
const json array = nested_array(depth, json(0)); const json array = nested_array(depth, json(0));
std::vector<std::uint8_t> expected_cbor(depth, 0x81); std::vector<std::uint8_t> expected_cbor(depth, 0x81);
@@ -505,10 +505,22 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
const json discarded_leaf(json::value_t::discarded); const json discarded_leaf(json::value_t::discarded);
const json deep_discarded = nested_array(depth, discarded_leaf); const json deep_discarded = nested_array(depth, discarded_leaf);
CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error); // with diagnostics, the message names the path to the discarded leaf
CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error); #if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error); std::string path;
CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error); for (std::size_t i = 0; i < depth; ++i)
{
path += "/0";
}
const std::string prefix = "[json.exception.type_error.321] (" + path + ") ";
#else
const std::string prefix = "[json.exception.type_error.321] ";
#endif
CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), (prefix + "cannot serialize discarded value to CBOR").c_str(), json::type_error);
CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), (prefix + "cannot serialize discarded value to MessagePack").c_str(), json::type_error);
CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), (prefix + "cannot serialize discarded value to UBJSON").c_str(), json::type_error);
CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), (prefix + "cannot serialize discarded value to BJData").c_str(), json::type_error);
} }
SECTION("does not overflow the C++ stack") SECTION("does not overflow the C++ stack")
+3 -3
View File
@@ -172,7 +172,7 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
// a floating-point type that is not a float or a double is written // a floating-point type that is not a float or a double is written
// with snprintf, whose locale-specific decimal point and thousands // with snprintf, whose locale-specific decimal point and thousands
// separator are undone afterwards // separator are undone afterwards
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>; using long_double_json = nlohmann::json::with_float_t<long double>;
CHECK(long_double_json(12345.5L).dump() == "12345.5"); CHECK(long_double_json(12345.5L).dump() == "12345.5");
CHECK(long_double_json(1.0L).dump() == "1.0"); CHECK(long_double_json(1.0L).dump() == "1.0");
CHECK(long_double_json(-0.25L).dump() == "-0.25"); CHECK(long_double_json(-0.25L).dump() == "-0.25");
@@ -272,7 +272,7 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519
} }
text += "]"; text += "]";
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>; using long_double_json = nlohmann::json::with_float_t<long double>;
// reference values, parsed without a locale switch // reference values, parsed without a locale switch
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr); REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
@@ -432,7 +432,7 @@ TEST_CASE("locale changes during a single dump() (#5709 item 3)")
// long double on 64-bit Arm, where it is IEEE-754 double) takes the // long double on 64-bit Arm, where it is IEEE-754 double) takes the
// locale-independent to_chars() path instead, and this test is a no-op // locale-independent to_chars() path instead, and this test is a no-op
// there. // there.
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>; using long_double_json = nlohmann::json::with_float_t<long double>;
using ld_limits = std::numeric_limits<long_double_json::number_float_t>; using ld_limits = std::numeric_limits<long_double_json::number_float_t>;
const bool is_ieee_single_or_double = const bool is_ieee_single_or_double =
(ld_limits::is_iec559 && ld_limits::digits == 24 && ld_limits::max_exponent == 128) || (ld_limits::is_iec559 && ld_limits::digits == 24 && ld_limits::max_exponent == 128) ||
+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 // objects nested `depth` levels deep under the key "a", with members that
// differ by `variant` on the way down // 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; std::string text;
for (std::size_t i = 0; i < depth; ++i) 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) for (int variant = 0; variant < 3; ++variant)
{ {
CAPTURE(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; json expected = result;
result.merge_patch(patch); result.merge_patch(patch);
reference_merge_patch(expected, 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 // applying a patch used to recurse once per nesting level. The result
// is only walked, never copied or compared, since those recurse too. // is only walked, never copied or compared, since those recurse too.
const std::size_t depth = 100000; const std::size_t depth = 100000;
json target = json::parse(nested_objects(depth, 0)); json target = json::parse(nested_objects_merge_patch(depth, 0));
target.merge_patch(json::parse(nested_objects(depth, 1))); target.merge_patch(json::parse(nested_objects_merge_patch(depth, 1)));
const json* p = &target; const json* p = &target;
for (std::size_t i = 0; i < depth; ++i) 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) CAPTURE(depth)
json j = json::parse(nested_objects(depth, 0)); json j = json::parse(nested_objects_merge_patch(depth, 0));
const json expected = j; const json expected = j;
j.merge_patch(j); j.merge_patch(j);
CHECK(j == expected); 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 // objects nested `depth` levels deep under the key "a", with members that
// differ by `variant` on the way down // 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; std::string text;
for (std::size_t i = 0; i < depth; ++i) 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) for (int variant = 0; variant < 3; ++variant)
{ {
CAPTURE(variant) CAPTURE(variant)
const json source = json::parse(nested_objects(depth, variant)); const json source = json::parse(nested_objects_update(depth, variant));
json result = json::parse(nested_objects(depth, (variant + 1) % 3)); json result = json::parse(nested_objects_update(depth, (variant + 1) % 3));
json expected = result; json expected = result;
result.update(source, true); result.update(source, true);
reference_update(expected, source); 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 // merging used to recurse once per nesting level. The result is only
// walked, never copied or compared, since those recurse too. // walked, never copied or compared, since those recurse too.
const std::size_t depth = 100000; const std::size_t depth = 100000;
json target = json::parse(nested_objects(depth, 0)); json target = json::parse(nested_objects_update(depth, 0));
target.update(json::parse(nested_objects(depth, 1)), true); target.update(json::parse(nested_objects_update(depth, 1)), true);
const json* p = &target; const json* p = &target;
for (std::size_t i = 0; i < depth; ++i) 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) CAPTURE(depth)
json j = json::parse(nested_objects(depth, 0)); json j = json::parse(nested_objects_update(depth, 0));
const json expected = j; const json expected = j;
j.update(j, true); j.update(j, true);
CHECK(j == expected); CHECK(j == expected);
+12 -12
View File
@@ -2285,7 +2285,7 @@ TEST_CASE("MessagePack Size above uint32 for object")
} }
#ifdef JSON_TEST_BEYOND_UINT32_STRING #ifdef JSON_TEST_BEYOND_UINT32_STRING
struct huge_string : std::string struct msgpack_huge_string : std::string
{ {
using std::string::string; using std::string::string;
@@ -2295,10 +2295,10 @@ struct huge_string : std::string
} }
}; };
using huge_string_json = nlohmann::basic_json < using msgpack_huge_string_json = nlohmann::basic_json <
std::map, std::map,
std::vector, std::vector,
huge_string, msgpack_huge_string,
bool, bool,
std::int64_t, std::int64_t,
std::uint64_t, std::uint64_t,
@@ -2310,16 +2310,16 @@ using huge_string_json = nlohmann::basic_json <
TEST_CASE("MessagePack Size above uint32 for 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( 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.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&); json::out_of_range&);
} }
#endif #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; using std::vector<std::uint8_t>::vector;
@@ -2329,7 +2329,7 @@ struct huge_binary : std::vector<std::uint8_t>
} }
}; };
using huge_binary_json = nlohmann::basic_json < using msgpack_huge_binary_json = nlohmann::basic_json <
std::map, std::map,
std::vector, std::vector,
std::string, std::string,
@@ -2339,19 +2339,19 @@ using huge_binary_json = nlohmann::basic_json <
double, double,
std::allocator, std::allocator,
nlohmann::adl_serializer, nlohmann::adl_serializer,
huge_binary, msgpack_huge_binary,
void >; void >;
TEST_CASE("MessagePack Size above uint32 for 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(0x01);
j.get_binary().push_back(0x02); j.get_binary().push_back(0x02);
CHECK_THROWS_WITH_AS( 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.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&); json::out_of_range&);
} }
@@ -2432,8 +2432,8 @@ TEST_CASE("MessagePack numbers use the active union member (see #5644)")
// used to read the union member that was not the active one, writing // used to read the union member that was not the active one, writing
// wrong bytes for some values; std::int64_t/std::uint64_t (the default // wrong bytes for some values; std::int64_t/std::uint64_t (the default
// types, where both members have the same width) were not affected // types, where both members have the same width) were not affected
using int32_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint64_t, double>; using int32_json = nlohmann::json::with_integers_t<std::int32_t, std::uint64_t>;
using int16_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int16_t, std::uint64_t, double>; using int16_json = nlohmann::json::with_integers_t<std::int16_t, std::uint64_t>;
SECTION("number_integer_t = std::int32_t") SECTION("number_integer_t = std::int32_t")
{ {
+22 -17
View File
@@ -44,6 +44,8 @@ using nlohmann::ordered_json;
// this type, which is presumably why the gap was never noticed. // this type, which is presumably why the gap was never noticed.
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
namespace unit_ordered_json2_detail
{
class alt_string; class alt_string;
bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage) 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) void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-internal-linkage)
@@ -217,7 +219,7 @@ void int_to_string(alt_string& target, std::size_t value)
target = std::to_string(value).c_str(); target = std::to_string(value).c_str();
} }
using alt_json = nlohmann::basic_json < using ordered_json2_alt_json = nlohmann::basic_json <
std::map, std::map,
std::vector, std::vector,
alt_string, alt_string,
@@ -232,6 +234,9 @@ bool operator<(const char* op1, const alt_string& op2) noexcept
{ {
return op1 < op2.str_impl; return op1 < op2.str_impl;
} }
} // namespace unit_ordered_json2_detail
using unit_ordered_json2_detail::ordered_json2_alt_json;
namespace namespace
{ {
@@ -262,11 +267,11 @@ ordered_json make_rich_ordered_json()
return j; 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["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"]["z_nested"] = true;
j["mango"]["a_nested"] = nullptr; j["mango"]["a_nested"] = nullptr;
j["banana"] = "some text"; j["banana"] = "some text";
@@ -339,47 +344,47 @@ TEST_CASE("ordered_json across binary formats")
TEST_CASE("alt_json (custom string_t) 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") SECTION("CBOR")
{ {
const auto bytes = alt_json::to_cbor(original); const auto bytes = ordered_json2_alt_json::to_cbor(original);
const auto restored = alt_json::from_cbor(bytes); const auto restored = ordered_json2_alt_json::from_cbor(bytes);
CHECK(restored == original); CHECK(restored == original);
} }
SECTION("MessagePack") SECTION("MessagePack")
{ {
const auto bytes = alt_json::to_msgpack(original); const auto bytes = ordered_json2_alt_json::to_msgpack(original);
const auto restored = alt_json::from_msgpack(bytes); const auto restored = ordered_json2_alt_json::from_msgpack(bytes);
CHECK(restored == original); CHECK(restored == original);
} }
SECTION("UBJSON") SECTION("UBJSON")
{ {
const auto bytes = alt_json::to_ubjson(original); const auto bytes = ordered_json2_alt_json::to_ubjson(original);
const auto restored = alt_json::from_ubjson(bytes); const auto restored = ordered_json2_alt_json::from_ubjson(bytes);
CHECK(restored == original); CHECK(restored == original);
} }
SECTION("BON8") SECTION("BON8")
{ {
const auto bytes = alt_json::to_bon8(original); const auto bytes = ordered_json2_alt_json::to_bon8(original);
const auto restored = alt_json::from_bon8(bytes); const auto restored = ordered_json2_alt_json::from_bon8(bytes);
CHECK(restored == original); CHECK(restored == original);
} }
SECTION("BSON") SECTION("BSON")
{ {
const auto bytes = alt_json::to_bson(original); const auto bytes = ordered_json2_alt_json::to_bson(original);
const auto restored = alt_json::from_bson(bytes); const auto restored = ordered_json2_alt_json::from_bson(bytes);
CHECK(restored == original); CHECK(restored == original);
} }
SECTION("BJData") SECTION("BJData")
{ {
const auto bytes = alt_json::to_bjdata(original); const auto bytes = ordered_json2_alt_json::to_bjdata(original);
const auto restored = alt_json::from_bjdata(bytes); const auto restored = ordered_json2_alt_json::from_bjdata(bytes);
CHECK(restored == original); 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("vier") == com.end());
CHECK(com.find(std::string("vier")) == com.end()); CHECK(com.find(std::string("vier")) == com.end());
CHECK(com.find(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") SECTION("insert")
+6 -8
View File
@@ -39,7 +39,7 @@ using nlohmann::json;
template<class K, class V, class dummy_compare, class A> template<class K, class V, class dummy_compare, class A>
using my_workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>; using my_workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
using my_json = nlohmann::basic_json<my_workaround_fifo_map>; using my_json = nlohmann::json::with_object_t<my_workaround_fifo_map>;
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// for #977 // for #977
@@ -86,8 +86,7 @@ struct foo_serializer < T, typename std::enable_if < !std::is_same<foo, T>::valu
}; };
} // namespace ns } // namespace ns
using foo_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, using foo_json = nlohmann::json::with_json_serializer_t<ns::foo_serializer>;
std::uint64_t, double, std::allocator, ns::foo_serializer, std::vector<std::uint8_t>>;
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// for #805 // for #805
@@ -254,7 +253,7 @@ TEST_CASE("regression tests 1")
{ {
// create JSON class with nonstandard integer number type // create JSON class with nonstandard integer number type
using custom_json = using custom_json =
nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float>; nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>;
custom_json j; custom_json j;
j["int_1"] = 1; j["int_1"] = 1;
CHECK(j["int_1"] == 1); CHECK(j["int_1"] == 1);
@@ -470,18 +469,17 @@ TEST_CASE("regression tests 1")
// create JSON class with nonstandard float number type // create JSON class with nonstandard float number type
// float // float
nlohmann::basic_json<std::map, std::vector, std::string, bool, int32_t, uint32_t, float> const j_float = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float> const j_float =
1.23e25f; 1.23e25f;
CHECK(j_float.get<float>() == 1.23e25f); CHECK(j_float.get<float>() == 1.23e25f);
// double // double
nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, double> const j_double = nlohmann::json const j_double =
1.23e35; 1.23e35;
CHECK(j_double.get<double>() == 1.23e35); CHECK(j_double.get<double>() == 1.23e35);
// long double // long double
nlohmann::basic_json<std::map, std::vector, std::string, bool, int64_t, uint64_t, long double> nlohmann::json::with_float_t<long double> const j_long_double = 1.23e45L;
const j_long_double = 1.23e45L;
CHECK(j_long_double.get<long double>() == 1.23e45L); CHECK(j_long_double.get<long double>() == 1.23e45L);
} }
+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
+13 -105
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-regression2-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -49,41 +48,6 @@ using ordered_json = nlohmann::ordered_json;
#include "test_utils.hpp" #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::basic_json<std::map, // ObjectType
std::vector, // ArrayType
std::string, // StringType
bool, // BooleanType
std::int64_t, // NumberIntegerType
std::uint64_t, // NumberUnsignedType
double, // NumberFloatType
std::allocator, // AllocatorType
nlohmann::adl_serializer, // JSONSerializer
std::vector<std::byte>, // BinaryType
void // CustomBaseClass
>;
#endif
#ifdef JSON_HAS_CPP_20
#if __has_include(<span>)
#include <span>
#endif
#endif
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// for #4825 - explicitly instantiating basic_json must compile; this // for #4825 - explicitly instantiating basic_json must compile; this
// forces instantiation of binary_writer::write_bjdata_ndarray, whose // forces instantiation of binary_writer::write_bjdata_ndarray, whose
@@ -92,13 +56,6 @@ using ordered_json = nlohmann::ordered_json;
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
template class nlohmann::basic_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 // NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
@@ -107,7 +64,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
// for #1021 // for #1021
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>; using float_json = nlohmann::json::with_float_t<float>;
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
namespace namespace
@@ -155,10 +112,8 @@ struct failing_allocator : std::allocator<T>
}; };
}; };
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, using failing_json = nlohmann::json::with_allocator_t<failing_allocator>;
std::int64_t, std::uint64_t, double, failing_allocator>; using failing_ordered_json = nlohmann::ordered_json::with_allocator_t<failing_allocator>;
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
std::int64_t, std::uint64_t, double, failing_allocator>;
// builds `depth` levels of nesting around a scalar, iteratively (never // builds `depth` levels of nesting around a scalar, iteratively (never
// recursing: each wrap only moves the previous, already-built value, which // recursing: each wrap only moves the previous, already-built value, which
@@ -298,18 +253,18 @@ struct adl_serializer<NonDefaultConstructible>
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
template<class T> template<class T>
class my_allocator : public std::allocator<T> class my_allocator_2982 : public std::allocator<T>
{ {
public: public:
using std::allocator<T>::allocator; using std::allocator<T>::allocator;
my_allocator() = default; my_allocator_2982() = default;
template<class U> my_allocator(const my_allocator<U>& /*unused*/) { } template<class U> my_allocator_2982(const my_allocator_2982<U>& /*unused*/) { }
template <class U> template <class U>
struct rebind struct rebind
{ {
using other = my_allocator<U>; using other = my_allocator_2982<U>;
}; };
}; };
@@ -481,13 +436,6 @@ TEST_CASE("regression tests 2")
CHECK(diffs.size() == 1); // Note the change here, was 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 " SECTION("issue #1299 - compile error in from_json converting to container "
"with std::pair") "with std::pair")
{ {
@@ -731,26 +679,6 @@ TEST_CASE("regression tests 2")
CHECK(j.dump() == "{}"); 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("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
{ {
SECTION("std::array") SECTION("std::array")
@@ -881,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") 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}; const json j = {1, 2, 3, 4};
json::to_cbor(j, my_vector); json::to_cbor(j, my_vector);
json k = json::from_cbor(my_vector); json k = json::from_cbor(my_vector);
@@ -897,26 +825,6 @@ TEST_CASE("regression tests 2")
CHECK(node.dump(-1, ' ', true, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}"); 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") SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
{ {
const Issue3669Holder h{}; const Issue3669Holder h{};
+171
View File
@@ -0,0 +1,171 @@
// __ _____ _____ _____
// __| | __| | | | 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"
// 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_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 #4804
/////////////////////////////////////////////////////////////////////
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
/////////////////////////////////////////////////////////////////////
// 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 #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
{
const std::vector<std::uint8_t> data = {0x80};
const auto decoded = json_4804::from_cbor(data);
CHECK((decoded == json_4804::array()));
}
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
{
// Test that assigning a custom BinaryType directly creates a binary value, not an array
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
const json_4804 j = original;
CHECK(j.is_binary());
CHECK(!j.is_array());
// Test round-tripping: extracting the binary value back as the custom container type
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted == original);
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
CHECK(default_json.is_array());
CHECK(!default_json.is_binary());
}
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
{
// Test that extracting a custom BinaryType from a parsed JSON array still works
// (not just from a binary-typed node)
const auto j = json_4804::parse("[1,2,3]");
CHECK(j.is_array());
CHECK(!j.is_binary());
// Extracting as custom BinaryType should work from arrays
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted.size() == 3);
CHECK(extracted[0] == std::byte{1});
CHECK(extracted[1] == std::byte{2});
CHECK(extracted[2] == std::byte{3});
}
#endif
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
+6 -253
View File
@@ -6,12 +6,11 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-regression3-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -48,49 +47,8 @@ using ordered_json = nlohmann::ordered_json;
#include <type_traits> #include <type_traits>
#include <utility> #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
/////////////////////////////////////////////////////////////////////
// for #4804
/////////////////////////////////////////////////////////////////////
using json_4804 = nlohmann::basic_json<std::map, // ObjectType
std::vector, // ArrayType
std::string, // StringType
bool, // BooleanType
std::int64_t, // NumberIntegerType
std::uint64_t, // NumberUnsignedType
double, // NumberFloatType
std::allocator, // AllocatorType
nlohmann::adl_serializer, // JSONSerializer
std::vector<std::byte>, // BinaryType
void // CustomBaseClass
>;
#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 // 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 // NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
@@ -205,22 +163,6 @@ inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-inter
tb._from_json(j); 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 // for #3204
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
@@ -284,37 +226,8 @@ struct Example_3810
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage) 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") 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") SECTION("issue #3077 - explicit constructor with default does not compile")
{ {
@@ -365,31 +278,6 @@ TEST_CASE("regression tests 3")
CHECK(td.str == "value"); 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") SECTION("issue #3204 - ambiguous regression")
{ {
const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param) const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
@@ -432,28 +320,6 @@ TEST_CASE("regression tests 3")
CHECK(oj["test"].dump() == expected); 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) #if !defined(_MSVC_LANG)
// MSVC returns garbage on invalid enum values, so this test is excluded // MSVC returns garbage on invalid enum values, so this test is excluded
// there. // there.
@@ -468,119 +334,7 @@ TEST_CASE("regression tests 3")
} }
#endif #endif
#ifdef JSON_HAS_CPP_17
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
{
const std::vector<std::uint8_t> data = {0x80};
const auto decoded = json_4804::from_cbor(data);
CHECK((decoded == json_4804::array()));
}
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
{
// Test that assigning a custom BinaryType directly creates a binary value, not an array
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
const json_4804 j = original;
CHECK(j.is_binary());
CHECK(!j.is_array());
// Test round-tripping: extracting the binary value back as the custom container type
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted == original);
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
CHECK(default_json.is_array());
CHECK(!default_json.is_binary());
}
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
{
// Test that extracting a custom BinaryType from a parsed JSON array still works
// (not just from a binary-typed node)
const auto j = json_4804::parse("[1,2,3]");
CHECK(j.is_array());
CHECK(!j.is_binary());
// Extracting as custom BinaryType should work from arrays
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted.size() == 3);
CHECK(extracted[0] == std::byte{1});
CHECK(extracted[1] == std::byte{2});
CHECK(extracted[2] == std::byte{3});
}
#endif
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 // 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) 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)
@@ -925,12 +679,11 @@ TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with
CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}})); CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}}));
} }
TEST_CASE("regression test #5476 - array type without reserve()") TEST_CASE("regression test #5476 - array type without reserve()")
{ {
// the capacity reserved for definite-length arrays must not require the // the capacity reserved for definite-length arrays must not require the
// array type to have a reserve() member function // array type to have a reserve() member function
using deque_json = nlohmann::basic_json<std::map, std::deque>; using deque_json = nlohmann::json::with_array_t<std::deque>;
SECTION("std::deque") SECTION("std::deque")
{ {
+1 -2
View File
@@ -367,8 +367,7 @@ TEST_CASE("dump for basic_json with long double number_float_t")
// serializer::dump_float(x, std::false_type). That branch must use the // serializer::dump_float(x, std::false_type). That branch must use the
// "%.*Lg" format specifier; using "%.*g" with a long double argument is // "%.*Lg" format specifier; using "%.*g" with a long double argument is
// undefined behavior and corrupts the output. // undefined behavior and corrupts the output.
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, using long_double_json = nlohmann::json::with_float_t<long double>;
bool, std::int64_t, std::uint64_t, long double>;
SECTION("round-trip dump/parse") SECTION("round-trip dump/parse")
{ {
+4 -7
View File
@@ -6,12 +6,10 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. This whole file tests
// When using macros that are only defined for particular versions of the standard // std::format (C++20), so it cannot be split: it is built for C++20 as well:
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // JSON_HAS_CPP_20 (do not remove)
// version macro in a comment close by, like this:
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -19,7 +17,6 @@
using json = nlohmann::json; using json = nlohmann::json;
using ordered_json = nlohmann::ordered_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 #if JSON_HAS_STD_FORMAT
#include <iterator> #include <iterator>
+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-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a // cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. // source file mentions the corresponding version macro. To avoid rebuilding this
// When using macros that are only defined for particular versions of the standard // large file for every standard, tests that depend on the standard version (e.g.,
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding // those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
// version macro in a comment close by, like this: // go into a separate file unit-user_defined_input-cpp<NN>.cpp. This file stays C++11-only.
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
@@ -25,10 +24,6 @@ using nlohmann::json;
#include <string> // string #include <string> // string
#include <vector> // vector #include <vector> // vector
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
#include <iterator>
#endif
namespace namespace
{ {
TEST_CASE("Use arbitrary stdlib container") 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})); 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 } // namespace