From bdc21ad1a713f08241003daf77f40450a5bcfa1b Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Sun, 1 May 2022 22:46:45 +0200 Subject: [PATCH 01/23] Add build step for ICPC (with fixes) (#3465) * :arrow_up: Doctest 2.4.7 * :construction_worker: add CI step for ICPC * :construction_worker: add CI step for ICPC * :construction_worker: add CI step for ICPC * :arrow_down: downgrade to Doctest 2.4.6 * :construction_worker: add CI step for ICPC * :construction_worker: add CI step for ICPC * :construction_worker: add CI step for ICPC * :construction_worker: add CI step for ICPC * :construction_worker: add CI step for ICPC * :mute: suppress warning #2196: routine is both "inline" and "noinline" * Re-enable detection on ICPC * Limit regression test for #3070 to Clang and GCC >=8.4 * Disable deprecation warnings on ICPC * Disable regression test for #1647 on ICPC (C++20) * Fix compilation failure of regression test for #3077 on ICPC * Disable wstring unit test on ICPC Fixes: error 913: invalid multibyte character sequence * Add ICPC to README Co-authored-by: Niels Lohmann --- .github/workflows/ubuntu.yml | 12 ++++++++++ README.md | 25 ++++++++++---------- cmake/ci.cmake | 15 ++++++++++++ include/nlohmann/detail/meta/type_traits.hpp | 9 ++++++- single_include/nlohmann/json.hpp | 9 ++++++- tests/CMakeLists.txt | 4 ++++ tests/src/unit-regression2.cpp | 7 ++++-- tests/src/unit-wstring.cpp | 3 +++ 8 files changed, 68 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index b5f583367..4d037e060 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -112,3 +112,15 @@ jobs: run: cmake -S . -B build -DJSON_CI=On - name: build run: cmake --build build --target ci_cuda_example + + ci_icpc: + runs-on: ubuntu-latest + container: ghcr.io/nlohmann/json-ci:v2.2.0 + steps: + - uses: actions/checkout@v2 + - name: cmake + run: cmake -S . -B build -DJSON_CI=On + - name: build + run: | + . /opt/intel/oneapi/setvars.sh + cmake --build build --target ci_icpc diff --git a/README.md b/README.md index a46a8569c..3f61377ba 100644 --- a/README.md +++ b/README.md @@ -1083,18 +1083,6 @@ The following compilers are currently used in continuous integration at [AppVeyo | Apple Clang 12.0.0 (clang-1200.0.32.27); Xcode 12.2 | macOS 10.15.7 | GitHub Actions | | Apple Clang 12.0.0 (clang-1200.0.32.28); Xcode 12.3 | macOS 10.15.7 | GitHub Actions | | Apple Clang 12.0.0 (clang-1200.0.32.29); Xcode 12.4 | macOS 10.15.7 | GitHub Actions | -| GCC 4.8.5 (Ubuntu 4.8.5-4ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 4.9.3 (Ubuntu 4.9.3-13ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.12) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 6.4.0 (Ubuntu 6.4.0-17ubuntu1) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 7.5.0 (Ubuntu 7.5.0-6ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project) | Windows-10.0.17763 | GitHub Actions | -| GCC 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project) | Windows-10.0.17763 | GitHub Actions | -| GCC 8.4.0 (Ubuntu 8.4.0-3ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 10.2.0 (Ubuntu 10.2.0-5ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | -| GCC 11.1.0 | Ubuntu (aarch64) | Drone CI | -| GCC 12.0.0 20211219 (experimental) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 3.5.2 (3.5.2-3ubuntu1) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 3.6.2 (3.6.2-3ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 3.7.1 (3.7.1-2ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | @@ -1115,6 +1103,19 @@ The following compilers are currently used in continuous integration at [AppVeyo | Clang 13.0.1 (13.0.1-++20211015123032+cf15ccdeb6d5-1exp120211015003613.5) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 14.0.0 (14.0.0-++20211221052852+55c71c9eac9b-1exp120211221172954.95) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 15.0.0 (15.0.0-++20220403052648+896770c9a92e-1~exp1~20220403172744.209) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 4.8.5 (Ubuntu 4.8.5-4ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 4.9.3 (Ubuntu 4.9.3-13ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.12) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 6.4.0 (Ubuntu 6.4.0-17ubuntu1) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 7.5.0 (Ubuntu 7.5.0-6ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project) | Windows-10.0.17763 | GitHub Actions | +| GCC 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project) | Windows-10.0.17763 | GitHub Actions | +| GCC 8.4.0 (Ubuntu 8.4.0-3ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 10.2.0 (Ubuntu 10.2.0-5ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 11.1.0 | Ubuntu (aarch64) | Drone CI | +| GCC 12.0.0 20211219 (experimental) | Ubuntu 20.04.3 LTS | GitHub Actions | +| Intel C++ Compiler 2021.5.0.20211109 | Ubuntu 20.04.3 LTS | GitHub Actions | | NVCC 11.0.221 | Ubuntu 20.04.3 LTS | GitHub Actions | | Visual Studio 14 2015 MSVC 19.0.24241.7 (Build Engine version 14.0.25420.1) | Windows-6.3.9600 | AppVeyor | | Visual Studio 15 2017 MSVC 19.16.27035.0 (Build Engine version 15.9.21+g9802d43bc3 for .NET Framework) | Windows-10.0.14393 | AppVeyor | diff --git a/cmake/ci.cmake b/cmake/ci.cmake index b2a2f470f..56a80c066 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -877,6 +877,21 @@ add_custom_target(ci_cuda_example COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_cuda_example ) +############################################################################### +# Intel C++ Compiler +############################################################################### + +add_custom_target(ci_icpc + COMMAND ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja + -DCMAKE_C_COMPILER=icc -DCMAKE_CXX_COMPILER=icpc + -DJSON_BuildTests=ON -DJSON_FastTests=ON + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpc + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpc + COMMAND cd ${PROJECT_BINARY_DIR}/build_icpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure + COMMENT "Compile and test with ICPC" +) + ############################################################################### # Clean up all generated files. ############################################################################### diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 2cc13f3ac..5e3ea3737 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -347,8 +347,15 @@ struct is_compatible_string_type template struct is_constructible_string_type { + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + static constexpr auto value = - is_constructible::value; }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index ad777c92c..949ac14be 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3482,8 +3482,15 @@ struct is_compatible_string_type template struct is_constructible_string_type { + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + static constexpr auto value = - is_constructible::value; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1d5c78697..75bc6f980 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,8 +49,12 @@ target_compile_options(test_main PUBLIC # https://github.com/nlohmann/json/issues/1114 $<$:/bigobj> $<$:-Wa,-mbig-obj> + # https://github.com/nlohmann/json/pull/3229 + $<$:-diag-disable=2196> + $<$>:-Wno-deprecated;-Wno-float-equal> $<$:-Wno-deprecated-declarations> + $<$:-diag-disable=1786> ) target_include_directories(test_main PUBLIC thirdparty/doctest diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index e034593ed..04cbac679 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -512,12 +512,15 @@ TEST_CASE("regression tests 2") SECTION("issue #1647 - compile error when deserializing enum if both non-default from_json and non-member operator== exists for other type") { + // does not compile on ICPC when targeting C++20 +#if !(defined(__INTEL_COMPILER) && __cplusplus >= 202000) { json j; NonDefaultFromJsonStruct x(j); NonDefaultFromJsonStruct y; CHECK(x == y); } +#endif auto val = nlohmann::json("one").get(); CHECK(val == for_1647::one); @@ -793,8 +796,8 @@ TEST_CASE("regression tests 2") const auto j_path = j.get(); CHECK(j_path == text_path); -#if !defined(_MSC_VER) && !(defined(__GNUC__) && __GNUC__ == 8 && __GNUC_MINOR__ < 4) - // works everywhere but on MSVC and GCC <8.4 +#if defined(__clang__) || ((defined(__GNUC__) && !defined(__INTEL_COMPILER)) && (__GNUC__ > 8 || (__GNUC__ == 8 && __GNUC_MINOR__ >= 4))) + // 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 } diff --git a/tests/src/unit-wstring.cpp b/tests/src/unit-wstring.cpp index d15ac8491..fa655988c 100644 --- a/tests/src/unit-wstring.cpp +++ b/tests/src/unit-wstring.cpp @@ -32,6 +32,8 @@ SOFTWARE. #include using nlohmann::json; +// ICPC errors out on multibyte character sequences in source files +#ifndef __INTEL_COMPILER namespace { bool wstring_is_utf16(); @@ -115,3 +117,4 @@ TEST_CASE("wide strings") } } } +#endif From b205361d8652759b6d850a37b227c8d57ee19005 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Sun, 1 May 2022 16:47:06 -0400 Subject: [PATCH 02/23] Handle invalid BJData optimized type, fix #3461 (#3463) * Handle invalid BJData optimized type, fix #3461 * Update unit test to handle bjdata optimized array type error --- .../nlohmann/detail/input/binary_reader.hpp | 7 ++ single_include/nlohmann/json.hpp | 7 ++ tests/src/unit-bjdata.cpp | 93 +++++++++++++------ 3 files changed, 77 insertions(+), 30 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 6474b8b05..75a20ed7b 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2178,6 +2178,13 @@ class binary_reader std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (JSON_HEDLEY_UNLIKELY( input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() )) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type") || (input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() ))) { return false; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 949ac14be..bbd84d2be 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10652,6 +10652,13 @@ class binary_reader std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (JSON_HEDLEY_UNLIKELY( input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() )) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type") || (input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() ))) { return false; diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index bc6c52833..dc2c63122 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2408,34 +2408,6 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type); CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size); } - - SECTION("do not accept NTFZ markers in ndarray optimized type") - { - json _; - std::vector v_N = {'[', '$', 'N', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; - std::vector v_T = {'[', '$', 'T', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; - std::vector v_F = {'[', '$', 'F', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; - std::vector v_Z = {'[', '$', 'Z', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; - - CHECK(json::from_bjdata(v_N, true, true).is_discarded()); - CHECK(json::from_bjdata(v_T, true, true).is_discarded()); - CHECK(json::from_bjdata(v_F, true, true).is_discarded()); - CHECK(json::from_bjdata(v_Z, true, true).is_discarded()); - } - - SECTION("do not accept NTFZ markers in ndarray optimized type") - { - json _; - std::vector v_N = {'[', '$', 'N', '#', '[', 'i', 1, 'i', 2, ']'}; - std::vector v_T = {'[', '$', 'T', '#', '[', 'i', 1, 'i', 2, ']'}; - std::vector v_F = {'[', '$', 'F', '#', '[', 'i', 1, 'i', 2, ']'}; - std::vector v_Z = {'[', '$', 'Z', '#', '[', 'i', 1, 'i', 2, ']'}; - - CHECK(json::from_bjdata(v_N, true, true).is_discarded()); - CHECK(json::from_bjdata(v_T, true, true).is_discarded()); - CHECK(json::from_bjdata(v_F, true, true).is_discarded()); - CHECK(json::from_bjdata(v_Z, true, true).is_discarded()); - } } } @@ -2515,6 +2487,56 @@ TEST_CASE("BJData") CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02"); } + + SECTION("do not accept NTFZ markers in ndarray optimized type") + { + json _; + std::vector v_N = {'[', '$', 'N', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + std::vector v_T = {'[', '$', 'T', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + std::vector v_F = {'[', '$', 'F', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + std::vector v_Z = {'[', '$', 'Z', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; + + CHECK_THROWS_AS(_ = json::from_bjdata(v_N), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_N, true, false).is_discarded()); + + CHECK_THROWS_AS(_ = json::from_bjdata(v_T), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_T, true, false).is_discarded()); + + CHECK_THROWS_AS(_ = json::from_bjdata(v_F), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_F, true, false).is_discarded()); + + CHECK_THROWS_AS(_ = json::from_bjdata(v_Z), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_Z, true, false).is_discarded()); + } + + SECTION("do not accept NTFZ markers in ndarray optimized type") + { + json _; + std::vector v_N = {'[', '$', 'N', '#', '[', 'i', 1, 'i', 2, ']'}; + std::vector v_T = {'[', '$', 'T', '#', '[', 'i', 1, 'i', 2, ']'}; + std::vector v_F = {'[', '$', 'F', '#', '[', 'i', 1, 'i', 2, ']'}; + std::vector v_Z = {'[', '$', 'Z', '#', '[', 'i', 1, 'i', 2, ']'}; + + CHECK_THROWS_AS(_ = json::from_bjdata(v_N), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_N, true, false).is_discarded()); + + CHECK_THROWS_AS(_ = json::from_bjdata(v_T), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_T, true, false).is_discarded()); + + CHECK_THROWS_AS(_ = json::from_bjdata(v_F), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_F, true, false).is_discarded()); + + CHECK_THROWS_AS(_ = json::from_bjdata(v_Z), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type"); + CHECK(json::from_bjdata(v_Z, true, false).is_discarded()); + } } SECTION("strings") @@ -2626,6 +2648,11 @@ TEST_CASE("BJData") CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input"); CHECK(json::from_bjdata(vU, true, false).is_discarded()); + + std::vector v1 = {'[', '$', '['}; + CHECK_THROWS_AS(_ = json::from_bjdata(v1), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v1), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5B is not a permitted optimized array type"); + CHECK(json::from_bjdata(v1, true, false).is_discarded()); } SECTION("arrays") @@ -3188,14 +3215,20 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") { SECTION("Array") { + json _; std::vector v = {'[', '$', 'N', '#', 'I', 0x00, 0x02}; - CHECK(json::from_bjdata(v, true, true).is_discarded()); + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type"); + CHECK(json::from_bjdata(v, true, false).is_discarded()); } SECTION("Object") { + json _; std::vector v = {'{', '$', 'Z', '#', 'i', 3, 'i', 4, 'n', 'a', 'm', 'e', 'i', 8, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 'i', 5, 'e', 'm', 'a', 'i', 'l'}; - CHECK(json::from_bjdata(v, true, true).is_discarded()); + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type"); + CHECK(json::from_bjdata(v, true, false).is_discarded()); } } } From d6efe672b577098623f7a9ea28ccb777d04aa0cf Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 9 May 2022 08:02:41 +0200 Subject: [PATCH 03/23] Document fuzzer usage (#3478) * :memo: document fuzzer usage * :memo: address review comments --- .gitignore | 4 +++ tests/fuzzing.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 tests/fuzzing.md diff --git a/.gitignore b/.gitignore index db6dcecb4..72eb2c225 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,10 @@ # build directories (vscode-cmake-tools, user-defined, ...) /build*/ +# fuzzers +/tests/parse_*_fuzzer +/tests/corpus_* + /docs/mkdocs/docs/examples/ /docs/mkdocs/docs/__pycache__/ /docs/mkdocs/site/ diff --git a/tests/fuzzing.md b/tests/fuzzing.md new file mode 100644 index 000000000..cfbf4f249 --- /dev/null +++ b/tests/fuzzing.md @@ -0,0 +1,81 @@ +# Fuzz testing + +Each parser of the library (JSON, BJData, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently, +[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and [afl++](https://github.com/AFLplusplus/AFLplusplus) are supported. + +## Corpus creation + +For most effective fuzzing, a [corpus](https://llvm.org/docs/LibFuzzer.html#corpus) should be provided. A corpus is a +directory with some simple input files that cover several features of the parser and is hence a good starting point +for mutations. + +```shell +TEST_DATA_VERSION=3.1.0 +wget https://github.com/nlohmann/json_test_data/archive/refs/tags/v$TEST_DATA_VERSION.zip +unzip v$TEST_DATA_VERSION.zip +rm v$TEST_DATA_VERSION.zip +for FORMAT in json bjdata bson cbor msgpack ubjson +do + rm -fr corpus_$FORMAT + mkdir corpus_$FORMAT + find json_test_data-$TEST_DATA_VERSION -size -5k -name "*.$FORMAT" -exec cp "{}" "corpus_$FORMAT" \; +done +rm -fr json_test_data-$TEST_DATA_VERSION +``` + +The generated corpus can be used with both libFuzzer and afl++. The remainder of this documentation assumes the corpus +directories have been created in the `tests` directory. + +## libFuzzer + +To use libFuzzer, you need to pass `-fsanitize=fuzzer` as `FUZZER_ENGINE`. In the `tests` directory, call + +```shell +make fuzzers FUZZER_ENGINE="-fsanitize=fuzzer" +``` + +This creates a fuzz tester binary for each parser that supports these +[command line options](https://llvm.org/docs/LibFuzzer.html#options). + +In case your default compiler is not a Clang compiler that includes libFuzzer (Clang 6.0 or later), you need to set the +`CXX` variable accordingly. Note the compiler provided by Xcode (AppleClang) does not contain libFuzzer. Please install +Clang via Homebrew calling `brew install llvm` and add `CXX=$(brew --prefix llvm)/bin/clang` to the `make` call: + +```shell +make fuzzers FUZZER_ENGINE="-fsanitize=fuzzer" CXX=$(brew --prefix llvm)/bin/clang +``` + +Then pass the corpus directory as command-line argument (assuming it is located in `tests`): + +```shell +./parse_cbor_fuzzer corpus_cbor +``` + +The fuzzer should be able to run indefinitely without crashing. In case of a crash, the tested input is dumped into +a file starting with `crash-`. + +## afl++ + +To use afl++, you need to pass `-fsanitize=fuzzer` as `FUZZER_ENGINE`. It will be replaced by a `libAFLDriver.a` to +re-use the same code written for libFuzzer with afl++. Furthermore, set `afl-clang-fast++` as compiler. + +```shell +CXX=afl-clang-fast++ make fuzzers FUZZER_ENGINE="-fsanitize=fuzzer" +``` + +Then the fuzzer is called like this in the `tests` directory: + +```shell +afl-fuzz -i corpus_cbor -o out -- ./parse_cbor_fuzzer +``` + +The fuzzer should be able to run indefinitely without crashing. In case of a crash, the tested input is written to the +directory `out`. + +## OSS-Fuzz + +The library is further fuzz-tested 24/7 by Google's [OSS-Fuzz project](https://github.com/google/oss-fuzz). It uses +the same `fuzzers` target as above and also relies on the `FUZZER_ENGINE` variable. See the used +[build script](https://github.com/google/oss-fuzz/blob/master/projects/json/build.sh) for more information. + +In case the build at OSS-Fuzz fails, an issue will be created automatically. From a8a547d7a212a6a39943bbd5b4220be504a1a33e Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Tue, 10 May 2022 15:13:24 -0400 Subject: [PATCH 04/23] change bjdata ndarray flag to detect negative size, as part of #3475 (#3479) * change bjdata ndarray flag to detect negative size, fix https://github.com/nlohmann/json/issues/3475 * fix CI error * fix CI on 32bit windows * remove platform specific out_of_range error messages * Incorporate suggestions from @nlohmann and @falbrechtskirchinger * fix CI errors * add coverage * fix sax event order * fix coverage --- .../nlohmann/detail/input/binary_reader.hpp | 55 ++++++++++---- single_include/nlohmann/json.hpp | 55 ++++++++++---- tests/src/unit-bjdata.cpp | 76 ++++++++++++++++--- 3 files changed, 144 insertions(+), 42 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 75a20ed7b..703e6c0f6 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1938,6 +1938,7 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; + bool is_ndarray = false; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { @@ -1952,7 +1953,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, size_and_type.second))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, size_and_type.second))) { return false; } @@ -1964,7 +1965,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray))) { return false; } @@ -1976,7 +1977,7 @@ class binary_reader { while (current != ']') { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, current))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, current))) { return false; } @@ -1991,8 +1992,9 @@ class binary_reader @param[out] result determined size @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result, char_int_type prefix = 0) + bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) { + is_ndarray = false; if (prefix == 0) { prefix = get_ignore_noop(); @@ -2132,7 +2134,7 @@ class binary_reader return false; } } - result |= (1ull << (sizeof(result) * 8 - 1)); // low 63 bit of result stores the total element count, sign-bit indicates ndarray + is_ndarray = true; return sax->end_array(); } result = 0; @@ -2168,6 +2170,7 @@ class binary_reader */ bool get_ubjson_size_type(std::pair& result) { + bool is_ndarray = false; result.first = string_t::npos; // size result.second = 0; // type @@ -2185,7 +2188,7 @@ class binary_reader exception_message(input_format, concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); } - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type") || (input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() ))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type"))) { return false; } @@ -2202,12 +2205,22 @@ class binary_reader exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); } - return get_ubjson_size_value(result.first); + bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + } + return is_error; } if (current == '#') { - return get_ubjson_size_value(result.first); + bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + } + return is_error; } return true; @@ -2408,17 +2421,26 @@ class binary_reader return false; } - // detect and encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): + // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]} - if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && (size_and_type.second & (1 << 8)) != 0) { std::map bjdtype = {{'U', "uint8"}, {'i', "int8"}, {'u', "uint16"}, {'I', "int16"}, {'m', "uint32"}, {'l', "int32"}, {'M', "uint64"}, {'L', "int64"}, {'d', "single"}, {'D', "double"}, {'C', "char"} }; + size_and_type.second &= ~(static_cast(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker + string_t key = "_ArrayType_"; - if (JSON_HEDLEY_UNLIKELY(bjdtype.count(size_and_type.second) == 0 || !sax->key(key) || !sax->string(bjdtype[size_and_type.second]) )) + if (JSON_HEDLEY_UNLIKELY(bjdtype.count(size_and_type.second) == 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(bjdtype[size_and_type.second]) )) { return false; } @@ -2428,7 +2450,6 @@ class binary_reader size_and_type.second = 'U'; } - size_and_type.first &= ~(1ull << (sizeof(std::size_t) * 8 - 1)); key = "_ArrayData_"; if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) )) { @@ -2508,9 +2529,12 @@ class binary_reader return false; } - if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + // do not accept ND-array size in objects in BJData + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && (size_and_type.second & (1 << 8)) != 0) { - return false; + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "BJData object does not support ND-array size in optimized format", "object"), nullptr)); } string_t key; @@ -2584,7 +2608,8 @@ class binary_reader { // get size of following number string std::size_t size{}; - auto res = get_ubjson_size_value(size); + bool is_ndarray = false; + auto res = get_ubjson_size_value(size, is_ndarray); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index bbd84d2be..a29f7bce9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10412,6 +10412,7 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; + bool is_ndarray = false; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { @@ -10426,7 +10427,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, size_and_type.second))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, size_and_type.second))) { return false; } @@ -10438,7 +10439,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray))) { return false; } @@ -10450,7 +10451,7 @@ class binary_reader { while (current != ']') { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, current))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, current))) { return false; } @@ -10465,8 +10466,9 @@ class binary_reader @param[out] result determined size @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result, char_int_type prefix = 0) + bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) { + is_ndarray = false; if (prefix == 0) { prefix = get_ignore_noop(); @@ -10606,7 +10608,7 @@ class binary_reader return false; } } - result |= (1ull << (sizeof(result) * 8 - 1)); // low 63 bit of result stores the total element count, sign-bit indicates ndarray + is_ndarray = true; return sax->end_array(); } result = 0; @@ -10642,6 +10644,7 @@ class binary_reader */ bool get_ubjson_size_type(std::pair& result) { + bool is_ndarray = false; result.first = string_t::npos; // size result.second = 0; // type @@ -10659,7 +10662,7 @@ class binary_reader exception_message(input_format, concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); } - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type") || (input_format == input_format_t::bjdata && std::find(bjdx.begin(), bjdx.end(), result.second) != bjdx.end() ))) + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type"))) { return false; } @@ -10676,12 +10679,22 @@ class binary_reader exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); } - return get_ubjson_size_value(result.first); + bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + } + return is_error; } if (current == '#') { - return get_ubjson_size_value(result.first); + bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + } + return is_error; } return true; @@ -10882,17 +10895,26 @@ class binary_reader return false; } - // detect and encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): + // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]} - if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && (size_and_type.second & (1 << 8)) != 0) { std::map bjdtype = {{'U', "uint8"}, {'i', "int8"}, {'u', "uint16"}, {'I', "int16"}, {'m', "uint32"}, {'l', "int32"}, {'M', "uint64"}, {'L', "int64"}, {'d', "single"}, {'D', "double"}, {'C', "char"} }; + size_and_type.second &= ~(static_cast(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker + string_t key = "_ArrayType_"; - if (JSON_HEDLEY_UNLIKELY(bjdtype.count(size_and_type.second) == 0 || !sax->key(key) || !sax->string(bjdtype[size_and_type.second]) )) + if (JSON_HEDLEY_UNLIKELY(bjdtype.count(size_and_type.second) == 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(bjdtype[size_and_type.second]) )) { return false; } @@ -10902,7 +10924,6 @@ class binary_reader size_and_type.second = 'U'; } - size_and_type.first &= ~(1ull << (sizeof(std::size_t) * 8 - 1)); key = "_ArrayData_"; if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) )) { @@ -10982,9 +11003,12 @@ class binary_reader return false; } - if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && size_and_type.first >= (1ull << (sizeof(std::size_t) * 8 - 1))) + // do not accept ND-array size in objects in BJData + if (input_format == input_format_t::bjdata && size_and_type.first != string_t::npos && (size_and_type.second & (1 << 8)) != 0) { - return false; + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "BJData object does not support ND-array size in optimized format", "object"), nullptr)); } string_t key; @@ -11058,7 +11082,8 @@ class binary_reader { // get size of following number string std::size_t size{}; - auto res = get_ubjson_size_value(size); + bool is_ndarray = false; + auto res = get_ubjson_size_value(size, is_ndarray); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index dc2c63122..d2fa9a9b2 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -1130,7 +1130,7 @@ TEST_CASE("BJData") { json j = json::from_bjdata(std::vector({'h', 0x00, 0x7c})); json::number_float_t d{j}; - CHECK(!std::isfinite(d)); + CHECK_FALSE(std::isfinite(d)); CHECK(j.dump() == "null"); } @@ -2035,77 +2035,98 @@ TEST_CASE("BJData") { std::vector v = {'[', 'T', 'F', ']'}; SaxCountdown scp(0); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("start_object()") { std::vector v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'}; SaxCountdown scp(0); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("key() in object") { std::vector v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'}; SaxCountdown scp(1); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("start_array(len)") { std::vector v = {'[', '#', 'i', '2', 'T', 'F'}; SaxCountdown scp(0); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("start_object(len)") { std::vector v = {'{', '#', 'i', '1', 3, 'f', 'o', 'o', 'F'}; SaxCountdown scp(0); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("key() in object with length") { std::vector v = {'{', 'i', 3, 'f', 'o', 'o', 'F', '}'}; SaxCountdown scp(1); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("start_array() in ndarray _ArraySize_") { std::vector v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2}; SaxCountdown scp(2); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("number_integer() in ndarray _ArraySize_") { std::vector v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2}; SaxCountdown scp(3); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("key() in ndarray _ArrayType_") + { + std::vector v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4}; + SaxCountdown scp(6); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("string() in ndarray _ArrayType_") + { + std::vector v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4}; + SaxCountdown scp(7); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("key() in ndarray _ArrayData_") { std::vector v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4}; SaxCountdown scp(8); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); + } + + SECTION("string() in ndarray _ArrayData_") + { + std::vector v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4}; + SaxCountdown scp(9); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("string() in ndarray _ArrayType_") { std::vector v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 3, 2, 6, 5, 4, 3, 2, 1}; SaxCountdown scp(11); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } SECTION("start_array() in ndarray _ArrayData_") { std::vector v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1}; SaxCountdown scp(13); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::bjdata)); + CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata)); } } @@ -2488,6 +2509,37 @@ TEST_CASE("BJData") CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02"); } + SECTION("optimized array: negative size") + { + std::vector v1 = {'[', '#', 'i', 0xF1}; + std::vector v2 = {'[', '$', 'I', '#', 'i', 0xF2}; + std::vector v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'}; + std::vector v4 = {'[', '$', 0xF6, '#', 'i', 0xF7}; + std::vector v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'}; + std::vector v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'}; + + json _; + static bool is_64bit = (sizeof(size_t) == 8); + + if (is_64bit) + { + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.out_of_range.408] excessive array size: 18446744073709551601", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.out_of_range.408] excessive array size: 18446744073709551602", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.out_of_range.408] excessive array size: 18446744073709551592", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v4), "[json.exception.out_of_range.408] excessive array size: 18446744073709551607", json::out_of_range&); + } + else + { + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.out_of_range.408] excessive array size: 4294967281", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.out_of_range.408] excessive array size: 4294967282", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.out_of_range.408] excessive array size: 4294967272", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v4), "[json.exception.out_of_range.408] excessive array size: 4294967287", json::out_of_range&); + } + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v5), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); + + CHECK(json::from_bjdata(v6, true, false).is_discarded()); + } + SECTION("do not accept NTFZ markers in ndarray optimized type") { json _; From 6a7392058e69956d610acefa7e3ce1cd60c8f5a6 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 17 May 2022 13:08:56 +0200 Subject: [PATCH 05/23] Complete documentation for 3.11.0 (#3464) * :busts_in_silhouette: update contributor and sponsor list * :construction: document BJData format * :construction: document BJData format * :memo: clarified documentation of [json.exception.parse_error.112] * :pencil2: adjust titles * :memo: add more examples * :rotating_light: adjust warnings for index.md files * :memo: add more examples * :fire: remove example for deprecated code * :memo: add missing enum entry * :memo: overwork table for binary formats * :white_check_mark: add test to create table for binary formats * :memo: fix wording in example * :memo: add more examples * Update iterators.md (#3481) * :sparkles: add check for overloads to linter #3455 * :busts_in_silhouette: update contributor list * :memo: add more examples * :memo: fix documentation * :memo: add more examples * :art: fix indentation * :fire: remove example for destructor * :memo: overwork documentation * Updated BJData documentation, #3464 (#3493) * update bjdata.md for #3464 * Minor edit * Fix URL typo * Add info on demoting ND array to a 1-D optimized array when singleton dimension Co-authored-by: Chaoqi Zhang Co-authored-by: Qianqian Fang --- README.md | 22 +- ...h_subtype__byte_container_with_subtype.cpp | 23 ++ ...ubtype__byte_container_with_subtype.output | 3 + ..._container_with_subtype__clear_subtype.cpp | 21 ++ ...ntainer_with_subtype__clear_subtype.output | 2 + ...te_container_with_subtype__has_subtype.cpp | 19 ++ ...container_with_subtype__has_subtype.output | 2 + ...te_container_with_subtype__set_subtype.cpp | 22 ++ ...container_with_subtype__set_subtype.output | 2 + .../byte_container_with_subtype__subtype.cpp | 22 ++ ...yte_container_with_subtype__subtype.output | 2 + docs/examples/cbor_tag_handler_t.cpp | 28 +++ docs/examples/cbor_tag_handler_t.output | 3 + docs/examples/default_object_comparator_t.cpp | 11 + .../default_object_comparator_t.output | 2 + docs/examples/error_handler_t.cpp | 24 ++ docs/examples/error_handler_t.output | 3 + docs/examples/from_bjdata.cpp | 20 ++ docs/examples/from_bjdata.output | 4 + docs/examples/get_allocator.cpp | 18 ++ docs/examples/get_allocator.output | 1 + docs/examples/iterator_wrapper.cpp | 29 --- docs/examples/iterator_wrapper.output | 7 - .../json_pointer__operator_string.cpp | 19 ++ .../json_pointer__operator_string.output | 2 + docs/examples/json_pointer__string_t.cpp | 13 + docs/examples/json_pointer__string_t.output | 2 + docs/examples/json_pointer__to_string.cpp | 1 - docs/examples/nlohmann_json_version.cpp | 12 + docs/examples/nlohmann_json_version.output | 1 + docs/examples/object_comparator_t.cpp | 11 + docs/examples/object_comparator_t.output | 2 + docs/examples/ordered_json.cpp | 14 ++ docs/examples/ordered_json.output | 5 + docs/examples/sax_parse.cpp | 37 +-- docs/examples/sax_parse.output | 39 ++- docs/examples/sax_parse__binary.cpp | 114 +++++++++ docs/examples/sax_parse__binary.output | 3 + docs/examples/to_bjdata.cpp | 63 +++++ docs/examples/to_bjdata.output | 4 + docs/mkdocs/docs/api/basic_json/at.md | 1 + docs/mkdocs/docs/api/basic_json/basic_json.md | 2 +- .../docs/api/basic_json/cbor_tag_handler_t.md | 17 ++ docs/mkdocs/docs/api/basic_json/contains.md | 4 +- .../basic_json/default_object_comparator_t.md | 16 ++ .../docs/api/basic_json/error_handler_t.md | 17 ++ .../mkdocs/docs/api/basic_json/from_bjdata.md | 93 +++++++ .../docs/api/basic_json/get_allocator.md | 22 +- docs/mkdocs/docs/api/basic_json/index.md | 2 + .../docs/api/basic_json/input_format_t.md | 22 +- .../api/basic_json/object_comparator_t.md | 17 +- docs/mkdocs/docs/api/basic_json/operator+=.md | 9 +- docs/mkdocs/docs/api/basic_json/operator[].md | 2 + .../docs/api/basic_json/operator_ValueType.md | 1 - .../mkdocs/docs/api/basic_json/operator_eq.md | 4 +- docs/mkdocs/docs/api/basic_json/push_back.md | 9 +- docs/mkdocs/docs/api/basic_json/to_bjdata.md | 70 ++++++ docs/mkdocs/docs/api/basic_json/value_t.md | 16 ++ .../byte_container_with_subtype.md | 16 ++ .../clear_subtype.md | 16 ++ .../has_subtype.md | 16 ++ .../set_subtype.md | 16 ++ .../byte_container_with_subtype/subtype.md | 17 ++ docs/mkdocs/docs/api/json.md | 16 ++ docs/mkdocs/docs/api/json_pointer/index.md | 8 +- .../docs/api/json_pointer/operator_string.md | 16 ++ docs/mkdocs/docs/api/json_pointer/string_t.md | 16 ++ docs/mkdocs/docs/api/json_sax/binary.md | 16 ++ docs/mkdocs/docs/api/json_sax/boolean.md | 16 ++ docs/mkdocs/docs/api/json_sax/end_array.md | 16 ++ docs/mkdocs/docs/api/json_sax/end_object.md | 16 ++ docs/mkdocs/docs/api/json_sax/key.md | 16 ++ docs/mkdocs/docs/api/json_sax/null.md | 16 ++ docs/mkdocs/docs/api/json_sax/number_float.md | 16 ++ .../docs/api/json_sax/number_integer.md | 16 ++ .../docs/api/json_sax/number_unsigned.md | 16 ++ docs/mkdocs/docs/api/json_sax/parse_error.md | 16 ++ docs/mkdocs/docs/api/json_sax/start_array.md | 16 ++ docs/mkdocs/docs/api/json_sax/start_object.md | 16 ++ docs/mkdocs/docs/api/json_sax/string.md | 16 ++ .../mkdocs/docs/api/macros/json_has_cpp_11.md | 13 + .../docs/api/macros/json_has_filesystem.md | 13 + docs/mkdocs/docs/api/macros/json_no_io.md | 14 ++ .../docs/api/macros/json_noexception.md | 13 + .../json_skip_unsupported_compiler_check.md | 13 + .../api/macros/nlohmann_json_version_major.md | 17 ++ docs/mkdocs/docs/api/ordered_json.md | 17 ++ .../docs/features/binary_formats/bjdata.md | 209 ++++++++++++++++ .../docs/features/binary_formats/index.md | 21 +- docs/mkdocs/docs/features/binary_values.md | 100 +++++++- .../features/element_access/checked_access.md | 44 ++-- .../element_access/unchecked_access.md | 43 ++-- docs/mkdocs/docs/features/iterators.md | 2 +- docs/mkdocs/docs/features/object_order.md | 21 +- docs/mkdocs/docs/home/exceptions.md | 39 ++- docs/mkdocs/mkdocs.yml | 3 + docs/mkdocs/scripts/check_structure.py | 59 +++-- include/nlohmann/json.hpp | 23 -- single_include/nlohmann/json.hpp | 23 -- tests/CMakeLists.txt | 2 +- tests/src/unit-binary_formats.cpp | 232 ++++++++++++++++++ tests/src/unit-bjdata.cpp | 15 -- 102 files changed, 1990 insertions(+), 247 deletions(-) create mode 100644 docs/examples/byte_container_with_subtype__byte_container_with_subtype.cpp create mode 100644 docs/examples/byte_container_with_subtype__byte_container_with_subtype.output create mode 100644 docs/examples/byte_container_with_subtype__clear_subtype.cpp create mode 100644 docs/examples/byte_container_with_subtype__clear_subtype.output create mode 100644 docs/examples/byte_container_with_subtype__has_subtype.cpp create mode 100644 docs/examples/byte_container_with_subtype__has_subtype.output create mode 100644 docs/examples/byte_container_with_subtype__set_subtype.cpp create mode 100644 docs/examples/byte_container_with_subtype__set_subtype.output create mode 100644 docs/examples/byte_container_with_subtype__subtype.cpp create mode 100644 docs/examples/byte_container_with_subtype__subtype.output create mode 100644 docs/examples/cbor_tag_handler_t.cpp create mode 100644 docs/examples/cbor_tag_handler_t.output create mode 100644 docs/examples/default_object_comparator_t.cpp create mode 100644 docs/examples/default_object_comparator_t.output create mode 100644 docs/examples/error_handler_t.cpp create mode 100644 docs/examples/error_handler_t.output create mode 100644 docs/examples/from_bjdata.cpp create mode 100644 docs/examples/from_bjdata.output create mode 100644 docs/examples/get_allocator.cpp create mode 100644 docs/examples/get_allocator.output delete mode 100644 docs/examples/iterator_wrapper.cpp delete mode 100644 docs/examples/iterator_wrapper.output create mode 100644 docs/examples/json_pointer__operator_string.cpp create mode 100644 docs/examples/json_pointer__operator_string.output create mode 100644 docs/examples/json_pointer__string_t.cpp create mode 100644 docs/examples/json_pointer__string_t.output create mode 100644 docs/examples/nlohmann_json_version.cpp create mode 100644 docs/examples/nlohmann_json_version.output create mode 100644 docs/examples/object_comparator_t.cpp create mode 100644 docs/examples/object_comparator_t.output create mode 100644 docs/examples/ordered_json.cpp create mode 100644 docs/examples/ordered_json.output create mode 100644 docs/examples/sax_parse__binary.cpp create mode 100644 docs/examples/sax_parse__binary.output create mode 100644 docs/examples/to_bjdata.cpp create mode 100644 docs/examples/to_bjdata.output create mode 100644 docs/mkdocs/docs/api/basic_json/from_bjdata.md create mode 100644 docs/mkdocs/docs/api/basic_json/to_bjdata.md create mode 100644 docs/mkdocs/docs/features/binary_formats/bjdata.md create mode 100644 tests/src/unit-binary_formats.cpp diff --git a/README.md b/README.md index 3f61377ba..96beb8899 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ There are myriads of [JSON](https://json.org) libraries out there, and each may - **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings. -- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](https://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289). +- **Serious testing**. Our code is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](https://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289). Other aspects were not so important to us: @@ -69,6 +69,10 @@ See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/. You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nlohmann). +### :office: Corporate Sponsor + +[![](https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Codacy-logo-black.svg/320px-Codacy-logo-black.svg.png)](https://github.com/codacy) + ### :label: Named Sponsors - [Michael Hartmann](https://github.com/reFX-Mike) @@ -1599,6 +1603,20 @@ I deeply appreciate the help of the following people. - [Dirk Stolle](https://github.com/striezel) fixed typos in documentation. - [Daniel Albuschat](https://github.com/daniel-kun) corrected the parameter name in the `parse` documentation. - [Prince Mendiratta](https://github.com/Prince-Mendiratta) fixed a link to the FAQ. +- [Florian Albrechtskirchinger](https://github.com/falbrechtskirchinger) implemented `std::string_view` support for object keys and made dozens of other improvements. +- [Qianqian Fang](https://github.com/fangq) implemented the Binary JData (BJData) format. +- [pketelsen](https://github.com/pketelsen) added macros `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` and `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`. +- [DarkZeros](https://github.com/DarkZeros) adjusted to code to not clash with Arduino defines. +- [flagarde](https://github.com/flagarde) fixed the output of `meta()` for MSVC. +- [Giovanni Cerretani](https://github.com/gcerretani) fixed a check for `std::filesystem`. +- [Dimitris Apostolou](https://github.com/rex4539) fixed a typo. +- [Ferry Huberts](https://github.com/fhuberts) fixed a typo. +- [Michael Nosthoff](https://github.com/heinemml) fixed a typo. +- [JungHoon Lee](https://github.com/jhnlee) fixed a typo. +- [Faruk D.](https://github.com/fdiblen) fixed the CITATION.CFF file. +- [Andrea Cocito](https://github.com/puffetto) added a clarification on macro usage to the documentation. +- [Krzysiek Karbowiak](https://github.com/kkarbowiak) refactored the tests to use `CHECK_THROWS_WITH_AS`. +- [Chaoqi Zhang](https://github.com/prncoprs) fixed a typo. Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone. @@ -1613,7 +1631,7 @@ The library itself consists of a single header file licensed under the MIT licen - [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code indentation - [**Clang**](https://clang.llvm.org) for compilation with code sanitizers - [**CMake**](https://cmake.org) for build automation -- [**Codacity**](https://www.codacy.com) for further [code analysis](https://www.codacy.com/app/nlohmann/json) +- [**Codacy**](https://www.codacy.com) for further [code analysis](https://www.codacy.com/app/nlohmann/json) - [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json) - [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json) - [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis diff --git a/docs/examples/byte_container_with_subtype__byte_container_with_subtype.cpp b/docs/examples/byte_container_with_subtype__byte_container_with_subtype.cpp new file mode 100644 index 000000000..1c10be5c2 --- /dev/null +++ b/docs/examples/byte_container_with_subtype__byte_container_with_subtype.cpp @@ -0,0 +1,23 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +using json = nlohmann::json; + +int main() +{ + // (1) create empty container + auto c1 = byte_container_with_subtype(); + + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // (2) create container + auto c2 = byte_container_with_subtype(bytes); + + // (3) create container with subtype + auto c3 = byte_container_with_subtype(bytes, 42); + + std::cout << json(c1) << "\n" << json(c2) << "\n" << json(c3) << std::endl; +} diff --git a/docs/examples/byte_container_with_subtype__byte_container_with_subtype.output b/docs/examples/byte_container_with_subtype__byte_container_with_subtype.output new file mode 100644 index 000000000..67ac1b2ef --- /dev/null +++ b/docs/examples/byte_container_with_subtype__byte_container_with_subtype.output @@ -0,0 +1,3 @@ +{"bytes":[],"subtype":null} +{"bytes":[202,254,186,190],"subtype":null} +{"bytes":[202,254,186,190],"subtype":42} diff --git a/docs/examples/byte_container_with_subtype__clear_subtype.cpp b/docs/examples/byte_container_with_subtype__clear_subtype.cpp new file mode 100644 index 000000000..f9ce6842b --- /dev/null +++ b/docs/examples/byte_container_with_subtype__clear_subtype.cpp @@ -0,0 +1,21 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +using json = nlohmann::json; + +int main() +{ + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // create container with subtype + auto c1 = byte_container_with_subtype(bytes, 42); + + std::cout << "before calling clear_subtype(): " << json(c1) << '\n'; + + c1.clear_subtype(); + + std::cout << "after calling clear_subtype(): " << json(c1) << '\n'; +} diff --git a/docs/examples/byte_container_with_subtype__clear_subtype.output b/docs/examples/byte_container_with_subtype__clear_subtype.output new file mode 100644 index 000000000..9d8212946 --- /dev/null +++ b/docs/examples/byte_container_with_subtype__clear_subtype.output @@ -0,0 +1,2 @@ +before calling clear_subtype(): {"bytes":[202,254,186,190],"subtype":42} +after calling clear_subtype(): {"bytes":[202,254,186,190],"subtype":null} diff --git a/docs/examples/byte_container_with_subtype__has_subtype.cpp b/docs/examples/byte_container_with_subtype__has_subtype.cpp new file mode 100644 index 000000000..61c21eaae --- /dev/null +++ b/docs/examples/byte_container_with_subtype__has_subtype.cpp @@ -0,0 +1,19 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +int main() +{ + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // create container + auto c1 = byte_container_with_subtype(bytes); + + // create container with subtype + auto c2 = byte_container_with_subtype(bytes, 42); + + std::cout << std::boolalpha << "c1.has_subtype() = " << c1.has_subtype() + << "\nc2.has_subtype() = " << c2.has_subtype() << std::endl; +} diff --git a/docs/examples/byte_container_with_subtype__has_subtype.output b/docs/examples/byte_container_with_subtype__has_subtype.output new file mode 100644 index 000000000..f4aade2a1 --- /dev/null +++ b/docs/examples/byte_container_with_subtype__has_subtype.output @@ -0,0 +1,2 @@ +c1.has_subtype() = false +c2.has_subtype() = true diff --git a/docs/examples/byte_container_with_subtype__set_subtype.cpp b/docs/examples/byte_container_with_subtype__set_subtype.cpp new file mode 100644 index 000000000..b2694c54d --- /dev/null +++ b/docs/examples/byte_container_with_subtype__set_subtype.cpp @@ -0,0 +1,22 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +using json = nlohmann::json; + +int main() +{ + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // create container without subtype + auto c = byte_container_with_subtype(bytes); + + std::cout << "before calling set_subtype(42): " << json(c) << '\n'; + + // set the subtype + c.set_subtype(42); + + std::cout << "after calling set_subtype(42): " << json(c) << '\n'; +} diff --git a/docs/examples/byte_container_with_subtype__set_subtype.output b/docs/examples/byte_container_with_subtype__set_subtype.output new file mode 100644 index 000000000..648b3ef24 --- /dev/null +++ b/docs/examples/byte_container_with_subtype__set_subtype.output @@ -0,0 +1,2 @@ +before calling set_subtype(42): {"bytes":[202,254,186,190],"subtype":null} +after calling set_subtype(42): {"bytes":[202,254,186,190],"subtype":42} diff --git a/docs/examples/byte_container_with_subtype__subtype.cpp b/docs/examples/byte_container_with_subtype__subtype.cpp new file mode 100644 index 000000000..cd230ade1 --- /dev/null +++ b/docs/examples/byte_container_with_subtype__subtype.cpp @@ -0,0 +1,22 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +int main() +{ + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // create container + auto c1 = byte_container_with_subtype(bytes); + + // create container with subtype + auto c2 = byte_container_with_subtype(bytes, 42); + + std::cout << "c1.subtype() = " << c1.subtype() + << "\nc2.subtype() = " << c2.subtype() << std::endl; + + // in case no subtype is set, return special value + assert(c1.subtype() == static_cast(-1)); +} diff --git a/docs/examples/byte_container_with_subtype__subtype.output b/docs/examples/byte_container_with_subtype__subtype.output new file mode 100644 index 000000000..47955277b --- /dev/null +++ b/docs/examples/byte_container_with_subtype__subtype.output @@ -0,0 +1,2 @@ +c1.subtype() = 18446744073709551615 +c2.subtype() = 42 diff --git a/docs/examples/cbor_tag_handler_t.cpp b/docs/examples/cbor_tag_handler_t.cpp new file mode 100644 index 000000000..79052c7a0 --- /dev/null +++ b/docs/examples/cbor_tag_handler_t.cpp @@ -0,0 +1,28 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // tagged byte string + std::vector vec = {{0xd8, 0x42, 0x44, 0xcA, 0xfe, 0xba, 0xbe}}; + + // cbor_tag_handler_t::error throws + try + { + auto b_throw_on_tag = json::from_cbor(vec, true, true, json::cbor_tag_handler_t::error); + } + catch (json::parse_error& e) + { + std::cout << e.what() << std::endl; + } + + // cbor_tag_handler_t::ignore ignores the tag + auto b_ignore_tag = json::from_cbor(vec, true, true, json::cbor_tag_handler_t::ignore); + std::cout << b_ignore_tag << std::endl; + + // cbor_tag_handler_t::store stores the tag as binary subtype + auto b_store_tag = json::from_cbor(vec, true, true, json::cbor_tag_handler_t::store); + std::cout << b_store_tag << std::endl; +} diff --git a/docs/examples/cbor_tag_handler_t.output b/docs/examples/cbor_tag_handler_t.output new file mode 100644 index 000000000..18920b137 --- /dev/null +++ b/docs/examples/cbor_tag_handler_t.output @@ -0,0 +1,3 @@ +[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0xD8 +{"bytes":[202,254,186,190],"subtype":null} +{"bytes":[202,254,186,190],"subtype":66} diff --git a/docs/examples/default_object_comparator_t.cpp b/docs/examples/default_object_comparator_t.cpp new file mode 100644 index 000000000..9f200fe6b --- /dev/null +++ b/docs/examples/default_object_comparator_t.cpp @@ -0,0 +1,11 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + std::cout << std::boolalpha + << "one < two : " << json::default_object_comparator_t{}("one", "two") << "\n" + << "three < four : " << json::default_object_comparator_t{}("three", "four") << std::endl; +} diff --git a/docs/examples/default_object_comparator_t.output b/docs/examples/default_object_comparator_t.output new file mode 100644 index 000000000..b1daf3b96 --- /dev/null +++ b/docs/examples/default_object_comparator_t.output @@ -0,0 +1,2 @@ +one < two : true +three < four : false diff --git a/docs/examples/error_handler_t.cpp b/docs/examples/error_handler_t.cpp new file mode 100644 index 000000000..add3f3b2d --- /dev/null +++ b/docs/examples/error_handler_t.cpp @@ -0,0 +1,24 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON value with invalid UTF-8 byte sequence + json j_invalid = "ä\xA9ü"; + try + { + std::cout << j_invalid.dump() << std::endl; + } + catch (json::type_error& e) + { + std::cout << e.what() << std::endl; + } + + std::cout << "string with replaced invalid characters: " + << j_invalid.dump(-1, ' ', false, json::error_handler_t::replace) + << "\nstring with ignored invalid characters: " + << j_invalid.dump(-1, ' ', false, json::error_handler_t::ignore) + << '\n'; +} diff --git a/docs/examples/error_handler_t.output b/docs/examples/error_handler_t.output new file mode 100644 index 000000000..718d62bee --- /dev/null +++ b/docs/examples/error_handler_t.output @@ -0,0 +1,3 @@ +[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9 +string with replaced invalid characters: "ä�ü" +string with ignored invalid characters: "äü" diff --git a/docs/examples/from_bjdata.cpp b/docs/examples/from_bjdata.cpp new file mode 100644 index 000000000..961164c29 --- /dev/null +++ b/docs/examples/from_bjdata.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create byte vector + std::vector v = {0x7B, 0x69, 0x07, 0x63, 0x6F, 0x6D, 0x70, 0x61, + 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, + 0x65, 0x6D, 0x61, 0x69, 0x00, 0x7D + }; + + // deserialize it with BJData + json j = json::from_bjdata(v); + + // print the deserialized JSON value + std::cout << std::setw(2) << j << std::endl; +} diff --git a/docs/examples/from_bjdata.output b/docs/examples/from_bjdata.output new file mode 100644 index 000000000..259f63bd4 --- /dev/null +++ b/docs/examples/from_bjdata.output @@ -0,0 +1,4 @@ +{ + "compact": true, + "schema": 0 +} diff --git a/docs/examples/get_allocator.cpp b/docs/examples/get_allocator.cpp new file mode 100644 index 000000000..35079a10c --- /dev/null +++ b/docs/examples/get_allocator.cpp @@ -0,0 +1,18 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + auto alloc = json::get_allocator(); + using traits_t = std::allocator_traits; + + json* j = traits_t::allocate(alloc, 1); + traits_t::construct(alloc, j, "Hello, world!"); + + std::cout << *j << std::endl; + + traits_t::destroy(alloc, j); + traits_t::deallocate(alloc, j, 1); +} diff --git a/docs/examples/get_allocator.output b/docs/examples/get_allocator.output new file mode 100644 index 000000000..8effb3e8c --- /dev/null +++ b/docs/examples/get_allocator.output @@ -0,0 +1 @@ +"Hello, world!" diff --git a/docs/examples/iterator_wrapper.cpp b/docs/examples/iterator_wrapper.cpp deleted file mode 100644 index 84d37254b..000000000 --- a/docs/examples/iterator_wrapper.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include - -using json = nlohmann::json; - -int main() -{ - // create JSON values - json j_object = {{"one", 1}, {"two", 2}}; - json j_array = {1, 2, 4, 8, 16}; - - ////////////////////////////////////////////////////////////////////////// - // The static function iterator_wrapper was deprecated in version 3.1.0 - // and will be removed in version 4.0.0. Please replace all occurrences - // of iterator_wrapper(j) with j.items(). - ////////////////////////////////////////////////////////////////////////// - - // example for an object - for (auto& x : json::iterator_wrapper(j_object)) - { - std::cout << "key: " << x.key() << ", value: " << x.value() << '\n'; - } - - // example for an array - for (auto& x : json::iterator_wrapper(j_array)) - { - std::cout << "key: " << x.key() << ", value: " << x.value() << '\n'; - } -} diff --git a/docs/examples/iterator_wrapper.output b/docs/examples/iterator_wrapper.output deleted file mode 100644 index 89b09f524..000000000 --- a/docs/examples/iterator_wrapper.output +++ /dev/null @@ -1,7 +0,0 @@ -key: one, value: 1 -key: two, value: 2 -key: 0, value: 1 -key: 1, value: 2 -key: 2, value: 4 -key: 3, value: 8 -key: 4, value: 16 diff --git a/docs/examples/json_pointer__operator_string.cpp b/docs/examples/json_pointer__operator_string.cpp new file mode 100644 index 000000000..56f213020 --- /dev/null +++ b/docs/examples/json_pointer__operator_string.cpp @@ -0,0 +1,19 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // different JSON Pointers + json::json_pointer ptr1("/foo/0"); + json::json_pointer ptr2("/a~1b"); + + // implicit conversion to string + std::string s; + s += ptr1; + s += "\n"; + s += ptr2; + + std::cout << s << std::endl; +} diff --git a/docs/examples/json_pointer__operator_string.output b/docs/examples/json_pointer__operator_string.output new file mode 100644 index 000000000..ec6aba2c2 --- /dev/null +++ b/docs/examples/json_pointer__operator_string.output @@ -0,0 +1,2 @@ +/foo/0 +/a~1b diff --git a/docs/examples/json_pointer__string_t.cpp b/docs/examples/json_pointer__string_t.cpp new file mode 100644 index 000000000..fbe0f179e --- /dev/null +++ b/docs/examples/json_pointer__string_t.cpp @@ -0,0 +1,13 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + json::json_pointer::string_t s = "This is a string."; + + std::cout << s << std::endl; + + std::cout << std::boolalpha << std::is_same::value << std::endl; +} diff --git a/docs/examples/json_pointer__string_t.output b/docs/examples/json_pointer__string_t.output new file mode 100644 index 000000000..d87113724 --- /dev/null +++ b/docs/examples/json_pointer__string_t.output @@ -0,0 +1,2 @@ +This is a string. +true diff --git a/docs/examples/json_pointer__to_string.cpp b/docs/examples/json_pointer__to_string.cpp index da397cdf4..ae1361aaa 100644 --- a/docs/examples/json_pointer__to_string.cpp +++ b/docs/examples/json_pointer__to_string.cpp @@ -19,7 +19,6 @@ int main() json::json_pointer ptr11("/ "); json::json_pointer ptr12("/m~0n"); - std::cout << ptr1.to_string() << '\n' << ptr2.to_string() << '\n' << ptr3.to_string() << '\n' diff --git a/docs/examples/nlohmann_json_version.cpp b/docs/examples/nlohmann_json_version.cpp new file mode 100644 index 000000000..ca5f53728 --- /dev/null +++ b/docs/examples/nlohmann_json_version.cpp @@ -0,0 +1,12 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + std::cout << "JSON for Modern C++ version " + << NLOHMANN_JSON_VERSION_MAJOR << "." + << NLOHMANN_JSON_VERSION_MINOR << "." + << NLOHMANN_JSON_VERSION_PATCH << std::endl; +} diff --git a/docs/examples/nlohmann_json_version.output b/docs/examples/nlohmann_json_version.output new file mode 100644 index 000000000..600343999 --- /dev/null +++ b/docs/examples/nlohmann_json_version.output @@ -0,0 +1 @@ +JSON for Modern C++ version 3.10.5 diff --git a/docs/examples/object_comparator_t.cpp b/docs/examples/object_comparator_t.cpp new file mode 100644 index 000000000..6b82c7ca6 --- /dev/null +++ b/docs/examples/object_comparator_t.cpp @@ -0,0 +1,11 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + std::cout << std::boolalpha + << "json::object_comparator_t(\"one\", \"two\") = " << json::object_comparator_t{}("one", "two") << "\n" + << "json::object_comparator_t(\"three\", \"four\") = " << json::object_comparator_t{}("three", "four") << std::endl; +} diff --git a/docs/examples/object_comparator_t.output b/docs/examples/object_comparator_t.output new file mode 100644 index 000000000..63620edb4 --- /dev/null +++ b/docs/examples/object_comparator_t.output @@ -0,0 +1,2 @@ +json::object_comparator_t("one", "two") = true +json::object_comparator_t("three", "four") = false diff --git a/docs/examples/ordered_json.cpp b/docs/examples/ordered_json.cpp new file mode 100644 index 000000000..effad530c --- /dev/null +++ b/docs/examples/ordered_json.cpp @@ -0,0 +1,14 @@ +#include +#include + +using ordered_json = nlohmann::ordered_json; + +int main() +{ + ordered_json j; + j["one"] = 1; + j["two"] = 2; + j["three"] = 3; + + std::cout << j.dump(2) << '\n'; +} diff --git a/docs/examples/ordered_json.output b/docs/examples/ordered_json.output new file mode 100644 index 000000000..120cbb284 --- /dev/null +++ b/docs/examples/ordered_json.output @@ -0,0 +1,5 @@ +{ + "one": 1, + "two": 2, + "three": 3 +} diff --git a/docs/examples/sax_parse.cpp b/docs/examples/sax_parse.cpp index 45273eb6c..8602687e3 100644 --- a/docs/examples/sax_parse.cpp +++ b/docs/examples/sax_parse.cpp @@ -6,7 +6,7 @@ using json = nlohmann::json; // a simple event consumer that collects string representations of the passed -// values; not inheriting from json::json_sax_t is not required, but can +// values; note inheriting from json::json_sax_t is not required, but can // help not to forget a required function class sax_event_consumer : public json::json_sax_t { @@ -15,79 +15,79 @@ class sax_event_consumer : public json::json_sax_t bool null() override { - events.push_back("value: null"); + events.push_back("null()"); return true; } bool boolean(bool val) override { - events.push_back("value: " + std::string(val ? "true" : "false")); + events.push_back("boolean(val=" + std::string(val ? "true" : "false") + ")"); return true; } bool number_integer(number_integer_t val) override { - events.push_back("value: " + std::to_string(val)); + events.push_back("number_integer(val=" + std::to_string(val) + ")"); return true; } bool number_unsigned(number_unsigned_t val) override { - events.push_back("value: " + std::to_string(val)); + events.push_back("number_unsigned(val=" + std::to_string(val) + ")"); return true; } bool number_float(number_float_t val, const string_t& s) override { - events.push_back("value: " + s); + events.push_back("number_float(val=" + std::to_string(val) + ", s=" + s + ")"); return true; } bool string(string_t& val) override { - events.push_back("value: " + val); + events.push_back("string(val=" + val + ")"); return true; } bool start_object(std::size_t elements) override { - events.push_back("start: object"); + events.push_back("start_object(elements=" + std::to_string(elements) + ")"); return true; } bool end_object() override { - events.push_back("end: object"); + events.push_back("end_object()"); return true; } bool start_array(std::size_t elements) override { - events.push_back("start: array"); + events.push_back("start_array(elements=" + std::to_string(elements) + ")"); return true; } bool end_array() override { - events.push_back("end: array"); + events.push_back("end_array()"); return true; } bool key(string_t& val) override { - events.push_back("key: " + val); + events.push_back("key(val=" + val + ")"); return true; } bool binary(json::binary_t& val) override { - events.push_back("binary"); + events.push_back("binary(val=[...])"); return true; } bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex) override { - events.push_back("error: " + std::string(ex.what())); + events.push_back("parse_error(position=" + std::to_string(position) + ", last_token=" + last_token + ",\n ex=" + std::string(ex.what()) + ")"); return false; } }; @@ -107,22 +107,23 @@ int main() "Width": 100 }, "Animated" : false, - "IDs": [116, 943, 234, 38793], + "IDs": [116, 943, 234, -38793], + "DeletionDate": null, "Distance": 12.723374634 } - } + }] )"; // create a SAX event consumer object sax_event_consumer sec; - // parse and serialize JSON + // parse JSON bool result = json::sax_parse(text, &sec); // output the recorded events for (auto& event : sec.events) { - std::cout << "(" << event << ") "; + std::cout << event << "\n"; } // output the result of sax_parse diff --git a/docs/examples/sax_parse.output b/docs/examples/sax_parse.output index e16c2c4de..dd2fc2f05 100644 --- a/docs/examples/sax_parse.output +++ b/docs/examples/sax_parse.output @@ -1,2 +1,37 @@ -(start: object) (key: Image) (start: object) (key: Width) (value: 800) (key: Height) (value: 600) (key: Title) (value: View from 15th Floor) (key: Thumbnail) (start: object) (key: Url) (value: http://www.example.com/image/481989943) (key: Height) (value: 125) (key: Width) (value: 100) (end: object) (key: Animated) (value: false) (key: IDs) (start: array) (value: 116) (value: 943) (value: 234) (value: 38793) (end: array) (key: Distance) (value: 12.723374634) (end: object) (end: object) -result: true +start_object(elements=18446744073709551615) +key(val=Image) +start_object(elements=18446744073709551615) +key(val=Width) +number_unsigned(val=800) +key(val=Height) +number_unsigned(val=600) +key(val=Title) +string(val=View from 15th Floor) +key(val=Thumbnail) +start_object(elements=18446744073709551615) +key(val=Url) +string(val=http://www.example.com/image/481989943) +key(val=Height) +number_unsigned(val=125) +key(val=Width) +number_unsigned(val=100) +end_object() +key(val=Animated) +boolean(val=false) +key(val=IDs) +start_array(elements=18446744073709551615) +number_unsigned(val=116) +number_unsigned(val=943) +number_unsigned(val=234) +number_integer(val=-38793) +end_array() +key(val=DeletionDate) +null() +key(val=Distance) +number_float(val=12.723375, s=12.723374634) +end_object() +end_object() +parse_error(position=460, last_token=12.723374634 } }], + ex=[json.exception.parse_error.101] parse error at line 17, column 6: syntax error while parsing value - unexpected ']'; expected end of input) + +result: false diff --git a/docs/examples/sax_parse__binary.cpp b/docs/examples/sax_parse__binary.cpp new file mode 100644 index 000000000..08bc85df6 --- /dev/null +++ b/docs/examples/sax_parse__binary.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +using json = nlohmann::json; + +// a simple event consumer that collects string representations of the passed +// values; note inheriting from json::json_sax_t is not required, but can +// help not to forget a required function +class sax_event_consumer : public json::json_sax_t +{ + public: + std::vector events; + + bool null() override + { + events.push_back("null()"); + return true; + } + + bool boolean(bool val) override + { + events.push_back("boolean(val=" + std::string(val ? "true" : "false") + ")"); + return true; + } + + bool number_integer(number_integer_t val) override + { + events.push_back("number_integer(val=" + std::to_string(val) + ")"); + return true; + } + + bool number_unsigned(number_unsigned_t val) override + { + events.push_back("number_unsigned(val=" + std::to_string(val) + ")"); + return true; + } + + bool number_float(number_float_t val, const string_t& s) override + { + events.push_back("number_float(val=" + std::to_string(val) + ", s=" + s + ")"); + return true; + } + + bool string(string_t& val) override + { + events.push_back("string(val=" + val + ")"); + return true; + } + + bool start_object(std::size_t elements) override + { + events.push_back("start_object(elements=" + std::to_string(elements) + ")"); + return true; + } + + bool end_object() override + { + events.push_back("end_object()"); + return true; + } + + bool start_array(std::size_t elements) override + { + events.push_back("start_array(elements=" + std::to_string(elements) + ")"); + return true; + } + + bool end_array() override + { + events.push_back("end_array()"); + return true; + } + + bool key(string_t& val) override + { + events.push_back("key(val=" + val + ")"); + return true; + } + + bool binary(json::binary_t& val) override + { + events.push_back("binary(val=[...])"); + return true; + } + + bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex) override + { + events.push_back("parse_error(position=" + std::to_string(position) + ", last_token=" + last_token + ",\n ex=" + std::string(ex.what()) + ")"); + return false; + } +}; + +int main() +{ + // CBOR byte string + std::vector vec = {{0x44, 0xcA, 0xfe, 0xba, 0xbe}}; + + // create a SAX event consumer object + sax_event_consumer sec; + + // parse CBOR + bool result = json::sax_parse(vec, &sec, json::input_format_t::cbor); + + // output the recorded events + for (auto& event : sec.events) + { + std::cout << event << "\n"; + } + + // output the result of sax_parse + std::cout << "\nresult: " << std::boolalpha << result << std::endl; +} diff --git a/docs/examples/sax_parse__binary.output b/docs/examples/sax_parse__binary.output new file mode 100644 index 000000000..f88089610 --- /dev/null +++ b/docs/examples/sax_parse__binary.output @@ -0,0 +1,3 @@ +binary(val=[...]) + +result: true diff --git a/docs/examples/to_bjdata.cpp b/docs/examples/to_bjdata.cpp new file mode 100644 index 000000000..0bfe33323 --- /dev/null +++ b/docs/examples/to_bjdata.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + +using json = nlohmann::json; + +// function to print BJData's diagnostic format +void print_byte(uint8_t byte) +{ + if (32 < byte and byte < 128) + { + std::cout << (char)byte; + } + else + { + std::cout << (int)byte; + } +} + +int main() +{ + // create a JSON value + json j = R"({"compact": true, "schema": false})"_json; + + // serialize it to BJData + std::vector v = json::to_bjdata(j); + + // print the vector content + for (auto& byte : v) + { + print_byte(byte); + } + std::cout << std::endl; + + // create an array of numbers + json array = {1, 2, 3, 4, 5, 6, 7, 8}; + + // serialize it to BJData using default representation + std::vector v_array = json::to_bjdata(array); + // serialize it to BJData using size optimization + std::vector v_array_size = json::to_bjdata(array, true); + // serialize it to BJData using type optimization + std::vector v_array_size_and_type = json::to_bjdata(array, true, true); + + // print the vector contents + for (auto& byte : v_array) + { + print_byte(byte); + } + std::cout << std::endl; + + for (auto& byte : v_array_size) + { + print_byte(byte); + } + std::cout << std::endl; + + for (auto& byte : v_array_size_and_type) + { + print_byte(byte); + } + std::cout << std::endl; +} diff --git a/docs/examples/to_bjdata.output b/docs/examples/to_bjdata.output new file mode 100644 index 000000000..087980cb9 --- /dev/null +++ b/docs/examples/to_bjdata.output @@ -0,0 +1,4 @@ +{i7compactTi6schemaF} +[i1i2i3i4i5i6i7i8] +[#i8i1i2i3i4i5i6i7i8 +[$i#i812345678 diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index dbd4cb6c9..3eea8dd15 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -184,6 +184,7 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also +- documentation on [checked access](../../features/element_access/checked_access.md) - see [`operator[]`](operator%5B%5D.md) for unchecked access by reference - see [`value`](value.md) for access with default value diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index 9a289d6e9..365cd7c22 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -241,7 +241,7 @@ basic_json(basic_json&& other) noexcept; - Overload 5: - !!! note + !!! note "Empty initializer list" When used without parentheses around an empty initializer list, `basic_json()` is called instead of this function, yielding the JSON `#!json null` value. diff --git a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md index 6622d8aca..e19c3edd9 100644 --- a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md @@ -20,6 +20,23 @@ ignore store : store tagged values as binary container with subtype (for bytes 0xd8..0xdb) +## Examples + +??? example + + The example below shows how the different values of the `cbor_tag_handler_t` influence the behavior of + [`from_cbor`](from_cbor.md) when reading a tagged byte string. + + ```cpp + --8<-- "examples/cbor_tag_handler_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/cbor_tag_handler_t.output" + ``` + ## Version history - Added in version 3.9.0. Added value `store` in 3.10.0. diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 67a5ffcc0..e6d9df85c 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -60,8 +60,8 @@ Logarithmic in the size of the JSON object. ## Notes -1. This method always returns `#!cpp false` when executed on a JSON type that is not an object. -2. This method can be executed on any JSON value type. +- This method always returns `#!cpp false` when executed on a JSON type that is not an object. +- This method can be executed on any JSON value type. !!! info "Postconditions" diff --git a/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md b/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md index 9e5f6c5bd..8a237f662 100644 --- a/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md +++ b/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md @@ -14,6 +14,22 @@ when looking up a key in an object. The actual comparator used depends on [`object_t`](object_t.md) and can be obtained via [`object_comparator_t`](object_comparator_t.md). +## Examples + +??? example + + The example below demonstrates the default comparator. + + ```cpp + --8<-- "examples/default_object_comparator_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/default_object_comparator_t.output" + ``` + ## Version history - Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/error_handler_t.md b/docs/mkdocs/docs/api/basic_json/error_handler_t.md index afd20f89d..dc32ced9b 100644 --- a/docs/mkdocs/docs/api/basic_json/error_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/error_handler_t.md @@ -20,6 +20,23 @@ replace ignore : ignore invalid UTF-8 sequences; all bytes are copied to the output unchanged +## Examples + +??? example + + The example below shows how the different values of the `error_handler_t` influence the behavior of + [`dump`](dump.md) when reading serializing an invalid UTF-8 sequence. + + ```cpp + --8<-- "examples/error_handler_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/error_handler_t.output" + ``` + ## Version history - Added in version 3.4.0. diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md new file mode 100644 index 000000000..3c5eeb351 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -0,0 +1,93 @@ +# nlohmann::basic_json::from_bjdata + +```cpp +// (1) +template +static basic_json from_bjdata(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true); +// (2) +template +static basic_json from_bjdata(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true); +``` + +Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format. + +1. Reads from a compatible input. +2. Reads from an iterator range. + +The exact mapping and its limitations is described on a [dedicated page](../../features/binary_formats/bjdata.md). + +## Template parameters + +`InputType` +: A compatible input, for instance: + + - an `std::istream` object + - a `FILE` pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators. + +`IteratorType` +: a compatible iterator type + +## Parameters + +`i` (in) +: an input in BJData format convertible to an input adapter + +`first` (in) +: iterator to start of the input + +`last` (in) +: iterator to end of the input + +`strict` (in) +: whether to expect the input to be consumed until EOF (`#!cpp true` by default) + +`allow_exceptions` (in) +: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) + +## Return value + +deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be +`value_t::discarded`. The latter can be checked with [`is_discarded`](is_discarded.md). + +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes in the JSON value. + +## Exceptions + +- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or + the end of file was not reached when `strict` was set to true +- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs +- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed + successfully + +## Complexity + +Linear in the size of the input. + +## Examples + +??? example + + The example shows the deserialization of a byte vector in BJData format to a JSON value. + + ```cpp + --8<-- "examples/from_bjdata.cpp" + ``` + + Output: + + ```json + --8<-- "examples/from_bjdata.output" + ``` + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/get_allocator.md b/docs/mkdocs/docs/api/basic_json/get_allocator.md index 1b7700c77..07a4d8456 100644 --- a/docs/mkdocs/docs/api/basic_json/get_allocator.md +++ b/docs/mkdocs/docs/api/basic_json/get_allocator.md @@ -10,10 +10,22 @@ Returns the allocator associated with the container. associated allocator +## Examples + +??? example + + The example shows how `get_allocator()` is used to created `json` values. + + ```cpp + --8<-- "examples/get_allocator.cpp" + ``` + + Output: + + ```json + --8<-- "examples/get_allocator.output" + ``` + ## Version history -- Unknown. - -!!! note - - This documentation page is a stub. +- Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index 68ac063ff..bc4dba153 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -268,10 +268,12 @@ Access to the JSON value ### Binary formats +- [**from_bjdata**](from_bjdata.md) (_static_) - create a JSON value from an input in BJData format - [**from_bson**](from_bson.md) (_static_) - create a JSON value from an input in BSON format - [**from_cbor**](from_cbor.md) (_static_) - create a JSON value from an input in CBOR format - [**from_msgpack**](from_msgpack.md) (_static_) - create a JSON value from an input in MessagePack format - [**from_ubjson**](from_ubjson.md) (_static_) - create a JSON value from an input in UBJSON format +- [**to_bjdata**](to_bjdata.md) (_static_) - create a BJData serialization of a given JSON value - [**to_bson**](to_bson.md) (_static_) - create a BSON serialization of a given JSON value - [**to_cbor**](to_cbor.md) (_static_) - create a CBOR serialization of a given JSON value - [**to_msgpack**](to_msgpack.md) (_static_) - create a MessagePack serialization of a given JSON value diff --git a/docs/mkdocs/docs/api/basic_json/input_format_t.md b/docs/mkdocs/docs/api/basic_json/input_format_t.md index 4accf6dee..a3baabab8 100644 --- a/docs/mkdocs/docs/api/basic_json/input_format_t.md +++ b/docs/mkdocs/docs/api/basic_json/input_format_t.md @@ -6,7 +6,8 @@ enum class input_format_t { cbor, msgpack, ubjson, - bson + bson, + bjdata }; ``` @@ -27,6 +28,25 @@ ubjson bson : BSON (Binary JSON) +bjdata +: BJData (Binary JData) + +## Examples + +??? example + + The example below shows how an `input_format_t` enum value is passed to `sax_parse` to set the input format to CBOR. + + ```cpp + --8<-- "examples/sax_parse__binary.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse__binary.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/basic_json/object_comparator_t.md b/docs/mkdocs/docs/api/basic_json/object_comparator_t.md index 6c64b6453..496a56267 100644 --- a/docs/mkdocs/docs/api/basic_json/object_comparator_t.md +++ b/docs/mkdocs/docs/api/basic_json/object_comparator_t.md @@ -1,6 +1,5 @@ # nlohmann::basic_json::object_comparator_t - ```cpp using object_comparator_t = typename object_t::key_compare; // or @@ -10,6 +9,22 @@ using object_comparator_t = default_object_comparator_t; The comparator used by [`object_t`](object_t.md). Defined as `#!cpp typename object_t::key_compare` if available, and [`default_object_comparator_t`](default_object_comparator_t.md) otherwise. +## Examples + +??? example + + The example below demonstrates the used object comparator. + + ```cpp + --8<-- "examples/object_comparator_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/object_comparator_t.output" + ``` + ## Version history - Added in version 3.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator+=.md b/docs/mkdocs/docs/api/basic_json/operator+=.md index 074b30003..dc5f2ecc4 100644 --- a/docs/mkdocs/docs/api/basic_json/operator+=.md +++ b/docs/mkdocs/docs/api/basic_json/operator+=.md @@ -41,12 +41,9 @@ reference operator+=(initializer_list_t init); ## Exceptions -1. The function can throw the following exceptions: - - Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than - JSON array or null; example: `"cannot use operator+=() with number"` -2. The function can throw the following exceptions: - - Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than - JSON object or null; example: `"cannot use operator+=() with number"` +All functions can throw the following exception: + - Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than + JSON array or null; example: `"cannot use operator+=() with number"` ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index cd5638b97..9fa0c8999 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -198,6 +198,8 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also +- documentation on [unchecked access](../../features/element_access/unchecked_access.md) +- documentation on [runtime assertions](../../features/assertions.md) - see [`at`](at.md) for access by reference with range checking - see [`value`](value.md) for access with default value diff --git a/docs/mkdocs/docs/api/basic_json/operator_ValueType.md b/docs/mkdocs/docs/api/basic_json/operator_ValueType.md index 787588781..7c1901668 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ValueType.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ValueType.md @@ -56,7 +56,6 @@ Linear in the size of the JSON value. [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) to `0` and replace any implicit conversions with calls to [`get`](../basic_json/get.md). - ## Examples ??? example diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index 49f96b1c0..3eec4fda4 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -44,13 +44,13 @@ Linear. ## Notes -!!! note +!!! note "Comparing special values" - NaN values never compare equal to themselves or to other NaN values. - JSON `#!cpp null` values are all equal. - Discarded values never compare equal to themselves. -!!! note +!!! note "Comparing floating-point numbers" Floating-point numbers inside JSON values numbers are compared with `json::number_float_t::operator==` which is `double::operator==` by default. To compare floating-point while respecting an epsilon, an alternative diff --git a/docs/mkdocs/docs/api/basic_json/push_back.md b/docs/mkdocs/docs/api/basic_json/push_back.md index 60bfed305..5c7d20dd6 100644 --- a/docs/mkdocs/docs/api/basic_json/push_back.md +++ b/docs/mkdocs/docs/api/basic_json/push_back.md @@ -37,12 +37,9 @@ void push_back(initializer_list_t init); ## Exceptions -1. The function can throw the following exceptions: - - Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than - JSON array or null; example: `"cannot use push_back() with number"` -2. The function can throw the following exceptions: - - Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than - JSON object or null; example: `"cannot use push_back() with number"` +All functions can throw the following exception: + - Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than + JSON array or null; example: `"cannot use push_back() with number"` ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md new file mode 100644 index 000000000..1ea850523 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -0,0 +1,70 @@ +# nlohmann::basic_json::to_bjdata + +```cpp +// (1) +static std::vector to_bjdata(const basic_json& j, + const bool use_size = false, + const bool use_type = false); + +// (2) +static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false); +static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false); +``` + +Serializes a given JSON value `j` to a byte vector using the BJData (Binary JData) serialization format. BJData +aims to be more compact than JSON itself, yet more efficient to parse. + +1. Returns a byte vector containing the BJData serialization. +2. Writes the BJData serialization to an output adapter. + +The exact mapping and its limitations is described on a [dedicated page](../../features/binary_formats/bjdata.md). + +## Parameters + +`j` (in) +: JSON value to serialize + +`o` (in) +: output adapter to write serialization to + +`use_size` (in) +: whether to add size annotations to container types; optional, `#!cpp false` by default. + +`use_type` (in) +: whether to add type annotations to container types (must be combined with `#!cpp use_size = true`); optional, +`#!cpp false` by default. + +## Return value + +1. BJData serialization as byte vector +2. (none) + +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes in the JSON value. + +## Complexity + +Linear in the size of the JSON value `j`. + +## Examples + +??? example + + The example shows the serialization of a JSON value to a byte vector in BJData format. + + ```cpp + --8<-- "examples/to_bjdata.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_bjdata.output" + ``` + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/value_t.md b/docs/mkdocs/docs/api/basic_json/value_t.md index 768b13ca1..e7d32c480 100644 --- a/docs/mkdocs/docs/api/basic_json/value_t.md +++ b/docs/mkdocs/docs/api/basic_json/value_t.md @@ -29,6 +29,22 @@ distinguishes these three types for numbers: [`number_unsigned_t`](number_unsign [`number_integer_t`](number_integer_t.md) is used for signed integers, and [`number_float_t`](number_float_t.md) is used for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. +## Examples + +??? example + + The following code how `type()` queries the `value_t` for all JSON types. + + ```cpp + --8<-- "examples/type.cpp" + ``` + + Output: + + ```json + --8<-- "examples/type.output" + ``` + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md index caa273df5..9913a9b5c 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md @@ -25,6 +25,22 @@ byte_container_with_subtype(container_type&& container, subtype_type subtype); `subtype` (in) : subtype +## Examples + +??? example + + The example below demonstrates how byte containers can be created. + + ```cpp + --8<-- "examples/byte_container_with_subtype__byte_container_with_subtype.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__byte_container_with_subtype.output" + ``` + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md index 56f8ee0c1..c62dead36 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md @@ -15,6 +15,22 @@ No-throw guarantee: this function never throws exceptions. Constant. +## Examples + +??? example + + The example below demonstrates how `clear_subtype` can remove subtypes. + + ```cpp + --8<-- "examples/byte_container_with_subtype__clear_subtype.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__clear_subtype.output" + ``` + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md index 2fe418132..e06286e29 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md @@ -18,6 +18,22 @@ No-throw guarantee: this function never throws exceptions. Constant. +## Examples + +??? example + + The example below demonstrates how `has_subtype` can check whether a subtype was set. + + ```cpp + --8<-- "examples/byte_container_with_subtype__has_subtype.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__has_subtype.output" + ``` + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md index 40cc2722f..cf21732b8 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md @@ -20,6 +20,22 @@ No-throw guarantee: this function never throws exceptions. Constant. +## Examples + +??? example + + The example below demonstrates how a subtype can be set with `set_subtype`. + + ```cpp + --8<-- "examples/byte_container_with_subtype__set_subtype.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__set_subtype.output" + ``` + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md index e78654b37..389241a79 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md @@ -19,6 +19,23 @@ No-throw guarantee: this function never throws exceptions. Constant. +## Examples + +??? example + + The example below demonstrates how the subtype can be retrieved with `subtype`. Note how `subtype_type(-1)` is + returned for container `c1`. + + ```cpp + --8<-- "examples/byte_container_with_subtype__subtype.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__subtype.output" + ``` + ## Version history - Added in version 3.8.0 diff --git a/docs/mkdocs/docs/api/json.md b/docs/mkdocs/docs/api/json.md index 48d344183..36edcc2c1 100644 --- a/docs/mkdocs/docs/api/json.md +++ b/docs/mkdocs/docs/api/json.md @@ -7,6 +7,22 @@ using json = basic_json<>; This type is the default specialization of the [basic_json](basic_json/index.md) class which uses the standard template types. +## Examples + +??? example + + The example below demonstrates how to use the type `nlohmann::json`. + + ```cpp + --8<-- "examples/README.cpp" + ``` + + Output: + + ```json + --8<-- "examples/README.output" + ``` + ## Version history Since version 1.0.0. diff --git a/docs/mkdocs/docs/api/json_pointer/index.md b/docs/mkdocs/docs/api/json_pointer/index.md index dca9c382c..dc07d89b3 100644 --- a/docs/mkdocs/docs/api/json_pointer/index.md +++ b/docs/mkdocs/docs/api/json_pointer/index.md @@ -14,11 +14,11 @@ are the base for JSON patches. `RefStringType` : the string type used for the reference tokens making up the JSON pointer -## Notes +!!! warning "Deprecation" -For backwards compatibility `RefStringType` may also be a specialization of [`basic_json`](../basic_json/index.md) in -which case `string_t` will be deduced as [`basic_json::string_t`](../basic_json/string_t.md). This feature is deprecated -and may be removed in a future major version. + For backwards compatibility `RefStringType` may also be a specialization of [`basic_json`](../basic_json/index.md) + in which case `string_t` will be deduced as [`basic_json::string_t`](../basic_json/string_t.md). This feature is + deprecated and may be removed in a future major version. ## Member types diff --git a/docs/mkdocs/docs/api/json_pointer/operator_string.md b/docs/mkdocs/docs/api/json_pointer/operator_string.md index 836728c18..f2f99cae7 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_string.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_string.md @@ -19,6 +19,22 @@ operator string_t() const } ``` +## Examples + +??? example + + The example shows how JSON Pointers can be implicitly converted to strings. + + ```cpp + --8<-- "examples/json_pointer__operator_string.cpp" + ``` + + Output: + + ```json + --8<-- "examples/json_pointer__operator_string.output" + ``` + ## Version history - Since version 2.0.0. diff --git a/docs/mkdocs/docs/api/json_pointer/string_t.md b/docs/mkdocs/docs/api/json_pointer/string_t.md index f18b51b81..c8527bc9c 100644 --- a/docs/mkdocs/docs/api/json_pointer/string_t.md +++ b/docs/mkdocs/docs/api/json_pointer/string_t.md @@ -7,6 +7,22 @@ The string type used for the reference tokens making up the JSON pointer. See [`basic_json::string_t`](../basic_json/string_t.md) for more information. +## Examples + +??? example + + The example shows the type `string_t` and its relation to `basic_json::string_t`. + + ```cpp + --8<-- "examples/json_pointer__string_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/json_pointer__string_t.output" + ``` + ## Version history - Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/json_sax/binary.md b/docs/mkdocs/docs/api/json_sax/binary.md index e9a1d3974..753e99c00 100644 --- a/docs/mkdocs/docs/api/json_sax/binary.md +++ b/docs/mkdocs/docs/api/json_sax/binary.md @@ -19,6 +19,22 @@ Whether parsing should proceed. It is safe to move the passed binary value. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse__binary.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse__binary.output" + ``` + ## Version history - Added in version 3.8.0. diff --git a/docs/mkdocs/docs/api/json_sax/boolean.md b/docs/mkdocs/docs/api/json_sax/boolean.md index a5a8ddcb7..78163b331 100644 --- a/docs/mkdocs/docs/api/json_sax/boolean.md +++ b/docs/mkdocs/docs/api/json_sax/boolean.md @@ -15,6 +15,22 @@ A boolean value was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/end_array.md b/docs/mkdocs/docs/api/json_sax/end_array.md index 245cf974c..46b4c7a4b 100644 --- a/docs/mkdocs/docs/api/json_sax/end_array.md +++ b/docs/mkdocs/docs/api/json_sax/end_array.md @@ -10,6 +10,22 @@ The end of an array was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/end_object.md b/docs/mkdocs/docs/api/json_sax/end_object.md index 5654e262e..8df0ab4e1 100644 --- a/docs/mkdocs/docs/api/json_sax/end_object.md +++ b/docs/mkdocs/docs/api/json_sax/end_object.md @@ -10,6 +10,22 @@ The end of an object was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/key.md b/docs/mkdocs/docs/api/json_sax/key.md index 5e7927256..ebd3ae2b2 100644 --- a/docs/mkdocs/docs/api/json_sax/key.md +++ b/docs/mkdocs/docs/api/json_sax/key.md @@ -19,6 +19,22 @@ Whether parsing should proceed. It is safe to move the passed object key value. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/null.md b/docs/mkdocs/docs/api/json_sax/null.md index bcbb4a348..71b2a458a 100644 --- a/docs/mkdocs/docs/api/json_sax/null.md +++ b/docs/mkdocs/docs/api/json_sax/null.md @@ -10,6 +10,22 @@ A null value was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/number_float.md b/docs/mkdocs/docs/api/json_sax/number_float.md index 94193280b..e1b3fb60f 100644 --- a/docs/mkdocs/docs/api/json_sax/number_float.md +++ b/docs/mkdocs/docs/api/json_sax/number_float.md @@ -18,6 +18,22 @@ A floating-point number was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/number_integer.md b/docs/mkdocs/docs/api/json_sax/number_integer.md index 00ff2eaa6..8628a655c 100644 --- a/docs/mkdocs/docs/api/json_sax/number_integer.md +++ b/docs/mkdocs/docs/api/json_sax/number_integer.md @@ -15,6 +15,22 @@ An integer number was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/number_unsigned.md b/docs/mkdocs/docs/api/json_sax/number_unsigned.md index 66d0bdac4..7b5974823 100644 --- a/docs/mkdocs/docs/api/json_sax/number_unsigned.md +++ b/docs/mkdocs/docs/api/json_sax/number_unsigned.md @@ -15,6 +15,22 @@ An unsigned integer number was read. Whether parsing should proceed. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/parse_error.md b/docs/mkdocs/docs/api/json_sax/parse_error.md index 00f1aa950..d4405d09e 100644 --- a/docs/mkdocs/docs/api/json_sax/parse_error.md +++ b/docs/mkdocs/docs/api/json_sax/parse_error.md @@ -23,6 +23,22 @@ A parse error occurred. Whether parsing should proceed (**must return `#!cpp false`**). +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/start_array.md b/docs/mkdocs/docs/api/json_sax/start_array.md index d10a6e5a6..8ef10a4ab 100644 --- a/docs/mkdocs/docs/api/json_sax/start_array.md +++ b/docs/mkdocs/docs/api/json_sax/start_array.md @@ -19,6 +19,22 @@ Whether parsing should proceed. Binary formats may report the number of elements. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/start_object.md b/docs/mkdocs/docs/api/json_sax/start_object.md index fd90f1947..24fedffc3 100644 --- a/docs/mkdocs/docs/api/json_sax/start_object.md +++ b/docs/mkdocs/docs/api/json_sax/start_object.md @@ -19,6 +19,22 @@ Whether parsing should proceed. Binary formats may report the number of elements. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/string.md b/docs/mkdocs/docs/api/json_sax/string.md index 113fe1393..1cb6014ac 100644 --- a/docs/mkdocs/docs/api/json_sax/string.md +++ b/docs/mkdocs/docs/api/json_sax/string.md @@ -19,6 +19,22 @@ Whether parsing should proceed. It is safe to move the passed string value. +## Examples + +??? example + + .The example below shows how the SAX interface is used. + + ```cpp + --8<-- "examples/sax_parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/sax_parse.output" + ``` + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md index 3bee84324..f3eaa585c 100644 --- a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md +++ b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md @@ -23,6 +23,19 @@ The default value is detected based on preprocessor macros such as `#!cpp __cplu - `#!cpp JSON_HAS_CPP_11` is always defined. - All macros are undefined outside the library. +## Examples + +??? example + + The code below forces the library to use the C++14 standard: + + ```cpp + #define JSON_HAS_CPP_14 1 + #include + + ... + ``` + ## Version history - Added in version 3.10.5. diff --git a/docs/mkdocs/docs/api/macros/json_has_filesystem.md b/docs/mkdocs/docs/api/macros/json_has_filesystem.md index 160ad8c24..308aea2ac 100644 --- a/docs/mkdocs/docs/api/macros/json_has_filesystem.md +++ b/docs/mkdocs/docs/api/macros/json_has_filesystem.md @@ -25,6 +25,19 @@ The default value is detected based on the preprocessor macros `#!cpp __cpp_lib_ filesystem support. - Both macros are undefined outside the library. +## Examples + +??? example + + The code below forces the library to use the header ``. + + ```cpp + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #include + + ... + ``` + ## Version history - Added in version 3.10.5. diff --git a/docs/mkdocs/docs/api/macros/json_no_io.md b/docs/mkdocs/docs/api/macros/json_no_io.md index 10ae24c8a..ef37384a5 100644 --- a/docs/mkdocs/docs/api/macros/json_no_io.md +++ b/docs/mkdocs/docs/api/macros/json_no_io.md @@ -16,6 +16,20 @@ By default, `#!cpp JSON_NO_IO` is not defined. #undef JSON_NO_IO ``` +## Examples + +??? example + + The code below forces the library not to use the headers ``, ``, ``, ``, and + ``. + + ```cpp + #define JSON_NO_IO 1 + #include + + ... + ``` + ## Version history - Added in version 3.10.0. diff --git a/docs/mkdocs/docs/api/macros/json_noexception.md b/docs/mkdocs/docs/api/macros/json_noexception.md index 0f32b63e9..c801b8567 100644 --- a/docs/mkdocs/docs/api/macros/json_noexception.md +++ b/docs/mkdocs/docs/api/macros/json_noexception.md @@ -23,6 +23,19 @@ By default, the macro is not defined. The explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824). +## Examples + +??? example + + The code below switches off exceptions in the library. + + ```cpp + #define JSON_NOEXCEPTION 1 + #include + + ... + ``` + ## See also - [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for more information how to switch off exceptions diff --git a/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md b/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md index c58d0ea85..374fa4c27 100644 --- a/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md +++ b/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md @@ -15,6 +15,19 @@ By default, the macro is not defined. #undef JSON_SKIP_UNSUPPORTED_COMPILER_CHECK ``` +## Examples + +??? example + + The code below switches off the check whether the compiler is supported. + + ```cpp + #define JSON_SKIP_UNSUPPORTED_COMPILER_CHECK 1 + #include + + ... + ``` + ## Version history Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md b/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md index 826785292..d7a314276 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md @@ -13,6 +13,23 @@ These macros are defined by the library and contain the version numbers accordin The macros are defined according to the current library version. +## Examples + +??? example + + The example below shows how `NLOHMANN_JSON_VERSION_MAJOR`, `NLOHMANN_JSON_VERSION_MINOR`, and + `NLOHMANN_JSON_VERSION_PATCH` are defined by the library. + + ```cpp + --8<-- "examples/nlohmann_json_version.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_json_version.output" + ``` + ## See also - [meta](../basic_json/meta.md) - returns version information on the library diff --git a/docs/mkdocs/docs/api/ordered_json.md b/docs/mkdocs/docs/api/ordered_json.md index 8b122f90b..7cfd9f4dd 100644 --- a/docs/mkdocs/docs/api/ordered_json.md +++ b/docs/mkdocs/docs/api/ordered_json.md @@ -6,9 +6,26 @@ using ordered_json = basic_json; This type preserves the insertion order of object keys. +## Examples + +??? example + + The example below demonstrates how `ordered_json` preserves the insertion order of object keys. + + ```cpp + --8<-- "examples/ordered_json.cpp" + ``` + + Output: + + ```json + --8<-- "examples/ordered_json.output" + ``` + ## See also - [ordered_map](ordered_map.md) +- [Object Order](../features/object_order.md) ## Version history diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md new file mode 100644 index 000000000..fae55658f --- /dev/null +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -0,0 +1,209 @@ +# BJData + +The [BJData format](https://neurojson.org) was derived from and improved upon +[Universal Binary JSON(UBJSON)](https://ubjson.org) specification (Draft 12). +Specifically, it introduces an optimized array container for efficient storage +of N-dimensional packed arrays (**ND-arrays**); it also adds 4 new type markers - +`[u] - uint16`, `[m] - uint32`, `[M] - uint64` and `[h] - float16` - to +unambigiously map common binary numeric types; furthermore, it uses little-endian +(LE) to store all numerics instead of big-endian (BE) as in UBJSON to avoid +unnecessary conversions on commonly available platforms. + +Compared to other binary-JSON-like formats such as MessagePack and CBOR, both BJData and +UBJSON demonstrate a rare combination of being both binary and **quasi-human-readable**. This +is because all semantic elements in BJData and UBJSON, including the data-type markers +and name/string types are directly human-readable. Data stored in the BJData/UBJSON format +are not only compact in size, fast to read/write, but also can be directly searched +or read using simple processing. + +!!! abstract "References" + + - [BJData Specification](https://neurojson.org/bjdata/draft2) + +## Serialization + +The library uses the following mapping from JSON values types to BJData types according to the BJData specification: + +| JSON value type | value/range | BJData type | marker | +|-----------------|-------------------------------------------|----------------|--------| +| null | `null` | null | `Z` | +| boolean | `true` | true | `T` | +| boolean | `false` | false | `F` | +| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | +| number_integer | -2147483648..-32769 | int32 | `l` | +| number_integer | -32768..-129 | int16 | `I` | +| number_integer | -128..127 | int8 | `i` | +| number_integer | 128..255 | uint8 | `U` | +| number_integer | 256..32767 | int16 | `I` | +| number_integer | 32768..65535 | uint16 | `u` | +| number_integer | 65536..2147483647 | int32 | `l` | +| number_integer | 2147483648..4294967295 | uint32 | `m` | +| number_integer | 4294967296..9223372036854775807 | int64 | `L` | +| number_integer | 9223372036854775808..18446744073709551615 | uint64 | `M` | +| number_unsigned | 0..127 | int8 | `i` | +| number_unsigned | 128..255 | uint8 | `U` | +| number_unsigned | 256..32767 | int16 | `I` | +| number_unsigned | 32768..65535 | uint16 | `u` | +| number_unsigned | 65536..2147483647 | int32 | `l` | +| number_unsigned | 2147483648..4294967295 | uint32 | `m` | +| number_unsigned | 4294967296..9223372036854775807 | int64 | `L` | +| number_unsigned | 9223372036854775808..18446744073709551615 | uint64 | `M` | +| number_float | *any value* | float64 | `D` | +| string | *with shortest length indicator* | string | `S` | +| array | *see notes on optimized format/ND-array* | array | `[` | +| object | *see notes on optimized format* | map | `{` | + +!!! success "Complete mapping" + + The mapping is **complete** in the sense that any JSON value type can be converted to a BJData value. + + Any BJData output created by `to_bjdata` can be successfully parsed by `from_bjdata`. + +!!! warning "Size constraints" + + The following values can **not** be converted to a BJData value: + + - strings with more than 18446744073709551615 bytes (theoretical) + +!!! info "Unused BJData markers" + + The following markers are not used in the conversion: + + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + +!!! info "NaN/infinity handling" + + If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the `dump()` + function which serializes NaN or Infinity to `null`. + + +!!! info "Endianness" + + A breaking difference between BJData and UBJSON is the endianness + of numerical values. In BJData, all numerical data types (integers + `UiuImlML` and floating-point values `hdD`) are stored in the little-endian (LE) + byte order as opposed to big-endian as used by UBJSON. To adopt LE + to store numeric records avoids unnecessary byte swapping on most modern + computers where LE is used as the default byte order. + +!!! info "Optimized formats" + + The optimized formats for containers are supported: Parameter + `use_size` adds size information to the beginning of a container and + removes the closing marker. Parameter `use_type` further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The `use_type` + parameter must only be used together with `use_size = true`. + + Note that `use_size = true` alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + +!!! info "ND-array optimized format" + + BJData extends UBJSON's optimized array **size** marker to support + ND-array of uniform numerical data types (referred to as the *packed array*). + For example, 2-D `uint8` integer array `[[1,2],[3,4],[5,6]]` that can be stored + as nested optimized array in UBJSON `[ [$U#i2 1 2 [$U#i2 3 4 [$U#i2 5 6 ]`, + can be further compressed in BJData and stored as `[$U#[$i#i2 2 3 1 2 3 4 5 6` + or `[$U#[i2 i3] 1 2 3 4 5 6`. + + In order to maintain the type and dimension information of an ND-array, + when this library parses a BJData ND-array via `from_bjdata`, it converts the + data into a JSON object, following the **annotated array format** as defined in the + [JData specification (Draft 3)](https://github.com/NeuroJSON/jdata/blob/master/JData_specification.md#annotated-storage-of-n-d-arrays). + For example, the above 2-D `uint8` array can be parsed and accessed as + + ```json + { + "_ArrayType_": "uint8", + "_ArraySize_": [2,3], + "_ArrayData_": [1,2,3,4,5,6] + } + ``` + + In the reversed direction, when `to_bjdata` detects a JSON object in the + above form, it automatically converts such object into a BJData ND-array + to generate compact output. The only exception is that when the 1-D dimensional + vector stored in `"_ArraySize_"` contains a single integer, or two integers with + one being 1, a regular 1-D optimized array is generated. + + The current version of this library has not yet supported automatic + recognition and conversion from a nested JSON array input to a BJData ND-array. + +!!! info "Restrictions in optimized data types for arrays and objects" + + Due to diminished space saving, hampered readability, and increased + security risks, in BJData, the allowed data types following the `$` marker + in an optimized array and object container are restricted to + **non-zero-fixed-length** data types. Therefore, the valid optimized + type markers can only be one of `UiuImlMLhdDC`. This also means other + variable (`[{SH`) or zero-length types (`TFN`) can not be used in an + optimized array or object in BJData. + +!!! info "Binary values" + + If the JSON data contains the binary type, the value stored is a list + of integers, as suggested by the BJData documentation. In particular, + this means that serialization and the deserialization of a JSON + containing binary values into BJData and back will result in a + different JSON object. + + +??? example + + ```cpp + --8<-- "examples/to_bjdata.cpp" + ``` + + Output: + + ```c + --8<-- "examples/to_bjdata.output" + ``` + +## Deserialization + +The library maps BJData types to JSON value types as follows: + +| BJData type | JSON value type | marker | +|-------------|-----------------------------------------|--------| +| no-op | *no value, next value is read* | `N` | +| null | `null` | `Z` | +| false | `false` | `F` | +| true | `true` | `T` | +| float16 | number_float | `h` | +| float32 | number_float | `d` | +| float64 | number_float | `D` | +| uint8 | number_unsigned | `U` | +| int8 | number_integer | `i` | +| uint16 | number_unsigned | `u` | +| int16 | number_integer | `I` | +| uint32 | number_unsigned | `m` | +| int32 | number_integer | `l` | +| uint64 | number_unsigned | `M` | +| int64 | number_integer | `L` | +| string | string | `S` | +| char | string | `C` | +| array | array (optimized values are supported) | `[` | +| ND-array | object (in JData annotated array format)|`[$.#[.`| +| object | object (optimized values are supported) | `{` | + +!!! success "Complete mapping" + + The mapping is **complete** in the sense that any BJData value can be converted to a JSON value. + + +??? example + + ```cpp + --8<-- "examples/from_bjdata.cpp" + ``` + + Output: + + ```json + --8<-- "examples/from_bjdata.output" + ``` diff --git a/docs/mkdocs/docs/features/binary_formats/index.md b/docs/mkdocs/docs/features/binary_formats/index.md index 3a969a5dc..e74290b09 100644 --- a/docs/mkdocs/docs/features/binary_formats/index.md +++ b/docs/mkdocs/docs/features/binary_formats/index.md @@ -1,7 +1,9 @@ # Binary Formats -Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports +Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over +a network. Hence, the library supports +- [BJData](bjdata.md) (Binary JData), - [BSON](bson.md) (Binary JSON), - [CBOR](cbor.md) (Concise Binary Object Representation), - [MessagePack](messagepack.md), and @@ -15,6 +17,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors. | Format | Serialization | Deserialization | |-------------|-----------------------------------------------|----------------------------------------------| +| BJData | complete | complete | | BSON | incomplete: top-level value must be an object | incomplete, but all JSON types are supported | | CBOR | complete | incomplete, but all JSON types are supported | | MessagePack | complete | complete | @@ -24,6 +27,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors. | Format | Binary values | Binary subtypes | |-------------|---------------|-----------------| +| BJData | not supported | not supported | | BSON | supported | supported | | CBOR | supported | supported | | MessagePack | supported | supported | @@ -35,11 +39,14 @@ See [binary values](../binary_values.md) for more information. | Format | canada.json | twitter.json | citm_catalog.json | jeopardy.json | |--------------------|-------------|--------------|-------------------|---------------| -| BSON | 85,8 % | 95,2 % | 95,8 % | 106,7 % | -| CBOR | 50,5 % | 86,3 % | 68,4 % | 88,0 % | -| MessagePack | 50,6 % | 86,0 % | 68,5 % | 87,9 % | -| UBJSON | 53,2 % | 91,3 % | 78,2 % | 96,6 % | -| UBJSON (size) | 58,6 % | 92,3 % | 86,8 % | 97,4 % | -| UBJSON (size+type) | 55,9 % | 92,3 % | 85,0 % | 95,0 % | +| BJData | 53.2 % | 91.1 % | 78.1 % | 96.6 % | +| BJData (size) | 58.6 % | 92.1 % | 86.7 % | 97.4 % | +| BJData (size+tyoe) | 58.6 % | 92.1 % | 86.5 % | 97.4 % | +| BSON | 85.8 % | 95.2 % | 95.8 % | 106.7 % | +| CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % | +| MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % | +| UBJSON | 53.2 % | 91.3 % | 78.2 % | 96.6 % | +| UBJSON (size) | 58.6 % | 92.3 % | 86.8 % | 97.4 % | +| UBJSON (size+type) | 55.9 % | 92.3 % | 85.0 % | 95.0 % | Sizes compared to minified JSON value. diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index c58834c05..5ad6433cf 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -1,8 +1,12 @@ # Binary Values -The library implements several [binary formats](binary_formats/index.md) that encode JSON in an efficient way. Most of these formats support binary values; that is, values that have semantics define outside the library and only define a sequence of bytes to be stored. +The library implements several [binary formats](binary_formats/index.md) that encode JSON in an efficient way. Most of +these formats support binary values; that is, values that have semantics define outside the library and only define a +sequence of bytes to be stored. -JSON itself does not have a binary value. As such, binary values are an extension that this library implements to store values received by a binary format. Binary values are never created by the JSON parser, and are only part of a serialized JSON text if they have been created manually or via a binary format. +JSON itself does not have a binary value. As such, binary values are an extension that this library implements to store +values received by a binary format. Binary values are never created by the JSON parser, and are only part of a +serialized JSON text if they have been created manually or via a binary format. ## API for binary values @@ -19,7 +23,9 @@ class json::binary_t { "std::vector" <|-- json::binary_t ``` -By default, binary values are stored as `std::vector`. This type can be changed by providing a template parameter to the `basic_json` type. To store binary subtypes, the storage type is extended and exposed as `json::binary_t`: +By default, binary values are stored as `std::vector`. This type can be changed by providing a template +parameter to the `basic_json` type. To store binary subtypes, the storage type is extended and exposed as +`json::binary_t`: ```cpp auto binary = json::binary_t({0xCA, 0xFE, 0xBA, 0xBE}); @@ -87,7 +93,9 @@ Binary values are serialized differently according to the formats. ### JSON -JSON does not have a binary type, and this library does not introduce a new type as this would break conformance. Instead, binary values are serialized as an object with two keys: `bytes` holds an array of integers, and `subtype` is an integer or `null`. +JSON does not have a binary type, and this library does not introduce a new type as this would break conformance. +Instead, binary values are serialized as an object with two keys: `bytes` holds an array of integers, and `subtype` +is an integer or `null`. ??? example @@ -115,11 +123,72 @@ JSON does not have a binary type, and this library does not introduce a new type !!! warning "No roundtrip for binary values" - The JSON parser will not parse the objects generated by binary values back to binary values. This is by design to remain standards compliant. Serializing binary values to JSON is only implemented for debugging purposes. + The JSON parser will not parse the objects generated by binary values back to binary values. This is by design to + remain standards compliant. Serializing binary values to JSON is only implemented for debugging purposes. + +### BJData + +[BJData](binary_formats/bjdata.md) neither supports binary values nor subtypes, and proposes to serialize binary values +as array of uint8 values. This translation is implemented by the library. + +??? example + + Code: + + ```cpp + // create a binary value of subtype 42 (will be ignored in BJData) + json j; + j["binary"] = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 42); + + // convert to BJData + auto v = json::to_bjdata(j); + ``` + + `v` is a `std::vector` with the following 20 elements: + + ```c + 0x7B // '{' + 0x69 0x06 // i 6 (length of the key) + 0x62 0x69 0x6E 0x61 0x72 0x79 // "binary" + 0x5B // '[' + 0x55 0xCA 0x55 0xFE 0x55 0xBA 0x55 0xBE // content (each byte prefixed with 'U') + 0x5D // ']' + 0x7D // '}' + ``` + + The following code uses the type and size optimization for UBJSON: + + ```cpp + // convert to UBJSON using the size and type optimization + auto v = json::to_bjdata(j, true, true); + ``` + + The resulting vector has 22 elements; the optimization is not effective for examples with few values: + + ```c + 0x7B // '{' + 0x23 0x69 0x01 // '#' 'i' type of the array elements: unsigned integers + 0x69 0x06 // i 6 (length of the key) + 0x62 0x69 0x6E 0x61 0x72 0x79 // "binary" + 0x5B // '[' array + 0x24 0x55 // '$' 'U' type of the array elements: unsigned integers + 0x23 0x69 0x04 // '#' i 4 number of array elements + 0xCA 0xFE 0xBA 0xBE // content + ``` + + Note that subtype (42) is **not** serialized and that UBJSON has **no binary type**, and deserializing `v` would + yield the following value: + + ```json + { + "binary": [202, 254, 186, 190] + } + ``` ### BSON -[BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00 is used. +[BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as +unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00 is used. ??? example @@ -159,7 +228,9 @@ JSON does not have a binary type, and this library does not introduce a new type ### CBOR -[CBOR](binary_formats/cbor.md) supports binary values, but no subtypes. Subtypes will be serialized as tags. Any binary value will be serialized as byte strings. The library will choose the smallest representation using the length of the byte array. +[CBOR](binary_formats/cbor.md) supports binary values, but no subtypes. Subtypes will be serialized as tags. Any binary +value will be serialized as byte strings. The library will choose the smallest representation using the length of the +byte array. ??? example @@ -185,7 +256,8 @@ JSON does not have a binary type, and this library does not introduce a new type 0xCA 0xFE 0xBA 0xBE // content ``` - Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless `json::cbor_tag_handler_t::ignore` or `json::cbor_tag_handler_t::store` is passed to `json::from_cbor`. + Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless + `json::cbor_tag_handler_t::ignore` or `json::cbor_tag_handler_t::store` is passed to `json::from_cbor`. ```json { @@ -198,7 +270,9 @@ JSON does not have a binary type, and this library does not introduce a new type ### MessagePack -[MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, ext8, ext16, and ext32. The subtype is then added as signed 8-bit integer. +[MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family +is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, ext8, ext16, and +ext32. The subtype is then added as signed 8-bit integer. If no subtype is given, the bin family (bin8, bin16, bin32) is used. @@ -239,7 +313,8 @@ If no subtype is given, the bin family (bin8, bin16, bin32) is used. ### UBJSON -[UBJSON](binary_formats/ubjson.md) neither supports binary values nor subtypes, and proposes to serialize binary values as array of uint8 values. This translation is implemented by the library. +[UBJSON](binary_formats/ubjson.md) neither supports binary values nor subtypes, and proposes to serialize binary values +as array of uint8 values. This translation is implemented by the library. ??? example @@ -251,7 +326,7 @@ If no subtype is given, the bin family (bin8, bin16, bin32) is used. j["binary"] = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 42); // convert to UBJSON - auto v = json::to_msgpack(j); + auto v = json::to_ubjson(j); ``` `v` is a `std::vector` with the following 20 elements: @@ -287,7 +362,8 @@ If no subtype is given, the bin family (bin8, bin16, bin32) is used. 0xCA 0xFE 0xBA 0xBE // content ``` - Note that subtype (42) is **not** serialized and that UBJSON has **no binary type**, and deserializing `v` would yield the following value: + Note that subtype (42) is **not** serialized and that UBJSON has **no binary type**, and deserializing `v` would + yield the following value: ```json { diff --git a/docs/mkdocs/docs/features/element_access/checked_access.md b/docs/mkdocs/docs/features/element_access/checked_access.md index fd444bebe..c4023cce5 100644 --- a/docs/mkdocs/docs/features/element_access/checked_access.md +++ b/docs/mkdocs/docs/features/element_access/checked_access.md @@ -2,9 +2,11 @@ ## Overview -The `#!cpp at()` member function performs checked access; that is, it returns a reference to the desired value if it exists and throws a [`basic_json::out_of_range` exception](../../home/exceptions.md#out-of-range) otherwise. +The [`at`](../../api/basic_json/at.md) member function performs checked access; that is, it returns a reference to the +desired value if it exists and throws a [`basic_json::out_of_range` exception](../../home/exceptions.md#out-of-range) +otherwise. -??? example +??? example "Read access" Consider the following JSON value: @@ -18,18 +20,18 @@ The `#!cpp at()` member function performs checked access; that is, it returns a Assume the value is parsed to a `json` variable `j`. - | expression | value | - | ---------- | ----- | - | `#!cpp j` | `#!json {"name": "Mary Smith", "age": 42, "hobbies": ["hiking", "reading"]}` | - | `#!cpp j.at("name")` | `#!json "Mary Smith"` | - | `#!cpp j.at("age")` | `#!json 42` | - | `#!cpp j.at("hobbies")` | `#!json ["hiking", "reading"]` | - | `#!cpp j.at("hobbies").at(0)` | `#!json "hiking"` | - | `#!cpp j.at("hobbies").at(1)` | `#!json "reading"` | + | expression | value | + |-------------------------------|------------------------------------------------------------------------------| + | `#!cpp j` | `#!json {"name": "Mary Smith", "age": 42, "hobbies": ["hiking", "reading"]}` | + | `#!cpp j.at("name")` | `#!json "Mary Smith"` | + | `#!cpp j.at("age")` | `#!json 42` | + | `#!cpp j.at("hobbies")` | `#!json ["hiking", "reading"]` | + | `#!cpp j.at("hobbies").at(0)` | `#!json "hiking"` | + | `#!cpp j.at("hobbies").at(1)` | `#!json "reading"` | The return value is a reference, so it can be modified by the original value. -??? example +??? example "Write access" ```cpp j.at("name") = "John Smith"; @@ -45,9 +47,10 @@ The return value is a reference, so it can be modified by the original value. } ``` -When accessing an invalid index (i.e., an index greater than or equal to the array size) or the passed object key is non-existing, an exception is thrown. +When accessing an invalid index (i.e., an index greater than or equal to the array size) or the passed object key is +non-existing, an exception is thrown. -??? example +??? example "Accessing via invalid index or missing key" ```cpp j.at("hobbies").at(3) = "cooking"; @@ -59,13 +62,24 @@ When accessing an invalid index (i.e., an index greater than or equal to the arr [json.exception.out_of_range.401] array index 3 is out of range ``` + When you [extended diagnostic messages](../../home/exceptions.md#extended-diagnostic-messages) are enabled by + defining [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md), the exception further gives information where + the key or index is missing or out of range. + + ``` + [json.exception.out_of_range.401] (/hobbies) array index 3 is out of range + ``` + ## Notes !!! failure "Exceptions" - - `at` can only be used with objects (with a string argument) or with arrays (with a numeric argument). For other types, a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error304) is thrown. - - [`basic_json::out_of_range` exception](../../home/exceptions.md#out-of-range) exceptions are thrown if the provided key is not found in an object or the provided index is invalid. + - [`at`](../../api/basic_json/at.md) can only be used with objects (with a string argument) or with arrays (with a + numeric argument). For other types, a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error304) + is thrown. + - [`basic_json::out_of_range` exception](../../home/exceptions.md#out-of-range) exceptions are thrown if the + provided key is not found in an object or the provided index is invalid. ## Summary diff --git a/docs/mkdocs/docs/features/element_access/unchecked_access.md b/docs/mkdocs/docs/features/element_access/unchecked_access.md index 4fdef35c7..1bdea94e7 100644 --- a/docs/mkdocs/docs/features/element_access/unchecked_access.md +++ b/docs/mkdocs/docs/features/element_access/unchecked_access.md @@ -2,9 +2,10 @@ ## Overview -Elements in a JSON object and a JSON array can be accessed via `#!cpp operator[]` similar to a `#!cpp std::map` and a `#!cpp std::vector`, respectively. +Elements in a JSON object and a JSON array can be accessed via [`operator[]`](../../api/basic_json/operator%5B%5D.md) +similar to a `#!cpp std::map` and a `#!cpp std::vector`, respectively. -??? example +??? example "Read access" Consider the following JSON value: @@ -18,18 +19,19 @@ Elements in a JSON object and a JSON array can be accessed via `#!cpp operator[] Assume the value is parsed to a `json` variable `j`. - | expression | value | - | ---------- | ----- | - | `#!cpp j` | `#!json {"name": "Mary Smith", "age": 42, "hobbies": ["hiking", "reading"]}` | - | `#!cpp j["name"]` | `#!json "Mary Smith"` | - | `#!cpp j["age"]` | `#!json 42` | - | `#!cpp j["hobbies"]` | `#!json ["hiking", "reading"]` | - | `#!cpp j["hobbies"][0]` | `#!json "hiking"` | - | `#!cpp j["hobbies"][1]` | `#!json "reading"` | + | expression | value | + |-------------------------|------------------------------------------------------------------------------| + | `#!cpp j` | `#!json {"name": "Mary Smith", "age": 42, "hobbies": ["hiking", "reading"]}` | + | `#!cpp j["name"]` | `#!json "Mary Smith"` | + | `#!cpp j["age"]` | `#!json 42` | + | `#!cpp j["hobbies"]` | `#!json ["hiking", "reading"]` | + | `#!cpp j["hobbies"][0]` | `#!json "hiking"` | + | `#!cpp j["hobbies"][1]` | `#!json "reading"` | -The return value is a reference, so it can modify the original value. In case the passed object key is non-existing, a `#!json null` value is inserted which can be immediately be overwritten. +The return value is a reference, so it can modify the original value. In case the passed object key is non-existing, a +`#!json null` value is inserted which can be immediately be overwritten. -??? example +??? example "Write access" ```cpp j["name"] = "John Smith"; @@ -47,9 +49,10 @@ The return value is a reference, so it can modify the original value. In case th } ``` -When accessing an invalid index (i.e., an index greater than or equal to the array size), the JSON array is resized such that the passed index is the new maximal index. Intermediate values are filled with `#!json null`. +When accessing an invalid index (i.e., an index greater than or equal to the array size), the JSON array is resized such +that the passed index is the new maximal index. Intermediate values are filled with `#!json null`. -??? example +??? example "Filling up arrays with `#!json null` values" ```cpp j["hobbies"][0] = "running"; @@ -76,17 +79,23 @@ When accessing an invalid index (i.e., an index greater than or equal to the arr - `#!cpp std::vector::operator[]` never inserts a new element. - `#!cpp std::map::operator[]` is not available for const values. - The type `#!cpp json` wraps all JSON value types. It would be impossible to remove `operator[]` for const objects. At the same time, inserting elements for non-const objects is really convenient as it avoids awkward `insert` calls. To this end, we decided to have an inserting non-const behavior for both arrays and objects. + The type `#!cpp json` wraps all JSON value types. It would be impossible to remove + [`operator[]`](../../api/basic_json/operator%5B%5D.md) for const objects. At the same time, inserting elements for + non-const objects is really convenient as it avoids awkward `insert` calls. To this end, we decided to have an + inserting non-const behavior for both arrays and objects. !!! info - The access is unchecked. In case the passed object key does not exist or the passed array index is invalid, no exception is thrown. + The access is unchecked. In case the passed object key does not exist or the passed array index is invalid, no + exception is thrown. !!! danger - It is **undefined behavior** to access a const object with a non-existing key. - It is **undefined behavior** to access a const array with an invalid index. - - In debug mode, an **assertion** will fire in both cases. You can disable assertions by defining the preprocessor symbol `#!cpp NDEBUG` or redefine the macro [`JSON_ASSERT(x)`](../macros.md#json_assertx). + - In debug mode, an **assertion** will fire in both cases. You can disable assertions by defining the preprocessor + symbol `#!cpp NDEBUG` or redefine the macro [`JSON_ASSERT(x)`](../macros.md#json_assertx). See the documentation + on [runtime assertions](../assertions.md) for more information. !!! failure "Exceptions" diff --git a/docs/mkdocs/docs/features/iterators.md b/docs/mkdocs/docs/features/iterators.md index 46f28f189..ce627e012 100644 --- a/docs/mkdocs/docs/features/iterators.md +++ b/docs/mkdocs/docs/features/iterators.md @@ -113,7 +113,7 @@ for (auto& [key, val] : j_object.items()) ```cpp json j = {1, 2, 3, 4}; - for (auto it = j.begin(); it != j.end(); ++it) + for (auto it = j.rbegin(); it != j.rend(); ++it) { std::cout << *it << std::endl; } diff --git a/docs/mkdocs/docs/features/object_order.md b/docs/mkdocs/docs/features/object_order.md index 0768f8020..3ee16a90e 100644 --- a/docs/mkdocs/docs/features/object_order.md +++ b/docs/mkdocs/docs/features/object_order.md @@ -42,30 +42,13 @@ If you do want to preserve the **insertion order**, you can try the type [`nlohm ??? example ```cpp - #include - #include - - using ordered_json = nlohmann::ordered_json; - - int main() - { - ordered_json j; - j["one"] = 1; - j["two"] = 2; - j["three"] = 3; - - std::cout << j.dump(2) << '\n'; - } + --8<-- "examples/ordered_json.cpp" ``` Output: ```json - { - "one": 1, - "two": 2, - "three": 3 - } + --8<-- "examples/ordered_json.output" ``` Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)). diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 666d225bd..c9885ae6f 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -271,7 +271,7 @@ In a JSON Pointer, only `~0` and `~1` are valid escape sequences. A JSON Pointer array index must be a number. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.parse_error.109] parse error: array index 'one' is not a number @@ -295,19 +295,34 @@ When parsing CBOR or MessagePack, the byte vector ends before the complete value ### json.exception.parse_error.112 -Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +An unexpected byte was read in a [binary format](../features/binary_formats/index.md) or length information is invalid ([BSON](../features/binary_formats/bson.md)). -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0x1C ``` + ``` + [json.exception.parse_error.112] parse error at byte 1: syntax error while parsing MessagePack value: invalid byte: 0xC1 + ``` + ``` + [json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02 + ``` + ``` + [json.exception.parse_error.112] parse error at byte 4: syntax error while parsing UBJSON size: expected '#' after type information; last byte: 0x02 + ``` + ``` + [json.exception.parse_error.112] parse error at byte 10: syntax error while parsing BSON string: string length must be at least 1, is -2147483648 + ``` + ``` + [json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1 + ``` ### json.exception.parse_error.113 While parsing a map key, a value that is not a string has been read. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF @@ -374,7 +389,7 @@ The iterators passed to constructor `basic_json(InputIT first, InputIT last)` ar In the [erase](../api/basic_json/erase.md) or insert function, the passed iterator `pos` does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.invalid_iterator.202] iterator does not fit current value @@ -538,7 +553,7 @@ To create an object from an initializer list, the initializer list must consist During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.type_error.302] type must be object, but is null @@ -551,7 +566,7 @@ During implicit or explicit value conversion, the JSON type must be compatible t To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is object @@ -564,7 +579,7 @@ To retrieve a reference to a value stored in a `basic_json` object with `get_ref The `at()` member functions can only be executed for certain JSON types. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.type_error.304] cannot use at() with string @@ -577,7 +592,7 @@ The `at()` member functions can only be executed for certain JSON types. The `operator[]` member functions can only be executed for certain JSON types. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.type_error.305] cannot use operator[] with a string argument with array @@ -620,7 +635,7 @@ The `push_back()` and `operator+=` member functions can only be executed for cer The `insert()` member functions can only be executed for certain JSON types. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.type_error.309] cannot use insert() with array @@ -643,7 +658,7 @@ The `swap()` member functions can only be executed for certain JSON types. The `emplace()` and `emplace_back()` member functions can only be executed for certain JSON types. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.type_error.311] cannot use emplace() with number @@ -718,7 +733,7 @@ The `dump()` function only works with UTF-8 encoded strings; that is, if you ass The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) -!!! failure "Example message" +!!! failure "Example messages" Serializing `#!json null` to BSON: ``` diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 38a8cadfa..dd4da8a0c 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -42,6 +42,7 @@ nav: - features/arbitrary_types.md - Binary Formats: - features/binary_formats/index.md + - features/binary_formats/bjdata.md - features/binary_formats/bson.md - features/binary_formats/cbor.md - features/binary_formats/messagepack.md @@ -109,6 +110,7 @@ nav: - 'exception': api/basic_json/exception.md - 'find': api/basic_json/find.md - 'flatten': api/basic_json/flatten.md + - 'from_bjdata': api/basic_json/from_bjdata.md - 'from_bson': api/basic_json/from_bson.md - 'from_cbor': api/basic_json/from_cbor.md - 'from_msgpack': api/basic_json/from_msgpack.md @@ -178,6 +180,7 @@ nav: - 'string_t': api/basic_json/string_t.md - 'swap': api/basic_json/swap.md - 'std::swap<basic_json>': api/basic_json/std_swap.md + - 'to_bjdata': api/basic_json/to_bjdata.md - 'to_bson': api/basic_json/to_bson.md - 'to_cbor': api/basic_json/to_cbor.md - 'to_msgpack': api/basic_json/to_msgpack.md diff --git a/docs/mkdocs/scripts/check_structure.py b/docs/mkdocs/scripts/check_structure.py index 92f6ff182..56de6e89f 100755 --- a/docs/mkdocs/scripts/check_structure.py +++ b/docs/mkdocs/scripts/check_structure.py @@ -47,14 +47,17 @@ def check_structure(): files = sorted(glob.glob('api/**/*.md', recursive=True)) for file in files: with open(file) as file_content: - section_idx = -1 - existing_sections = [] - in_initial_code_example = False - previous_line = None - h1sections = 0 + section_idx = -1 # the index of the current h2 section + existing_sections = [] # the list of h2 sections in the file + in_initial_code_example = False # whether we are inside the first code example block + previous_line = None # the previous read line + h1sections = 0 # the number of h1 sections in the file + last_overload = 0 # the last seen overload number in the code example + documented_overloads = {} # the overloads that have been documented in the current block + current_section = None # the name of the current section - for lineno, line in enumerate(file_content.readlines()): - line = line.strip() + for lineno, original_line in enumerate(file_content.readlines()): + line = original_line.strip() if line.startswith('# '): h1sections += 1 @@ -70,10 +73,21 @@ def check_structure(): # lines longer than 160 characters are bad (unless they are tables) if len(line) > 160 and '|' not in line: - report('whitespace/line_length', f'{file}:{lineno+1}', f'line is too long ({len(line)} vs. 160 chars)') + report('whitespace/line_length', f'{file}:{lineno+1} ({current_section})', f'line is too long ({len(line)} vs. 160 chars)') # check if sections are correct if line.startswith('## '): + # before starting a new section, check if the previous one documented all overloads + if current_section in documented_overloads and last_overload != 0: + if len(documented_overloads[current_section]) > 0 and len(documented_overloads[current_section]) != last_overload: + expected = list(range(1, last_overload+1)) + undocumented = [x for x in expected if x not in documented_overloads[current_section]] + unexpected = [x for x in documented_overloads[current_section] if x not in expected] + if len(undocumented): + report('style/numbering', f'{file}:{lineno} ({current_section})', f'undocumented overloads: {", ".join([f"({x})" for x in undocumented])}') + if len(unexpected): + report('style/numbering', f'{file}:{lineno} ({current_section})', f'unexpected overloads: {", ".join([f"({x})" for x in unexpected])}') + current_section = line.strip('## ') existing_sections.append(current_section) @@ -83,13 +97,29 @@ def check_structure(): report('structure/section_order', f'{file}:{lineno+1}', f'section "{current_section}" is in an unexpected order (should be before "{expected_sections[section_idx]}")') section_idx = idx else: - report('structure/unknown_section', f'{file}:{lineno+1}', f'section "{current_section}" is not part of the expected sections') + if 'index.md' not in file: # index.md files may have a different structure + report('structure/unknown_section', f'{file}:{lineno+1}', f'section "{current_section}" is not part of the expected sections') + + # collect the numbered items of the current section to later check if they match the number of overloads + if last_overload != 0 and not in_initial_code_example: + if len(original_line) and original_line[0].isdigit(): + number = int(re.findall(r"^(\d+).", original_line)[0]) + if current_section not in documented_overloads: + documented_overloads[current_section] = [] + documented_overloads[current_section].append(number) # code example if line == '```cpp' and section_idx == -1: in_initial_code_example = True if in_initial_code_example and line.startswith('//'): + # check numbering of overloads + if any(map(str.isdigit, line)): + number = int(re.findall(r'\d+', line)[0]) + if number != last_overload + 1: + report('style/numbering', f'{file}:{lineno+1}', f'expected number ({number}) to be ({last_overload +1 })') + last_overload = number + if any(map(str.isdigit, line)) and '(' not in line: report('style/numbering', f'{file}:{lineno+1}', 'number should be in parentheses: {line}') @@ -98,18 +128,19 @@ def check_structure(): # consecutive blank lines are bad if line == '' and previous_line == '': - report('whitespace/blank_lines', f'{file}:{lineno}-{lineno+1}', 'consecutive blank lines') + report('whitespace/blank_lines', f'{file}:{lineno}-{lineno+1} ({current_section})', 'consecutive blank lines') # check that non-example admonitions have titles untitled_admonition = re.match(r'^(\?\?\?|!!!) ([^ ]+)$', line) if untitled_admonition and untitled_admonition.group(2) != 'example': - report('style/admonition_title', f'{file}:{lineno}', f'"{untitled_admonition.group(2)}" admonitions should have a title') + report('style/admonition_title', f'{file}:{lineno} ({current_section})', f'"{untitled_admonition.group(2)}" admonitions should have a title') previous_line = line - for required_section in required_sections: - if required_section not in existing_sections: - report('structure/missing_section', f'{file}:{lineno+1}', f'required section "{required_section}" was not found') + if 'index.md' not in file: # index.md files may have a different structure + for required_section in required_sections: + if required_section not in existing_sections: + report('structure/missing_section', f'{file}:{lineno+1}', f'required section "{required_section}" was not found') def check_examples(): diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index aed4a1a01..fb0f5fc18 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4403,29 +4403,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bjdata(const T* ptr, std::size_t len, - const bool strict = true, - const bool allow_exceptions = true) - { - return from_bjdata(ptr, ptr + len, strict, allow_exceptions); - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bjdata(detail::span_input_adapter&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = i.get(); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a29f7bce9..e2cd6bc2d 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -22512,29 +22512,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res ? result : basic_json(value_t::discarded); } - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bjdata(const T* ptr, std::size_t len, - const bool strict = true, - const bool allow_exceptions = true) - { - return from_bjdata(ptr, ptr + len, strict, allow_exceptions); - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bjdata(detail::span_input_adapter&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = i.get(); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 75bc6f980..2f6409c57 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -76,7 +76,7 @@ endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # avoid stack overflow, see https://github.com/nlohmann/json/issues/2955 - json_test_set_test_options("test-cbor;test-msgpack;test-ubjson;test-bjdata" LINK_OPTIONS /STACK:4000000) + json_test_set_test_options("test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000) endif() # disable exceptions for test-disabled_exceptions diff --git a/tests/src/unit-binary_formats.cpp b/tests/src/unit-binary_formats.cpp new file mode 100644 index 000000000..05e889b0f --- /dev/null +++ b/tests/src/unit-binary_formats.cpp @@ -0,0 +1,232 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.10.5 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +#include +#include + +TEST_CASE("Binary Formats" * doctest::skip()) +{ + SECTION("canada.json") + { + const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json"; + json j = json::parse(std::ifstream(filename)); + + const auto json_size = j.dump().size(); + const auto bjdata_1_size = json::to_bjdata(j).size(); + const auto bjdata_2_size = json::to_bjdata(j, true).size(); + const auto bjdata_3_size = json::to_bjdata(j, true, true).size(); + const auto bson_size = json::to_bson(j).size(); + const auto cbor_size = json::to_cbor(j).size(); + const auto msgpack_size = json::to_msgpack(j).size(); + const auto ubjson_1_size = json::to_ubjson(j).size(); + const auto ubjson_2_size = json::to_ubjson(j, true).size(); + const auto ubjson_3_size = json::to_ubjson(j, true, true).size(); + + CHECK(json_size == 2090303); + CHECK(bjdata_1_size == 1112030); + CHECK(bjdata_2_size == 1224148); + CHECK(bjdata_3_size == 1224148); + CHECK(bson_size == 1794522); + CHECK(cbor_size == 1055552); + CHECK(msgpack_size == 1056145); + CHECK(ubjson_1_size == 1112030); + CHECK(ubjson_2_size == 1224148); + CHECK(ubjson_3_size == 1169069); + + CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0)); + CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199)); + CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563)); + CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563)); + CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849)); + CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497)); + CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526)); + CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(53.199)); + CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(58.563)); + CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(55.928)); + } + + SECTION("twitter.json") + { + const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json"; + json j = json::parse(std::ifstream(filename)); + + const auto json_size = j.dump().size(); + const auto bjdata_1_size = json::to_bjdata(j).size(); + const auto bjdata_2_size = json::to_bjdata(j, true).size(); + const auto bjdata_3_size = json::to_bjdata(j, true, true).size(); + const auto bson_size = json::to_bson(j).size(); + const auto cbor_size = json::to_cbor(j).size(); + const auto msgpack_size = json::to_msgpack(j).size(); + const auto ubjson_1_size = json::to_ubjson(j).size(); + const auto ubjson_2_size = json::to_ubjson(j, true).size(); + const auto ubjson_3_size = json::to_ubjson(j, true, true).size(); + + CHECK(json_size == 466906); + CHECK(bjdata_1_size == 425342); + CHECK(bjdata_2_size == 429970); + CHECK(bjdata_3_size == 429970); + CHECK(bson_size == 444568); + CHECK(cbor_size == 402814); + CHECK(msgpack_size == 401510); + CHECK(ubjson_1_size == 426160); + CHECK(ubjson_2_size == 430788); + CHECK(ubjson_3_size == 430798); + + CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0)); + CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(91.097)); + CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(92.089)); + CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(92.089)); + CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.215)); + CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(86.273)); + CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(85.993)); + CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(91.273)); + CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(92.264)); + CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(92.266)); + } + + SECTION("citm_catalog.json") + { + const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json"; + json j = json::parse(std::ifstream(filename)); + + const auto json_size = j.dump().size(); + const auto bjdata_1_size = json::to_bjdata(j).size(); + const auto bjdata_2_size = json::to_bjdata(j, true).size(); + const auto bjdata_3_size = json::to_bjdata(j, true, true).size(); + const auto bson_size = json::to_bson(j).size(); + const auto cbor_size = json::to_cbor(j).size(); + const auto msgpack_size = json::to_msgpack(j).size(); + const auto ubjson_1_size = json::to_ubjson(j).size(); + const auto ubjson_2_size = json::to_ubjson(j, true).size(); + const auto ubjson_3_size = json::to_ubjson(j, true, true).size(); + + CHECK(json_size == 500299); + CHECK(bjdata_1_size == 390781); + CHECK(bjdata_2_size == 433557); + CHECK(bjdata_3_size == 432964); + CHECK(bson_size == 479430); + CHECK(cbor_size == 342373); + CHECK(msgpack_size == 342473); + CHECK(ubjson_1_size == 391463); + CHECK(ubjson_2_size == 434239); + CHECK(ubjson_3_size == 425073); + + CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0)); + CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(78.109)); + CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(86.659)); + CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(86.541)); + CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.828)); + CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(68.433)); + CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(68.453)); + CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(78.245)); + CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(86.795)); + CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(84.963)); + } + + SECTION("jeopardy.json") + { + const auto* filename = TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json"; + json j = json::parse(std::ifstream(filename)); + + const auto json_size = j.dump().size(); + const auto bjdata_1_size = json::to_bjdata(j).size(); + const auto bjdata_2_size = json::to_bjdata(j, true).size(); + const auto bjdata_3_size = json::to_bjdata(j, true, true).size(); + const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON + const auto cbor_size = json::to_cbor(j).size(); + const auto msgpack_size = json::to_msgpack(j).size(); + const auto ubjson_1_size = json::to_ubjson(j).size(); + const auto ubjson_2_size = json::to_ubjson(j, true).size(); + const auto ubjson_3_size = json::to_ubjson(j, true, true).size(); + + CHECK(json_size == 52508728); + CHECK(bjdata_1_size == 50710965); + CHECK(bjdata_2_size == 51144830); + CHECK(bjdata_3_size == 51144830); + CHECK(bson_size == 56008520); + CHECK(cbor_size == 46187320); + CHECK(msgpack_size == 46158575); + CHECK(ubjson_1_size == 50710965); + CHECK(ubjson_2_size == 51144830); + CHECK(ubjson_3_size == 49861422); + + CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0)); + CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576)); + CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402)); + CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402)); + CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665)); + CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961)); + CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906)); + CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(96.576)); + CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(97.402)); + CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(94.958)); + } + + SECTION("sample.json") + { + const auto* filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json"; + json j = json::parse(std::ifstream(filename)); + + const auto json_size = j.dump().size(); + const auto bjdata_1_size = json::to_bjdata(j).size(); + const auto bjdata_2_size = json::to_bjdata(j, true).size(); + const auto bjdata_3_size = json::to_bjdata(j, true, true).size(); + // BSON cannot process the file as it contains code point U+0000 + const auto cbor_size = json::to_cbor(j).size(); + const auto msgpack_size = json::to_msgpack(j).size(); + const auto ubjson_1_size = json::to_ubjson(j).size(); + const auto ubjson_2_size = json::to_ubjson(j, true).size(); + const auto ubjson_3_size = json::to_ubjson(j, true, true).size(); + + CHECK(json_size == 168677); + CHECK(bjdata_1_size == 148695); + CHECK(bjdata_2_size == 150569); + CHECK(bjdata_3_size == 150569); + CHECK(cbor_size == 147095); + CHECK(msgpack_size == 147017); + CHECK(ubjson_1_size == 148695); + CHECK(ubjson_2_size == 150569); + CHECK(ubjson_3_size == 150883); + + CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0)); + CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(88.153)); + CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(89.264)); + CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(89.264)); + CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.205)); + CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.158)); + CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(88.153)); + CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(89.264)); + CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450)); + } +} diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index d2fa9a9b2..0a1e6e02d 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3404,21 +3404,6 @@ TEST_CASE("BJData roundtrips" * doctest::skip()) CHECK(j1 == j2); } - { - INFO_WITH_TEMP(filename + ": uint8_t* and size"); - // parse JSON file - std::ifstream f_json(filename); - json j1 = json::parse(f_json); - - // parse BJData file - auto packed = utils::read_binary_file(filename + ".bjdata"); - json j2; - CHECK_NOTHROW(j2 = json::from_bjdata({packed.data(), packed.size()})); - - // compare parsed JSON values - CHECK(j1 == j2); - } - { INFO_WITH_TEMP(filename + ": output to output adapters"); // parse JSON file From 93c9e0c703b98f5082413920a62067ae0b68671e Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Wed, 18 May 2022 12:38:15 -0400 Subject: [PATCH 06/23] Discard optimized containers with negative counts in UBJSON/BJData (#3491,#3492,#3490) (#3500) * Discard optimized containers with negative counts in UBJSON/BJData (#3491,#3492,#3490) * fix msvc error * update unit tests for negative sized containers * use a loop to test 0 ndarray dimension * throw an error when count is negative, merge CHECK_THROW_AS and _WITH with _WITH_AS --- .../nlohmann/detail/input/binary_reader.hpp | 29 +++ single_include/nlohmann/json.hpp | 29 +++ tests/src/unit-bjdata.cpp | 234 +++++++----------- 3 files changed, 149 insertions(+), 143 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 703e6c0f6..10f8f000f 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2020,6 +2020,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return true; } @@ -2031,6 +2036,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); return true; } @@ -2042,6 +2052,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); return true; } @@ -2053,6 +2068,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); return true; } @@ -2120,6 +2140,15 @@ class binary_reader } if (!dim.empty()) // if ndarray, convert to an object in JData annotated array format { + for (auto i : dim) // test if any dimension in an ndarray is 0, if so, return a 1D empty container + { + if ( i == 0 ) + { + result = 0; + return true; + } + } + string_t key = "_ArraySize_"; if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size()))) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e2cd6bc2d..e1c352d82 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10494,6 +10494,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return true; } @@ -10505,6 +10510,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); return true; } @@ -10516,6 +10526,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); return true; } @@ -10527,6 +10542,11 @@ class binary_reader { return false; } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } result = static_cast(number); return true; } @@ -10594,6 +10614,15 @@ class binary_reader } if (!dim.empty()) // if ndarray, convert to an object in JData annotated array format { + for (auto i : dim) // test if any dimension in an ndarray is 0, if so, return a 1D empty container + { + if ( i == 0 ) + { + result = 0; + return true; + } + } + string_t key = "_ArraySize_"; if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size()))) { diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 0a1e6e02d..e9ce1b14e 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -1041,8 +1041,7 @@ TEST_CASE("BJData") { json _; std::vector vec0 = {'h'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vec0), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vec0), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec0), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vec0, true, false).is_discarded()); } @@ -1050,8 +1049,7 @@ TEST_CASE("BJData") { json _; std::vector vec1 = {'h', 0x00}; - CHECK_THROWS_AS(_ = json::from_bjdata(vec1), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vec1), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec1), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vec1, true, false).is_discarded()); } } @@ -2022,9 +2020,8 @@ TEST_CASE("BJData") SECTION("strict mode") { json _; - CHECK_THROWS_AS(_ = json::from_bjdata(vec), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vec), - "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: expected end of input; last byte: 0x5A"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec), + "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: expected end of input; last byte: 0x5A", json::parse_error&); } } } @@ -2363,6 +2360,7 @@ TEST_CASE("BJData") { // create vector with two elements of the same type std::vector v_0 = {'[', '$', 'i', '#', '[', ']'}; + std::vector v_E = {'[', '$', 'i', '#', '[', 'i', 2, 'i', 0, ']'}; std::vector v_i = {'[', '$', 'i', '#', '[', 'i', 1, 'i', 2, ']', 0x7F, 0x7F}; std::vector v_U = {'[', '$', 'U', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0xFF}; std::vector v_I = {'[', '$', 'I', '#', '[', 'i', 1, 'i', 2, ']', 0xFF, 0x7F, 0xFF, 0x7F}; @@ -2377,6 +2375,7 @@ TEST_CASE("BJData") // check if vector is parsed correctly CHECK(json::from_bjdata(v_0) == json::array()); + CHECK(json::from_bjdata(v_E) == json::array()); CHECK(json::from_bjdata(v_i) == json({127, 127})); CHECK(json::from_bjdata(v_U) == json({255, 255})); CHECK(json::from_bjdata(v_I) == json({32767, 32767})); @@ -2437,9 +2436,8 @@ TEST_CASE("BJData") SECTION("empty byte vector") { json _; - CHECK_THROWS_AS(_ = json::from_bjdata(std::vector()), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(std::vector()), - "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(std::vector()), + "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); } SECTION("char") @@ -2448,15 +2446,13 @@ TEST_CASE("BJData") { std::vector v = {'C'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input", json::parse_error&); } SECTION("byte out of range") { std::vector v = {'C', 130}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82"); } } @@ -2467,16 +2463,14 @@ TEST_CASE("BJData") { std::vector v = {'S'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); } SECTION("invalid byte") { std::vector v = {'S', '1', 'a'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&); } SECTION("parse bjdata markers in ubjson") @@ -2488,14 +2482,9 @@ TEST_CASE("BJData") json _; // check if string is parsed correctly to "a" - CHECK_THROWS_AS(_ = json::from_ubjson(s_u), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(s_u), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x75"); - - CHECK_THROWS_AS(_ = json::from_ubjson(s_m), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(s_m), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x6D"); - - CHECK_THROWS_AS(_ = json::from_ubjson(s_M), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(s_M), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4D"); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(s_u), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x75", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(s_m), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x6D", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(s_M), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4D", json::parse_error&); } } @@ -2505,8 +2494,7 @@ TEST_CASE("BJData") { std::vector v = {'[', '$', 'i', 2}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02", json::parse_error&); } SECTION("optimized array: negative size") @@ -2518,26 +2506,37 @@ TEST_CASE("BJData") std::vector v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'}; std::vector v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'}; + std::vector vI = {'[', '#', 'I', 0x00, 0xF1}; + std::vector vl = {'[', '#', 'l', 0x00, 0x00, 0x00, 0xF2}; + std::vector vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3}; + json _; - static bool is_64bit = (sizeof(size_t) == 8); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(v1, true, false).is_discarded()); - if (is_64bit) - { - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.out_of_range.408] excessive array size: 18446744073709551601", json::out_of_range&); - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.out_of_range.408] excessive array size: 18446744073709551602", json::out_of_range&); - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.out_of_range.408] excessive array size: 18446744073709551592", json::out_of_range&); - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v4), "[json.exception.out_of_range.408] excessive array size: 18446744073709551607", json::out_of_range&); - } - else - { - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.out_of_range.408] excessive array size: 4294967281", json::out_of_range&); - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.out_of_range.408] excessive array size: 4294967282", json::out_of_range&); - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.out_of_range.408] excessive array size: 4294967272", json::out_of_range&); - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v4), "[json.exception.out_of_range.408] excessive array size: 4294967287", json::out_of_range&); - } - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v5), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(v2, true, false).is_discarded()); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(v3, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v4), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(v4, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v5), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(v5, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v6), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); CHECK(json::from_bjdata(v6, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vI), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(vI, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(vI, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); + CHECK(json::from_bjdata(vI, true, false).is_discarded()); } SECTION("do not accept NTFZ markers in ndarray optimized type") @@ -2548,20 +2547,16 @@ TEST_CASE("BJData") std::vector v_F = {'[', '$', 'F', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; std::vector v_Z = {'[', '$', 'Z', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; - CHECK_THROWS_AS(_ = json::from_bjdata(v_N), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_N, true, false).is_discarded()); - CHECK_THROWS_AS(_ = json::from_bjdata(v_T), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_T, true, false).is_discarded()); - CHECK_THROWS_AS(_ = json::from_bjdata(v_F), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_F, true, false).is_discarded()); - CHECK_THROWS_AS(_ = json::from_bjdata(v_Z), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_Z, true, false).is_discarded()); } @@ -2573,20 +2568,16 @@ TEST_CASE("BJData") std::vector v_F = {'[', '$', 'F', '#', '[', 'i', 1, 'i', 2, ']'}; std::vector v_Z = {'[', '$', 'Z', '#', '[', 'i', 1, 'i', 2, ']'}; - CHECK_THROWS_AS(_ = json::from_bjdata(v_N), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_N), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_N, true, false).is_discarded()); - CHECK_THROWS_AS(_ = json::from_bjdata(v_T), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_T), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x54 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_T, true, false).is_discarded()); - CHECK_THROWS_AS(_ = json::from_bjdata(v_F), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_F), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x46 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_F, true, false).is_discarded()); - CHECK_THROWS_AS(_ = json::from_bjdata(v_Z), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v_Z), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v_Z, true, false).is_discarded()); } } @@ -2595,18 +2586,15 @@ TEST_CASE("BJData") { std::vector vS = {'S'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vS, true, false).is_discarded()); std::vector v = {'S', 'i', '2', 'a'}; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData string: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData string: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v, true, false).is_discarded()); std::vector vC = {'C'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vC), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vC), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vC), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BJData char: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vC, true, false).is_discarded()); } @@ -2614,47 +2602,38 @@ TEST_CASE("BJData") { std::vector vU = {'[', '#', 'U'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vU, true, false).is_discarded()); std::vector vi = {'[', '#', 'i'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vi), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vi, true, false).is_discarded()); std::vector vI = {'[', '#', 'I'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vI), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vI), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vI), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vI, true, false).is_discarded()); std::vector vu = {'[', '#', 'u'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vu), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vu, true, false).is_discarded()); std::vector vl = {'[', '#', 'l'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vl), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vl), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vl, true, false).is_discarded()); std::vector vm = {'[', '#', 'm'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vm), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vm, true, false).is_discarded()); std::vector vL = {'[', '#', 'L'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vL), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vL), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vL, true, false).is_discarded()); std::vector vM = {'[', '#', 'M'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vM), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vM, true, false).is_discarded()); std::vector v0 = {'[', '#', 'T', ']'}; - CHECK_THROWS_AS(_ = json::from_bjdata(v0), json::parse_error&); CHECK_THROWS_WITH(_ = json::from_bjdata(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x54"); CHECK(json::from_bjdata(v0, true, false).is_discarded()); } @@ -2663,23 +2642,19 @@ TEST_CASE("BJData") { json _; std::vector vu = {'[', '#', 'u'}; - CHECK_THROWS_AS(_ = json::from_ubjson(vu), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(vu), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x75"); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vu), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x75", json::parse_error&); CHECK(json::from_ubjson(vu, true, false).is_discarded()); std::vector vm = {'[', '#', 'm'}; - CHECK_THROWS_AS(_ = json::from_ubjson(vm), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(vm), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x6D"); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vm), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x6D", json::parse_error&); CHECK(json::from_ubjson(vm, true, false).is_discarded()); std::vector vM = {'[', '#', 'M'}; - CHECK_THROWS_AS(_ = json::from_ubjson(vM), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(vM), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x4D"); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vM), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x4D", json::parse_error&); CHECK(json::from_ubjson(vM, true, false).is_discarded()); std::vector v0 = {'[', '#', '['}; - CHECK_THROWS_AS(_ = json::from_ubjson(v0), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_ubjson(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x5B"); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v0), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x5B", json::parse_error&); CHECK(json::from_ubjson(v0, true, false).is_discarded()); } @@ -2687,23 +2662,19 @@ TEST_CASE("BJData") { std::vector v0 = {'[', '$'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(v0), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v0), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData type: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v0), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BJData type: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v0, true, false).is_discarded()); std::vector vi = {'[', '$', '#'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vi), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vi, true, false).is_discarded()); std::vector vU = {'[', '$', 'U'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vU, true, false).is_discarded()); std::vector v1 = {'[', '$', '['}; - CHECK_THROWS_AS(_ = json::from_bjdata(v1), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v1), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5B is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5B is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v1, true, false).is_discarded()); } @@ -2711,18 +2682,15 @@ TEST_CASE("BJData") { std::vector vST = {'[', '$', 'i', '#', 'i', 2, 1}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(vST), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vST, true, false).is_discarded()); std::vector vS = {'[', '#', 'i', 2, 'i', 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vS, true, false).is_discarded()); std::vector v = {'[', 'i', 2, 'i', 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v, true, false).is_discarded()); } @@ -2730,53 +2698,43 @@ TEST_CASE("BJData") { std::vector vST = {'[', '$', 'i', '#', '[', '$', 'i', '#'}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(vST), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vST), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0xFF"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0xFF", json::parse_error&); CHECK(json::from_bjdata(vST, true, false).is_discarded()); std::vector v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1, 2}; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 13: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 13: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v, true, false).is_discarded()); std::vector vS0 = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(vS0), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vS0), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS0), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vS0, true, false).is_discarded()); std::vector vS = {'[', '$', 'i', '#', '[', '#', 'i', 2, 1, 2, 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x01"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x01", json::parse_error&); CHECK(json::from_bjdata(vS, true, false).is_discarded()); std::vector vT = {'[', '$', 'i', '#', '[', 'i', 2, 'i'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vT), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vT), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vT), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vT, true, false).is_discarded()); std::vector vT0 = {'[', '$', 'i', '#', '[', 'i'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vT0), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vT0), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vT0), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vT0, true, false).is_discarded()); std::vector vu = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'u', 1, 0}; - CHECK_THROWS_AS(_ = json::from_bjdata(vu), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vu), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vu, true, false).is_discarded()); std::vector vm = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'm', 1, 0, 0, 0}; - CHECK_THROWS_AS(_ = json::from_bjdata(vm), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vm, true, false).is_discarded()); std::vector vM = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'M', 1, 0, 0, 0, 0, 0, 0, 0}; - CHECK_THROWS_AS(_ = json::from_bjdata(vM), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vM, true, false).is_discarded()); std::vector vU = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5}; - CHECK_THROWS_AS(_ = json::from_bjdata(vU), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vU), "[json.exception.parse_error.110] parse error at byte 18: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vU, true, false).is_discarded()); std::vector vT1 = {'[', '$', 'T', '#', '[', '$', 'i', '#', 'i', 2, 2, 3}; @@ -2790,43 +2748,35 @@ TEST_CASE("BJData") { std::vector vST = {'{', '$', 'i', '#', 'i', 2, 'i', 1, 'a', 1}; json _; - CHECK_THROWS_AS(_ = json::from_bjdata(vST), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST), "[json.exception.parse_error.110] parse error at byte 11: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vST, true, false).is_discarded()); std::vector vT = {'{', '$', 'i', 'i', 1, 'a', 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(vT), json::parse_error&); CHECK_THROWS_WITH(_ = json::from_bjdata(vT), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x69"); CHECK(json::from_bjdata(vT, true, false).is_discarded()); std::vector vS = {'{', '#', 'i', 2, 'i', 1, 'a', 'i', 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(vS), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vS), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vS, true, false).is_discarded()); std::vector v = {'{', 'i', 1, 'a', 'i', 1}; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v, true, false).is_discarded()); std::vector v2 = {'{', 'i', 1, 'a', 'i', 1, 'i'}; - CHECK_THROWS_AS(_ = json::from_bjdata(v2), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v2, true, false).is_discarded()); std::vector v3 = {'{', 'i', 1, 'a'}; - CHECK_THROWS_AS(_ = json::from_bjdata(v3), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v3), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(v3, true, false).is_discarded()); std::vector vST1 = {'{', '$', 'd', '#', 'i', 2, 'i', 1, 'a'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vST1), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vST1), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData number: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST1), "[json.exception.parse_error.110] parse error at byte 10: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vST1, true, false).is_discarded()); std::vector vST2 = {'{', '#', 'i', 2, 'i', 1, 'a'}; - CHECK_THROWS_AS(_ = json::from_bjdata(vST2), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(vST2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData value: unexpected end of input"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vST2), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); CHECK(json::from_bjdata(vST2, true, false).is_discarded()); std::vector vO = {'{', '#', '[', 'i', 2, 'i', 1, ']', 'i', 1, 'a', 'i', 1, 'i', 1, 'b', 'i', 2}; @@ -3269,8 +3219,7 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") { json _; std::vector v = {'[', '$', 'N', '#', 'I', 0x00, 0x02}; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x4E is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v, true, false).is_discarded()); } @@ -3278,8 +3227,7 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") { json _; std::vector v = {'{', '$', 'Z', '#', 'i', 3, 'i', 4, 'n', 'a', 'm', 'e', 'i', 8, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 'i', 5, 'e', 'm', 'a', 'i', 'l'}; - CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); - CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type"); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x5A is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(v, true, false).is_discarded()); } } From 6ff2ea3aed110c17c2a4d9180dd5d422e1dfe6d0 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 19 May 2022 19:56:33 +0200 Subject: [PATCH 07/23] :goal_net: add assertion for invariant in SAX-DOM parser (#3498) --- include/nlohmann/detail/input/json_sax.hpp | 9 +++++++++ single_include/nlohmann/json.hpp | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 406008167..9355137cf 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -233,6 +233,9 @@ class json_sax_dom_parser bool key(string_t& val) { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + // add null at given key and store the reference for later object_element = &(ref_stack.back()->m_value.object->operator[](val)); return true; @@ -240,6 +243,9 @@ class json_sax_dom_parser bool end_object() { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + ref_stack.back()->set_parents(); ref_stack.pop_back(); return true; @@ -259,6 +265,9 @@ class json_sax_dom_parser bool end_array() { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); + ref_stack.back()->set_parents(); ref_stack.pop_back(); return true; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e1c352d82..9ddddb17c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6230,6 +6230,9 @@ class json_sax_dom_parser bool key(string_t& val) { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + // add null at given key and store the reference for later object_element = &(ref_stack.back()->m_value.object->operator[](val)); return true; @@ -6237,6 +6240,9 @@ class json_sax_dom_parser bool end_object() { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + ref_stack.back()->set_parents(); ref_stack.pop_back(); return true; @@ -6256,6 +6262,9 @@ class json_sax_dom_parser bool end_array() { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); + ref_stack.back()->set_parents(); ref_stack.pop_back(); return true; From 41226d0a03ef3e1f832af4c5cdda1e354fa4fce0 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Fri, 20 May 2022 03:41:51 -0400 Subject: [PATCH 08/23] prevent ndarray dimension vector from recusive array, nlohmann/json#3500 (#3502) --- include/nlohmann/detail/input/binary_reader.hpp | 7 +++++-- single_include/nlohmann/json.hpp | 7 +++++-- tests/src/unit-bjdata.cpp | 8 ++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 10f8f000f..f2dea07c8 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1938,7 +1938,7 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; - bool is_ndarray = false; + bool is_ndarray = true; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { @@ -1994,7 +1994,6 @@ class binary_reader */ bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) { - is_ndarray = false; if (prefix == 0) { prefix = get_ignore_noop(); @@ -2128,6 +2127,10 @@ class binary_reader { break; } + if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimention vector can only contain integers", "size"), nullptr)); + } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9ddddb17c..e793abf1e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10421,7 +10421,7 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; - bool is_ndarray = false; + bool is_ndarray = true; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { @@ -10477,7 +10477,6 @@ class binary_reader */ bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) { - is_ndarray = false; if (prefix == 0) { prefix = get_ignore_noop(); @@ -10611,6 +10610,10 @@ class binary_reader { break; } + if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimention vector can only contain integers", "size"), nullptr)); + } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) { diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index e9ce1b14e..296a8864c 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2742,6 +2742,14 @@ TEST_CASE("BJData") std::vector vh = {'[', '$', 'h', '#', '[', '$', 'i', '#', 'i', 2, 2, 3}; CHECK(json::from_bjdata(vh, true, false).is_discarded()); + + std::vector vR = {'[', '$', 'i', '#', '[', 'i', 1, '[', ']', ']', 1}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: ndarray dimention vector can only contain integers", json::parse_error&); + CHECK(json::from_bjdata(vR, true, false).is_discarded()); + + std::vector vRo = {'[', '$', 'i', '#', '[', 'i', 0, '{', '}', ']', 1}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vRo), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x7B", json::parse_error&); + CHECK(json::from_bjdata(vRo, true, false).is_discarded()); } SECTION("objects") From ede66678580596028bcd6e18871a35a54bac01d7 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Mon, 23 May 2022 00:07:22 -0400 Subject: [PATCH 09/23] Prevent ndarray size vector from recursive use, fix nlohmann/json#3503 (#3505) * Prevent ndarray size vector from recursive use, fix nlohmann/json#3503 * fix ci error * complete coverage * add missing coverage * fix style issue in added test --- .../nlohmann/detail/input/binary_reader.hpp | 36 ++++++++++++------- single_include/nlohmann/json.hpp | 36 ++++++++++++------- tests/src/unit-bjdata.cpp | 31 ++++++++++++++++ 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index f2dea07c8..d259e0448 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1938,9 +1938,9 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; - bool is_ndarray = true; + bool inside_ndarray = true; - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, inside_ndarray))) { return false; } @@ -1953,7 +1953,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, size_and_type.second))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, size_and_type.second))) { return false; } @@ -1965,7 +1965,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray))) { return false; } @@ -1977,7 +1977,7 @@ class binary_reader { while (current != ']') { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, current))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, current))) { return false; } @@ -1990,9 +1990,12 @@ class binary_reader /*! @param[out] result determined size + @param[in,out] inside_ndarray whether the parser is parsing an ND array dimensional vector + @param[in] prefix type marker if already read, otherwise set to 0 + @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) + bool get_ubjson_size_value(std::size_t& result, bool& inside_ndarray, char_int_type prefix = 0) { if (prefix == 0) { @@ -2127,7 +2130,7 @@ class binary_reader { break; } - if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + if (inside_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array { return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimention vector can only contain integers", "size"), nullptr)); } @@ -2166,7 +2169,7 @@ class binary_reader return false; } } - is_ndarray = true; + inside_ndarray = true; return sax->end_array(); } result = 0; @@ -2197,14 +2200,15 @@ class binary_reader for a more compact representation. @param[out] result pair of the size and the type + @param[in] inside_ndarray whether the parser is parsing an ND array dimensional vector @return whether pair creation completed */ - bool get_ubjson_size_type(std::pair& result) + bool get_ubjson_size_type(std::pair& result, bool inside_ndarray = false) { - bool is_ndarray = false; result.first = string_t::npos; // size result.second = 0; // type + bool is_ndarray = false; get_ignore_noop(); @@ -2240,6 +2244,11 @@ class binary_reader bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray) { + if (inside_ndarray) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray can not be recursive", "size"), nullptr)); + } result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters } return is_error; @@ -2250,7 +2259,8 @@ class binary_reader bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray) { - result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray requires both type and size", "size"), nullptr)); } return is_error; } @@ -2640,8 +2650,8 @@ class binary_reader { // get size of following number string std::size_t size{}; - bool is_ndarray = false; - auto res = get_ubjson_size_value(size, is_ndarray); + bool inside_ndarray = false; + auto res = get_ubjson_size_value(size, inside_ndarray); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e793abf1e..5993ce32a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10421,9 +10421,9 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; - bool is_ndarray = true; + bool inside_ndarray = true; - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, inside_ndarray))) { return false; } @@ -10436,7 +10436,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, size_and_type.second))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, size_and_type.second))) { return false; } @@ -10448,7 +10448,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray))) { return false; } @@ -10460,7 +10460,7 @@ class binary_reader { while (current != ']') { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, is_ndarray, current))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, current))) { return false; } @@ -10473,9 +10473,12 @@ class binary_reader /*! @param[out] result determined size + @param[in,out] inside_ndarray whether the parser is parsing an ND array dimensional vector + @param[in] prefix type marker if already read, otherwise set to 0 + @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) + bool get_ubjson_size_value(std::size_t& result, bool& inside_ndarray, char_int_type prefix = 0) { if (prefix == 0) { @@ -10610,7 +10613,7 @@ class binary_reader { break; } - if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + if (inside_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array { return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimention vector can only contain integers", "size"), nullptr)); } @@ -10649,7 +10652,7 @@ class binary_reader return false; } } - is_ndarray = true; + inside_ndarray = true; return sax->end_array(); } result = 0; @@ -10680,14 +10683,15 @@ class binary_reader for a more compact representation. @param[out] result pair of the size and the type + @param[in] inside_ndarray whether the parser is parsing an ND array dimensional vector @return whether pair creation completed */ - bool get_ubjson_size_type(std::pair& result) + bool get_ubjson_size_type(std::pair& result, bool inside_ndarray = false) { - bool is_ndarray = false; result.first = string_t::npos; // size result.second = 0; // type + bool is_ndarray = false; get_ignore_noop(); @@ -10723,6 +10727,11 @@ class binary_reader bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray) { + if (inside_ndarray) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray can not be recursive", "size"), nullptr)); + } result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters } return is_error; @@ -10733,7 +10742,8 @@ class binary_reader bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray) { - result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray requires both type and size", "size"), nullptr)); } return is_error; } @@ -11123,8 +11133,8 @@ class binary_reader { // get size of following number string std::size_t size{}; - bool is_ndarray = false; - auto res = get_ubjson_size_value(size, is_ndarray); + bool inside_ndarray = false; + auto res = get_ubjson_size_value(size, inside_ndarray); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 296a8864c..c93a5de51 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2372,6 +2372,7 @@ TEST_CASE("BJData") std::vector v_D = {'[', '$', 'D', '#', '[', 'i', 1, 'i', 2, ']', 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40, 0x4a, 0xd8, 0x12, 0x4d, 0xfb, 0x21, 0x09, 0x40}; std::vector v_S = {'[', '#', '[', 'i', 1, 'i', 2, ']', 'S', 'i', 1, 'a', 'S', 'i', 1, 'a'}; std::vector v_C = {'[', '$', 'C', '#', '[', 'i', 1, 'i', 2, ']', 'a', 'a'}; + std::vector v_R = {'[', '#', '[', 'i', 2, ']', 'i', 6, 'U', 7}; // check if vector is parsed correctly CHECK(json::from_bjdata(v_0) == json::array()); @@ -2387,6 +2388,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_D) == json({3.1415926, 3.1415926})); CHECK(json::from_bjdata(v_S) == json({"a", "a"})); CHECK(json::from_bjdata(v_C) == json({"a", "a"})); + CHECK(json::from_bjdata(v_R) == json({6, 7})); } SECTION("optimized ndarray (type and vector-size as size-optimized array)") @@ -2750,6 +2752,30 @@ TEST_CASE("BJData") std::vector vRo = {'[', '$', 'i', '#', '[', 'i', 0, '{', '}', ']', 1}; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vRo), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x7B", json::parse_error&); CHECK(json::from_bjdata(vRo, true, false).is_discarded()); + + std::vector vR1 = {'[', '$', 'i', '#', '[', '[', 'i', 1, ']', ']', 1}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR1), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimention vector can only contain integers", json::parse_error&); + CHECK(json::from_bjdata(vR1, true, false).is_discarded()); + + std::vector vR2 = {'[', '$', 'i', '#', '[', '#', '[', 'i', 1, ']', ']', 1}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR2), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x5D", json::parse_error&); + CHECK(json::from_bjdata(vR2, true, false).is_discarded()); + + std::vector vR3 = {'[', '#', '[', 'i', '2', 'i', 2, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR3), "[json.exception.parse_error.112] parse error at byte 8: syntax error while parsing BJData size: ndarray requires both type and size", json::parse_error&); + CHECK(json::from_bjdata(vR3, true, false).is_discarded()); + + std::vector vR4 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', 1, ']', 1}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR4), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input", json::parse_error&); + CHECK(json::from_bjdata(vR4, true, false).is_discarded()); + + std::vector vR5 = {'[', '$', 'i', '#', '[', '[', '[', ']', ']', ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR5), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimention vector can only contain integers", json::parse_error&); + CHECK(json::from_bjdata(vR5, true, false).is_discarded()); + + std::vector vR6 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR6), "[json.exception.parse_error.112] parse error at byte 14: syntax error while parsing BJData size: ndarray can not be recursive", json::parse_error&); + CHECK(json::from_bjdata(vR6, true, false).is_discarded()); } SECTION("objects") @@ -2788,7 +2814,12 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(vST2, true, false).is_discarded()); std::vector vO = {'{', '#', '[', 'i', 2, 'i', 1, ']', 'i', 1, 'a', 'i', 1, 'i', 1, 'b', 'i', 2}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vO), "[json.exception.parse_error.112] parse error at byte 8: syntax error while parsing BJData size: ndarray requires both type and size", json::parse_error&); CHECK(json::from_bjdata(vO, true, false).is_discarded()); + + std::vector vO2 = {'{', '$', 'i', '#', '[', 'i', 2, 'i', 1, ']', 'i', 1, 'a', 1, 'i', 1, 'b', 2}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vO2), "[json.exception.parse_error.112] parse error at byte 10: syntax error while parsing BJData object: BJData object does not support ND-array size in optimized format", json::parse_error&); + CHECK(json::from_bjdata(vO2, true, false).is_discarded()); } } From 6b97599a274b9b72caffa1332d5384c9aac27590 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Sun, 29 May 2022 13:08:06 +0200 Subject: [PATCH 10/23] Fix C++20/gcc-12 issues (Part 2) (#3446) * Add C++20 3-way comparison operator and fix broken comparisons Fixes #3207. Fixes #3409. * Fix iterators to meet (more) std::ranges requirements Fixes #3130. Related discussion: #3408 * Add note about CMake standard version selection to unit tests Document how CMake chooses which C++ standard version to use when building tests. * Update documentation * CI: add legacy discarded value comparison * Fix internal linkage errors when building a module --- .github/workflows/ubuntu.yml | 2 +- CMakeLists.txt | 24 +- cmake/ci.cmake | 19 +- cmake/test.cmake | 17 +- docs/mkdocs/docs/api/basic_json/index.md | 3 +- .../mkdocs/docs/api/basic_json/operator_eq.md | 37 +- .../mkdocs/docs/api/basic_json/operator_ge.md | 40 +- .../mkdocs/docs/api/basic_json/operator_gt.md | 38 +- .../mkdocs/docs/api/basic_json/operator_le.md | 40 +- .../mkdocs/docs/api/basic_json/operator_lt.md | 61 +- .../mkdocs/docs/api/basic_json/operator_ne.md | 39 +- .../docs/api/basic_json/operator_spaceship.md | 70 ++ docs/mkdocs/docs/api/basic_json/value_t.md | 39 +- docs/mkdocs/docs/api/macros/index.md | 8 + .../mkdocs/docs/api/macros/json_has_ranges.md | 18 + .../macros/json_has_three_way_comparison.md | 19 + ...n_use_legacy_discarded_value_comparison.md | 61 ++ docs/mkdocs/docs/css/custom.css | 4 + docs/mkdocs/mkdocs.yml | 15 +- .../nlohmann/detail/conversions/from_json.hpp | 49 +- .../nlohmann/detail/conversions/to_json.hpp | 47 +- .../nlohmann/detail/iterators/iter_impl.hpp | 5 +- .../detail/iterators/iteration_proxy.hpp | 65 +- include/nlohmann/detail/macro_scope.hpp | 31 +- include/nlohmann/detail/macro_unscope.hpp | 3 + include/nlohmann/detail/value_t.hpp | 30 +- include/nlohmann/json.hpp | 374 +++++---- single_include/nlohmann/json.hpp | 759 +++++++++++------- tests/CMakeLists.txt | 9 + tests/src/unit-class_parser.cpp | 8 +- tests/src/unit-comparison.cpp | 554 ++++++++++--- tests/src/unit-conversions.cpp | 15 +- tests/src/unit-items.cpp | 32 +- tests/src/unit-iterators2.cpp | 109 +++ tests/src/unit-regression2.cpp | 9 +- 35 files changed, 1963 insertions(+), 690 deletions(-) create mode 100644 docs/mkdocs/docs/api/basic_json/operator_spaceship.md create mode 100644 docs/mkdocs/docs/api/macros/json_has_ranges.md create mode 100644 docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md create mode 100644 docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md create mode 100644 docs/mkdocs/docs/css/custom.css diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 4d037e060..39098e4d7 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -48,7 +48,7 @@ jobs: container: ghcr.io/nlohmann/json-ci:v2.3.0 strategy: matrix: - target: [ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions] + target: [ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison] steps: - uses: actions/checkout@v3 - name: cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f65d9a32..632e6dec2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,13 +37,14 @@ if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} else() set(JSON_BuildTests_INIT OFF) endif() -option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${JSON_BuildTests_INIT}) -option(JSON_CI "Enable CI build targets." OFF) -option(JSON_Diagnostics "Use extended diagnostic messages." OFF) -option(JSON_ImplicitConversions "Enable implicit conversions." ON) -option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) -option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF) -option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) +option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${JSON_BuildTests_INIT}) +option(JSON_CI "Enable CI build targets." OFF) +option(JSON_Diagnostics "Use extended diagnostic messages." OFF) +option(JSON_ImplicitConversions "Enable implicit conversions." ON) +option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF) +option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) +option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF) +option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) if (JSON_CI) include(ci) @@ -77,6 +78,10 @@ if (NOT JSON_ImplicitConversions) message(STATUS "Implicit conversions are disabled") endif() +if (JSON_LegacyDiscardedValueComparison) + message(STATUS "Legacy discarded value comparison enabled") +endif() + if (JSON_Diagnostics) message(STATUS "Diagnostics enabled") endif() @@ -100,8 +105,9 @@ endif() target_compile_definitions( ${NLOHMANN_JSON_TARGET_NAME} INTERFACE - JSON_USE_IMPLICIT_CONVERSIONS=$ - JSON_DIAGNOSTICS=$ + $<$>:JSON_USE_IMPLICIT_CONVERSIONS=0> + $<$:JSON_DIAGNOSTICS=1> + $<$:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> ) target_include_directories( diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 56a80c066..ed26e02b5 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -498,6 +498,20 @@ add_custom_target(ci_test_diagnostics COMMENT "Compile and test with improved diagnostics enabled" ) +############################################################################### +# Enable legacy discarded value comparison. +############################################################################### + +add_custom_target(ci_test_legacycomparison + COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja + -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON -DJSON_LegacyDiscardedValueComparison=ON + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_legacycomparison + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_legacycomparison + COMMAND cd ${PROJECT_BINARY_DIR}/build_legacycomparison && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMENT "Compile and test with legacy discarded value comparison enabled" +) + ############################################################################### # Coverage. ############################################################################### @@ -797,8 +811,9 @@ endfunction() ci_get_cmake(3.1.0 CMAKE_3_1_0_BINARY) ci_get_cmake(3.13.0 CMAKE_3_13_0_BINARY) -set(JSON_CMAKE_FLAGS_3_1_0 "JSON_Install;JSON_MultipleHeaders;JSON_ImplicitConversions;JSON_Valgrind;JSON_Diagnostics;JSON_SystemInclude") -set(JSON_CMAKE_FLAGS_3_13_0 "JSON_BuildTests") +set(JSON_CMAKE_FLAGS_3_1_0 JSON_Diagnostics JSON_ImplicitConversions JSON_LegacyDiscardedValueComparison + JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind) +set(JSON_CMAKE_FLAGS_3_13_0 JSON_BuildTests) function(ci_add_cmake_flags_targets flag min_version) string(TOLOWER "ci_cmake_flag_${flag}" flag_target) diff --git a/cmake/test.cmake b/cmake/test.cmake index 1d146646b..89b7c6ec7 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -156,6 +156,7 @@ endfunction() ############################################################################# # json_test_add_test_for( # +# [NAME ] # MAIN
# [CXX_STANDARDS ...] [FORCE]) # @@ -165,6 +166,7 @@ endfunction() # # if C++ standard is supported by the compiler and the # source file contains JSON_HAS_CPP_. +# Use NAME to override the filename-derived test name. # Use FORCE to create the test regardless of the file containing # JSON_HAS_CPP_. # Test targets are linked against
. @@ -172,15 +174,22 @@ endfunction() ############################################################################# function(json_test_add_test_for file) - cmake_parse_arguments(args "FORCE" "MAIN" "CXX_STANDARDS" ${ARGN}) - - get_filename_component(file_basename ${file} NAME_WE) - string(REGEX REPLACE "unit-([^$]+)" "test-\\1" test_name ${file_basename}) + cmake_parse_arguments(args "FORCE" "MAIN;NAME" "CXX_STANDARDS" ${ARGN}) if("${args_MAIN}" STREQUAL "") message(FATAL_ERROR "Required argument MAIN
missing.") endif() + if("${args_NAME}" STREQUAL "") + get_filename_component(file_basename ${file} NAME_WE) + string(REGEX REPLACE "unit-([^$]+)" "test-\\1" test_name ${file_basename}) + else() + set(test_name ${args_NAME}) + if(NOT test_name MATCHES "test-[^$]+") + message(FATAL_ERROR "Test name must start with 'test-'.") + endif() + endif() + if("${args_CXX_STANDARDS}" STREQUAL "") set(args_CXX_STANDARDS 11) endif() diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index bc4dba153..2191d671f 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -233,9 +233,10 @@ Access to the JSON value - [**operator==**](operator_eq.md) - comparison: equal - [**operator!=**](operator_ne.md) - comparison: not equal - [**operator<**](operator_lt.md) - comparison: less than -- [**operator<=**](operator_le.md) - comparison: less than or equal - [**operator>**](operator_gt.md) - comparison: greater than +- [**operator<=**](operator_le.md) - comparison: less than or equal - [**operator>=**](operator_ge.md) - comparison: greater than or equal +- [**operator<=>**](operator_spaceship.md) - comparison: 3-way ### Serialization / Dumping diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index 3eec4fda4..6c86bf13e 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -1,21 +1,31 @@ # nlohmann::basic_json::operator== ```cpp -bool operator==(const_reference lhs, const_reference rhs) noexcept; +// until C++20 +bool operator==(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator==(const_reference lhs, const ScalarType rhs) noexcept; +bool operator==(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator==(ScalarType lhs, const const_reference rhs) noexcept; +bool operator==(ScalarType lhs, const const_reference rhs) noexcept; // (2) + +// since C++20 +class basic_json { + bool operator==(const_reference rhs) const noexcept; // (1) + + template + bool operator==(ScalarType rhs) const noexcept; // (2) +}; ``` -Compares two JSON values for equality according to the following rules: +1. Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) neither value is discarded, or (2) they are of the same + type and their stored values are the same according to their respective `operator==`. + - Integer and floating-point numbers are automatically converted before comparison. -- Two JSON values are equal if (1) they are not discarded, (2) they are from the same type, and (3) their stored values - are the same according to their respective `operator==`. -- Integer and floating-point numbers are automatically converted before comparison. Note that two NaN values are always - treated as unequal. +2. Compares a JSON value and a scalar or a scalar and a JSON value for equality by converting the + scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -32,7 +42,7 @@ Compares two JSON values for equality according to the following rules: ## Return value -whether the values `lhs` and `rhs` are equal +whether the values `lhs`/`*this` and `rhs` are equal ## Exception safety @@ -46,7 +56,11 @@ Linear. !!! note "Comparing special values" - - NaN values never compare equal to themselves or to other NaN values. + - `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. - JSON `#!cpp null` values are all equal. - Discarded values never compare equal to themselves. @@ -117,4 +131,5 @@ Linear. ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. +2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_ge.md b/docs/mkdocs/docs/api/basic_json/operator_ge.md index 68aac6557..6730f6809 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ge.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ge.md @@ -1,17 +1,25 @@ # nlohmann::basic_json::operator>= ```cpp -bool operator>=(const_reference lhs, const_reference rhs) noexcept, +// until C++20 +bool operator>=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator>=(const_reference lhs, const ScalarType rhs) noexcept; +bool operator>=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator>=(ScalarType lhs, const const_reference rhs) noexcept; +bool operator>=(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` by calculating -`#!cpp !(lhs < rhs)`. +1. Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` + according to the following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(lhs < rhs)`. + +2. Compares wether a JSON value is greater than or equal to a scalar or a scalar is greater than or + equal to a JSON value by converting the scalar to a JSON value and comparing both JSON values + according to 1. ## Template parameters @@ -38,6 +46,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -54,6 +77,11 @@ Linear. --8<-- "examples/operator__greaterequal.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_gt.md b/docs/mkdocs/docs/api/basic_json/operator_gt.md index 92ec30594..540e6076d 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_gt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_gt.md @@ -1,16 +1,24 @@ # nlohmann::basic_json::operator> ```cpp -bool operator>(const_reference lhs, const_reference rhs) noexcept, +// until C++20 +bool operator>(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator>(const_reference lhs, const ScalarType rhs) noexcept; +bool operator>(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator>(ScalarType lhs, const const_reference rhs) noexcept; +bool operator>(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is greater than another JSON value `rhs` by calculating `#!cpp !(lhs <= rhs)`. +1. Compares whether one JSON value `lhs` is greater than another JSON value `rhs` according to the + following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(lhs <= rhs)`. + +2. Compares wether a JSON value is greater than a scalar or a scalar is greater than a JSON value by + converting the scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -37,6 +45,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -53,6 +76,11 @@ Linear. --8<-- "examples/operator__greater.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_le.md b/docs/mkdocs/docs/api/basic_json/operator_le.md index 54f9a2809..c0f90acb2 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_le.md +++ b/docs/mkdocs/docs/api/basic_json/operator_le.md @@ -1,17 +1,25 @@ # nlohmann::basic_json::operator<= ```cpp -bool operator<=(const_reference lhs, const_reference rhs) noexcept, +// until C++20 +bool operator<=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator<=(const_reference lhs, const ScalarType rhs) noexcept; +bool operator<=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator<=(ScalarType lhs, const const_reference rhs) noexcept; +bool operator<=(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs` by calculating -`#cpp !(rhs < lhs)`. +1. Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs` + according to the following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(rhs < lhs)`. + +1. Compares wether a JSON value is less than or equal to a scalar or a scalar is less than or equal + to a JSON value by converting the scalar to a JSON value and comparing both JSON values according + to 1. ## Template parameters @@ -38,6 +46,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -54,6 +77,11 @@ Linear. --8<-- "examples/operator__lessequal.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_lt.md b/docs/mkdocs/docs/api/basic_json/operator_lt.md index d1a4999b4..b5d191ec4 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_lt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_lt.md @@ -1,31 +1,34 @@ # nlohmann::basic_json::operator< ```cpp -bool operator<(const_reference lhs, const_reference rhs) noexcept; +// until C++20 +bool operator<(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator<(const_reference lhs, const ScalarType rhs) noexcept; +bool operator<(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator<(ScalarType lhs, const const_reference rhs) noexcept; +bool operator<(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the following rules: +1. Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the + following rules: + - If either operand is discarded, the comparison yields `#!cpp false`. + - If both operands have the same type, the values are compared using their respective `operator<`. + - Integer and floating-point numbers are automatically converted before comparison. + - In case `lhs` and `rhs` have different types, the values are ignored and the order of the types + is considered, which is: + 1. null + 2. boolean + 3. number (all types) + 4. object + 5. array + 6. string + 7. binary + For instance, any boolean value is considered less than any string. -- If `lhs` and `rhs` have the same type, the values are compared using the default `<` operator. -- Integer and floating-point numbers are automatically converted before comparison -- Discarded values a -- In case `lhs` and `rhs` have different types, the values are ignored and the order of the types is considered, which - is: - 1. null - 2. boolean - 3. number (all types) - 4. object - 5. array - 6. string - 7. binary - - For instance, any boolean value is considered less than any string. +2. Compares wether a JSON value is less than a scalar or a scalar is less than a JSON value by converting + the scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -52,6 +55,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -68,6 +86,11 @@ Linear. --8<-- "examples/operator__less.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index e94da9b76..f5d989b50 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -1,16 +1,32 @@ # nlohmann::basic_json::operator!= ```cpp -bool operator!=(const_reference lhs, const_reference rhs) noexcept; +// until C++20 +bool operator!=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; +bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; +bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; // (2) + +// since C++20 +class basic_json { + bool operator!=(const_reference rhs) const noexcept; // (1) + + template + bool operator!=(ScalarType rhs) const noexcept; // (2) +}; ``` -Compares two JSON values for inequality by calculating `#!cpp !(lhs == rhs)`. +1. Compares two JSON values for inequality according to the following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(lhs == rhs)` (until C++20) or + `#!cpp !(*this == rhs)` (since C++20). + +2. Compares a JSON value and a scalar or a scalar and a JSON value for inequality by converting the + scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -27,7 +43,7 @@ Compares two JSON values for inequality by calculating `#!cpp !(lhs == rhs)`. ## Return value -whether the values `lhs` and `rhs` are not equal +whether the values `lhs`/`*this` and `rhs` are not equal ## Exception safety @@ -37,6 +53,16 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + ## Examples ??? example @@ -69,4 +95,5 @@ Linear. ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. +2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_spaceship.md b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md new file mode 100644 index 000000000..4beba4f86 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md @@ -0,0 +1,70 @@ +# nlohmann::basic_json::operator<=> + +```cpp +// since C++20 +class basic_json { + std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1) + + template + std::partial_ordering operator<=>(const ScalarType rhs) const noexcept; // (2) +}; +``` + +1. 3-way compares two JSON values producing a result of type `std::partial_ordering` according to the following rules: + - Two JSON values compare with a result of `std::partial_ordering::unordered` if either value is discarded. + - If both JSON values are of the same type, the result is produced by 3-way comparing their stored values using their + respective `operator<=>`. + - Integer and floating-point numbers are converted to their common type and then 3-way compared using their respective + `operator<=>`. + For instance, comparing an integer and a floating-point value will 3-way compare the first value convertered to + floating-point with the second value. + - Otherwise, yields a result by comparing the type (see [`value_t`](value_t.md)). + +2. 3-way compares a JSON value and a scalar or a scalar and a JSON value by converting the scalar to a JSON value and 3-way + comparing both JSON values (see 1). + +## Template parameters + +`ScalarType` +: a scalar type according to `std::is_scalar::value` + +## Parameters + +`rhs` (in) +: second value to consider + +## Return value + +the `std::partial_ordering` of the 3-way comparison of `*this` and `rhs` + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Linear. + +## Notes + +!!! note "Comparing `NaN`" + + - `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `std::partial_ordering::unordered`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +## See also + +- [**operator==**](operator_eq.md) - comparison: equal +- [**operator!=**](operator_ne.md) - comparison: not equal +- [**operator<**](operator_lt.md) - comparison: less than +- [**operator<=**](operator_le.md) - comparison: less than or equal +- [**operator>**](operator_gt.md) - comparison: greater than +- [**operator>=**](operator_ge.md) - comparison: greater than or equal + +## Version history + +1. Added in version 3.11.0. +2. Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/value_t.md b/docs/mkdocs/docs/api/basic_json/value_t.md index e7d32c480..f83574083 100644 --- a/docs/mkdocs/docs/api/basic_json/value_t.md +++ b/docs/mkdocs/docs/api/basic_json/value_t.md @@ -24,10 +24,41 @@ functions [`is_null`](is_null.md), [`is_object`](is_object.md), [`is_array`](is_ ## Notes -There are three enumeration entries (number_integer, number_unsigned, and number_float), because the library -distinguishes these three types for numbers: [`number_unsigned_t`](number_unsigned_t.md) is used for unsigned integers, -[`number_integer_t`](number_integer_t.md) is used for signed integers, and [`number_float_t`](number_float_t.md) is used -for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. +!!! note "Ordering" + + The order of types is as follows: + + 1. `null` + 2. `boolean` + 3. `number_integer`, `number_unsigned`, `number_float` + 4. `object` + 5. `array` + 6. `string` + 7. `binary` + + `discarded` is unordered. + +!!! note "Types of numbers" + + There are three enumerators for numbers (`number_integer`, `number_unsigned`, and `number_float`) to distinguish + between different types of numbers: + + - [`number_unsigned_t`](number_unsigned_t.md) for unsigned integers + - [`number_integer_t`](number_integer_t.md) for signed integers + - [`number_float_t`](number_float_t.md) for floating-point numbers or to approximate integers which do not fit + into the limits of their respective type + +!!! warning "Comparison operators" + + `operator<` and `operator<=>` (since C++20) are overloaded and compare according to the ordering described above. + Until C++20 all other relational and equality operators yield results according to the integer value of each + enumerator. + Since C++20 some compilers consider the _rewritten candidates_ generated from `operator<=>` during overload + resolution, while others do not. + For predictable and portable behavior use: + + - `operator<` or `operator<=>` when wanting to compare according to the order described above + - `operator==` or `operator!=` when wanting to compare according to each enumerators integer value ## Examples diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index 56924da44..5f0a7a194 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -17,6 +17,8 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_HAS_CPP_11**
**JSON_HAS_CPP_14**
**JSON_HAS_CPP_17**
**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard - [**JSON_HAS_FILESYSTEM**
**JSON_HAS_EXPERIMENTAL_FILESYSTEM**](json_has_filesystem.md) - control `std::filesystem` support +- [**JSON_HAS_RANGES**](json_has_ranges.md) - control `std::ranges` support +- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support - [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers - [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers @@ -29,6 +31,12 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions + +## Comparison behavior + +- [**JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON**](json_use_legacy_discarded_value_comparison.md) - + control comparison of discarded values + ## Serialization/deserialization macros - [**NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)**
**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)**](nlohmann_define_type_intrusive.md) - serialization/deserialization of types _with_ access to private variables diff --git a/docs/mkdocs/docs/api/macros/json_has_ranges.md b/docs/mkdocs/docs/api/macros/json_has_ranges.md new file mode 100644 index 000000000..ae596979e --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_has_ranges.md @@ -0,0 +1,18 @@ +# JSON_HAS_RANGES + +```cpp +#define JSON_HAS_RANGES /* value */ +``` + +This macro indicates whether the standard library has any support for ranges. Implies support for concepts. +Possible values are `1` when supported or `0` when unsupported. + +## Default definition + +The default value is detected based on the preprocessor macro `#!cpp __cpp_lib_ranges`. + +When the macro is not defined, the library will define it to its default value. + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md new file mode 100644 index 000000000..fc1dcb43c --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md @@ -0,0 +1,19 @@ +# JSON_HAS_THREE_WAY_COMPARISON + +```cpp +#define JSON_HAS_THREE_WAY_COMPARISON /* value */ +``` + +This macro indicates whether the compiler and standard library support 3-way comparison. +Possible values are `1` when supported or `0` when unsupported. + +## Default definition + +The default value is detected based on the preprocessor macros `#!cpp __cpp_impl_three_way_comparison` +and `#!cpp __cpp_lib_three_way_comparison`. + +When the macro is not defined, the library will define it to its default value. + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md new file mode 100644 index 000000000..4f630db12 --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md @@ -0,0 +1,61 @@ +# JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + +```cpp +#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON /* value */ +``` + +This macro enables the (incorrect) legacy comparison behavior of discarded JSON values. +Possible values are `1` to enable or `0` to disable (default). + +When enabled, comparisons involving at least one discarded JSON value yield results as follows: + +| **Operator** | **Result** | +|--------------|---------------| +| `==` | `#!cpp false` | +| `!=` | `#!cpp true` | +| `<` | `#!cpp false` | +| `<=` | `#!cpp true` | +| `>=` | `#!cpp true` | +| `>` | `#!cpp false` | + +Otherwise, comparisons involving at least one discarded JSON value always yield `#!cpp false`. + +## Default definition + +The default value is `0`. + +```cpp +#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +``` + +When the macro is not defined, the library will define it to its default value. + +## Notes + +!!! warning "Inconsistent behavior in C++20 and beyond" + + When targeting C++20 or above, enabling the legacy comparison behavior is _strongly_ + discouraged. + + - The 3-way comparison operator (`<=>`) will always give the correct result + (`#!cpp std::partial_ordering::unordered`) regardless of the value of + `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`. + - Overloads for the equality and relational operators emulate the legacy behavior. + + Code outside your control may use either 3-way comparison or the equality and + relational operators, resulting in inconsistent and unpredictable behavior. + + See [`operator<=>`](../basic_json/operator_spaceship.md) for more information on 3-way + comparison. + +!!! warning "Deprecation" + + The legacy comparison behavior is deprecated and may be removed in a future major + version release. + + New code should not depend on it and existing code should try to remove or rewrite + expressions relying on it. + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/css/custom.css b/docs/mkdocs/docs/css/custom.css new file mode 100644 index 000000000..7a1008b0b --- /dev/null +++ b/docs/mkdocs/docs/css/custom.css @@ -0,0 +1,4 @@ +/* disable ligatures in code and preformatted blocks */ +code, pre { + font-variant-ligatures: none; +} diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index dd4da8a0c..08ef336db 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -154,15 +154,16 @@ nav: - 'operator value_t': api/basic_json/operator_value_t.md - 'operator[]': api/basic_json/operator[].md - 'operator=': api/basic_json/operator=.md + - 'operator+=': api/basic_json/operator+=.md - 'operator==': api/basic_json/operator_eq.md - 'operator!=': api/basic_json/operator_ne.md - 'operator<': api/basic_json/operator_lt.md - - 'operator<<': api/basic_json/operator_ltlt.md - - 'operator<=': api/basic_json/operator_le.md - 'operator>': api/basic_json/operator_gt.md - - 'operator>>': api/basic_json/operator_gtgt.md + - 'operator<=': api/basic_json/operator_le.md - 'operator>=': api/basic_json/operator_ge.md - - 'operator+=': api/basic_json/operator+=.md + - 'operator<=>': api/basic_json/operator_spaceship.md + - 'operator<<': api/basic_json/operator_ltlt.md + - 'operator>>': api/basic_json/operator_gtgt.md - 'operator""_json': api/basic_json/operator_literal_json.md - 'operator""_json_pointer': api/basic_json/operator_literal_json_pointer.md - 'out_of_range': api/basic_json/out_of_range.md @@ -246,6 +247,8 @@ nav: - 'JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM': api/macros/json_has_filesystem.md - 'JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md + - 'JSON_HAS_RANGES': api/macros/json_has_ranges.md + - 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md - 'JSON_NOEXCEPTION': api/macros/json_noexception.md - 'JSON_NO_IO': api/macros/json_no_io.md - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md @@ -253,6 +256,7 @@ nav: - 'JSON_THROW_USER': api/macros/json_throw_user.md - 'JSON_TRY_USER': api/macros/json_throw_user.md - 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md + - 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md - 'NLOHMANN_DEFINE_TYPE_INTRUSIVE': api/macros/nlohmann_define_type_intrusive.md - 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_type_intrusive.md - 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE': api/macros/nlohmann_define_type_non_intrusive.md @@ -319,5 +323,8 @@ plugins: minify_html: true - git-revision-date-localized +extra_css: + - css/custom.css + extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 079baa723..f0a93f291 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -39,7 +39,7 @@ namespace nlohmann namespace detail { template -void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { @@ -86,7 +86,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { @@ -96,7 +96,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -111,7 +111,7 @@ template < std::is_assignable::value && !std::is_same::value && !is_json_ref::value, int > = 0 > -void from_json(const BasicJsonType& j, StringType& s) +inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -122,26 +122,26 @@ void from_json(const BasicJsonType& j, StringType& s) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) +inline void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); @@ -151,7 +151,7 @@ void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) +inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -168,7 +168,7 @@ void from_json(const BasicJsonType& j, std::forward_list& l) // valarray doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray& l) +inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -193,7 +193,7 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } template -void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } @@ -237,8 +237,8 @@ template::value, int> = 0> -void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) { using std::end; @@ -295,7 +295,7 @@ auto from_json(BasicJsonType&& j, identity_tag> tag) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { @@ -307,7 +307,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) template::value, int> = 0> -void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { @@ -339,7 +339,7 @@ template < typename BasicJsonType, typename ArithmeticType, !std::is_same::value&& !std::is_same::value, int > = 0 > -void from_json(const BasicJsonType& j, ArithmeticType& val) +inline void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { @@ -389,7 +389,7 @@ std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) { p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); } @@ -401,7 +401,7 @@ std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) { t = from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); } @@ -421,7 +421,7 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t) template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::map& m) +inline void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -441,7 +441,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::unordered_map& m) +inline void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -460,7 +460,7 @@ void from_json(const BasicJsonType& j, std::unordered_map -void from_json(const BasicJsonType& j, std_fs::path& p) +inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -482,11 +482,16 @@ struct from_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& from_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 7628ae011..fde46ef7c 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -267,55 +267,55 @@ struct external_constructor template::value, int> = 0> -void to_json(BasicJsonType& j, T b) noexcept +inline void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> -void to_json(BasicJsonType& j, const CompatibleString& s) +inline void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template -void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> -void to_json(BasicJsonType& j, FloatType val) noexcept +inline void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, EnumType e) noexcept +inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template -void to_json(BasicJsonType& j, const std::vector& e) +inline void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } @@ -328,39 +328,39 @@ template < typename BasicJsonType, typename CompatibleArrayType, !std::is_same::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template -void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> -void to_json(BasicJsonType& j, const std::valarray& arr) +inline void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template -void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +inline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template -void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } @@ -370,13 +370,13 @@ template < enable_if_t < !std::is_constructible::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) int > = 0 > -void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +inline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > -void to_json(BasicJsonType& j, const std::pair& p) +inline void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } @@ -384,26 +384,26 @@ void to_json(BasicJsonType& j, const std::pair& p) // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> -void to_json(BasicJsonType& j, const T& b) +inline void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template -void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> -void to_json(BasicJsonType& j, const T& t) +inline void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM template -void to_json(BasicJsonType& j, const std_fs::path& p) +inline void to_json(BasicJsonType& j, const std_fs::path& p) { j = p.string(); } @@ -420,11 +420,16 @@ struct to_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `to_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& to_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index cdcdaff1f..81c61b3e0 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -51,9 +51,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); + // superficial check for the LegacyBidirectionalIterator named requirement + static_assert(std::is_base_of::value + && std::is_base_of::value, + "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); public: - /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The C++ Standard has never required user-defined iterators to derive from std::iterator. /// A user-defined iterator should provide publicly accessible typedefs named diff --git a/include/nlohmann/detail/iterators/iteration_proxy.hpp b/include/nlohmann/detail/iterators/iteration_proxy.hpp index 9994b364c..a75614de1 100644 --- a/include/nlohmann/detail/iterators/iteration_proxy.hpp +++ b/include/nlohmann/detail/iterators/iteration_proxy.hpp @@ -6,6 +6,10 @@ #include // tuple_size, get, tuple_element #include // move +#if JSON_HAS_RANGES + #include // enable_borrowed_range +#endif + #include #include @@ -25,14 +29,14 @@ template class iteration_proxy_value public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; - using pointer = value_type * ; - using reference = value_type & ; + using pointer = value_type *; + using reference = value_type &; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator - IteratorType anchor; + IteratorType anchor{}; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index @@ -40,15 +44,30 @@ template class iteration_proxy_value /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) - const string_type empty_str{}; + string_type empty_str{}; public: - explicit iteration_proxy_value(IteratorType it) noexcept + explicit iteration_proxy_value() = default; + explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_default_constructible::value) : anchor(std::move(it)) + , array_index(array_index_) {} + iteration_proxy_value(iteration_proxy_value const&) = default; + iteration_proxy_value& operator=(iteration_proxy_value const&) = default; + // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions + iteration_proxy_value(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value) = default; + iteration_proxy_value& operator=(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_assignable::value + && std::is_nothrow_move_assignable::value) = default; + ~iteration_proxy_value() = default; + /// dereference operator (needed for range-based for) - iteration_proxy_value& operator*() + const iteration_proxy_value& operator*() const { return *this; } @@ -62,6 +81,14 @@ template class iteration_proxy_value return *this; } + iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto tmp = iteration_proxy_value(anchor, array_index); + ++anchor; + ++array_index; + return tmp; + } + /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { @@ -122,25 +149,34 @@ template class iteration_proxy { private: /// the container to iterate - typename IteratorType::reference container; + typename IteratorType::pointer container = nullptr; public: + explicit iteration_proxy() = default; + /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept - : container(cont) {} + : container(&cont) {} + + iteration_proxy(iteration_proxy const&) = default; + iteration_proxy& operator=(iteration_proxy const&) = default; + iteration_proxy(iteration_proxy&&) noexcept = default; + iteration_proxy& operator=(iteration_proxy&&) noexcept = default; + ~iteration_proxy() = default; /// return iterator begin (needed for range-based for) - iteration_proxy_value begin() noexcept + iteration_proxy_value begin() const noexcept { - return iteration_proxy_value(container.begin()); + return iteration_proxy_value(container->begin()); } /// return iterator end (needed for range-based for) - iteration_proxy_value end() noexcept + iteration_proxy_value end() const noexcept { - return iteration_proxy_value(container.end()); + return iteration_proxy_value(container->end()); } }; + // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 @@ -187,3 +223,8 @@ class tuple_element> #pragma clang diagnostic pop #endif } // namespace std + +#if JSON_HAS_RANGES + template + inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; +#endif diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index cc9ac5fc7..18a527c19 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -37,6 +37,12 @@ #define JSON_HAS_CPP_11 #endif +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + #if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) #ifdef JSON_HAS_CPP_17 #if defined(__cpp_lib_filesystem) @@ -98,14 +104,31 @@ #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L \ - && defined(__cpp_impl_three_way_comparison)&& __cpp_impl_three_way_comparison >= 201907L + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L #define JSON_HAS_THREE_WAY_COMPARISON 1 #else #define JSON_HAS_THREE_WAY_COMPARISON 0 #endif #endif +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else @@ -429,3 +452,7 @@ #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index ec57b02cc..396fe1a33 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -14,6 +14,7 @@ #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS #ifndef JSON_TEST_KEEP_MACROS @@ -26,6 +27,8 @@ #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #endif #include diff --git a/include/nlohmann/detail/value_t.hpp b/include/nlohmann/detail/value_t.hpp index a98c4355a..5a32b646e 100644 --- a/include/nlohmann/detail/value_t.hpp +++ b/include/nlohmann/detail/value_t.hpp @@ -5,6 +5,11 @@ #include // uint8_t #include // string +#include +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + namespace nlohmann { namespace detail @@ -64,7 +69,11 @@ Returns an ordering that is similar to Python: @since version 1.0.0 */ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif { static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, @@ -75,7 +84,26 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept const auto l_index = static_cast(lhs); const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif } + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif } // namespace detail } // namespace nlohmann diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index fb0f5fc18..7a1030c76 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1905,7 +1905,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::negation>, detail::negation>, detail::negation>>, - #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif @@ -3538,7 +3537,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} - public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -3546,6 +3544,212 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @name lexicographical comparison operators /// @{ + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_value.array) op (*rhs.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_value.object) op (*rhs.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_value.string) op (*rhs.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_value.boolean) op (rhs.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_value.number_integer) op (rhs.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_value.number_float) op (rhs.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_value.binary) op (*rhs.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_integer) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_integer op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + bool operator==(const_reference rhs) const noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + bool operator!=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !operator==(rhs); + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as an odd number of inverses of others + // need to be overloaded to emulate the legacy comparison behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(*this < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ friend bool operator==(const_reference lhs, const_reference rhs) noexcept @@ -3554,71 +3758,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; - - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; - - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; - - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; - - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; - - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -3646,6 +3786,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs == rhs); } @@ -3671,76 +3815,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, + // default_result is used if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) } /// @brief comparison: less than @@ -3765,6 +3843,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(rhs < lhs); } @@ -3790,6 +3872,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } return !(lhs <= rhs); } @@ -3815,6 +3902,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs < rhs); } @@ -3835,6 +3926,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return basic_json(lhs) >= rhs; } +#endif + +#undef JSON_IMPLEMENT_OPERATOR /// @} @@ -5031,10 +5125,14 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', @brief compare two value_t enum values @since version 3.0.0 */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif } }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5993ce32a..30386c5e8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -103,84 +103,6 @@ SOFTWARE. #include // uint8_t #include // string -namespace nlohmann -{ -namespace detail -{ -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -} -} // namespace detail -} // namespace nlohmann - -// #include - - // #include @@ -2342,6 +2264,12 @@ using is_detected_convertible = #define JSON_HAS_CPP_11 #endif +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + #if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) #ifdef JSON_HAS_CPP_17 #if defined(__cpp_lib_filesystem) @@ -2403,14 +2331,31 @@ using is_detected_convertible = #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L \ - && defined(__cpp_impl_three_way_comparison)&& __cpp_impl_three_way_comparison >= 201907L + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L #define JSON_HAS_THREE_WAY_COMPARISON 1 #else #define JSON_HAS_THREE_WAY_COMPARISON 0 #endif #endif +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else @@ -2735,6 +2680,117 @@ using is_detected_convertible = #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif +} // namespace detail +} // namespace nlohmann + +// #include + + +// #include + namespace nlohmann { @@ -4133,7 +4189,7 @@ namespace nlohmann namespace detail { template -void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { @@ -4180,7 +4236,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { @@ -4190,7 +4246,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -4205,7 +4261,7 @@ template < std::is_assignable::value && !std::is_same::value && !is_json_ref::value, int > = 0 > -void from_json(const BasicJsonType& j, StringType& s) +inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -4216,26 +4272,26 @@ void from_json(const BasicJsonType& j, StringType& s) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) +inline void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); @@ -4245,7 +4301,7 @@ void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) +inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4262,7 +4318,7 @@ void from_json(const BasicJsonType& j, std::forward_list& l) // valarray doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray& l) +inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4287,7 +4343,7 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } template -void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } @@ -4331,8 +4387,8 @@ template::value, int> = 0> -void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) { using std::end; @@ -4389,7 +4445,7 @@ auto from_json(BasicJsonType&& j, identity_tag> tag) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { @@ -4401,7 +4457,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) template::value, int> = 0> -void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { @@ -4433,7 +4489,7 @@ template < typename BasicJsonType, typename ArithmeticType, !std::is_same::value&& !std::is_same::value, int > = 0 > -void from_json(const BasicJsonType& j, ArithmeticType& val) +inline void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { @@ -4483,7 +4539,7 @@ std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) { p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); } @@ -4495,7 +4551,7 @@ std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) { t = from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); } @@ -4515,7 +4571,7 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t) template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::map& m) +inline void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4535,7 +4591,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::unordered_map& m) +inline void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4554,7 +4610,7 @@ void from_json(const BasicJsonType& j, std::unordered_map -void from_json(const BasicJsonType& j, std_fs::path& p) +inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -4576,13 +4632,18 @@ struct from_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& from_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann // #include @@ -4608,6 +4669,10 @@ constexpr const auto& from_json = detail::static_const::va #include // tuple_size, get, tuple_element #include // move +#if JSON_HAS_RANGES + #include // enable_borrowed_range +#endif + // #include // #include @@ -4629,14 +4694,14 @@ template class iteration_proxy_value public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; - using pointer = value_type * ; - using reference = value_type & ; + using pointer = value_type *; + using reference = value_type &; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator - IteratorType anchor; + IteratorType anchor{}; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index @@ -4644,15 +4709,30 @@ template class iteration_proxy_value /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) - const string_type empty_str{}; + string_type empty_str{}; public: - explicit iteration_proxy_value(IteratorType it) noexcept + explicit iteration_proxy_value() = default; + explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_default_constructible::value) : anchor(std::move(it)) + , array_index(array_index_) {} + iteration_proxy_value(iteration_proxy_value const&) = default; + iteration_proxy_value& operator=(iteration_proxy_value const&) = default; + // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions + iteration_proxy_value(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value) = default; + iteration_proxy_value& operator=(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_assignable::value + && std::is_nothrow_move_assignable::value) = default; + ~iteration_proxy_value() = default; + /// dereference operator (needed for range-based for) - iteration_proxy_value& operator*() + const iteration_proxy_value& operator*() const { return *this; } @@ -4666,6 +4746,14 @@ template class iteration_proxy_value return *this; } + iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto tmp = iteration_proxy_value(anchor, array_index); + ++anchor; + ++array_index; + return tmp; + } + /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { @@ -4726,25 +4814,34 @@ template class iteration_proxy { private: /// the container to iterate - typename IteratorType::reference container; + typename IteratorType::pointer container = nullptr; public: + explicit iteration_proxy() = default; + /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept - : container(cont) {} + : container(&cont) {} + + iteration_proxy(iteration_proxy const&) = default; + iteration_proxy& operator=(iteration_proxy const&) = default; + iteration_proxy(iteration_proxy&&) noexcept = default; + iteration_proxy& operator=(iteration_proxy&&) noexcept = default; + ~iteration_proxy() = default; /// return iterator begin (needed for range-based for) - iteration_proxy_value begin() noexcept + iteration_proxy_value begin() const noexcept { - return iteration_proxy_value(container.begin()); + return iteration_proxy_value(container->begin()); } /// return iterator end (needed for range-based for) - iteration_proxy_value end() noexcept + iteration_proxy_value end() const noexcept { - return iteration_proxy_value(container.end()); + return iteration_proxy_value(container->end()); } }; + // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 @@ -4792,6 +4889,11 @@ class tuple_element> #endif } // namespace std +#if JSON_HAS_RANGES + template + inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; +#endif + // #include // #include @@ -5051,55 +5153,55 @@ struct external_constructor template::value, int> = 0> -void to_json(BasicJsonType& j, T b) noexcept +inline void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> -void to_json(BasicJsonType& j, const CompatibleString& s) +inline void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template -void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> -void to_json(BasicJsonType& j, FloatType val) noexcept +inline void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, EnumType e) noexcept +inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template -void to_json(BasicJsonType& j, const std::vector& e) +inline void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } @@ -5112,39 +5214,39 @@ template < typename BasicJsonType, typename CompatibleArrayType, !std::is_same::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template -void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> -void to_json(BasicJsonType& j, const std::valarray& arr) +inline void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template -void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +inline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template -void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } @@ -5154,13 +5256,13 @@ template < enable_if_t < !std::is_constructible::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) int > = 0 > -void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +inline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > -void to_json(BasicJsonType& j, const std::pair& p) +inline void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } @@ -5168,26 +5270,26 @@ void to_json(BasicJsonType& j, const std::pair& p) // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> -void to_json(BasicJsonType& j, const T& b) +inline void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template -void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> -void to_json(BasicJsonType& j, const T& t) +inline void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM template -void to_json(BasicJsonType& j, const std_fs::path& p) +inline void to_json(BasicJsonType& j, const std_fs::path& p) { j = p.string(); } @@ -5204,13 +5306,18 @@ struct to_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `to_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& to_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann // #include @@ -12155,9 +12262,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); + // superficial check for the LegacyBidirectionalIterator named requirement + static_assert(std::is_base_of::value + && std::is_base_of::value, + "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); public: - /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The C++ Standard has never required user-defined iterators to derive from std::iterator. /// A user-defined iterator should provide publicly accessible typedefs named @@ -20065,7 +20175,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::negation>, detail::negation>, detail::negation>>, - #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif @@ -21698,7 +21807,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} - public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -21706,6 +21814,212 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @name lexicographical comparison operators /// @{ + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_value.array) op (*rhs.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_value.object) op (*rhs.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_value.string) op (*rhs.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_value.boolean) op (rhs.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_value.number_integer) op (rhs.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_value.number_float) op (rhs.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_value.binary) op (*rhs.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_integer) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_integer op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + bool operator==(const_reference rhs) const noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + bool operator!=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !operator==(rhs); + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as an odd number of inverses of others + // need to be overloaded to emulate the legacy comparison behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(*this < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ friend bool operator==(const_reference lhs, const_reference rhs) noexcept @@ -21714,71 +22028,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; - - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; - - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; - - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; - - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; - - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -21806,6 +22056,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs == rhs); } @@ -21831,76 +22085,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, + // default_result is used if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) } /// @brief comparison: less than @@ -21925,6 +22113,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(rhs < lhs); } @@ -21950,6 +22142,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } return !(lhs <= rhs); } @@ -21975,6 +22172,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs < rhs); } @@ -21995,6 +22196,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return basic_json(lhs) >= rhs; } +#endif + +#undef JSON_IMPLEMENT_OPERATOR /// @} @@ -23191,10 +23395,14 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', @brief compare two value_t enum values @since version 3.0.0 */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif } }; @@ -23248,6 +23456,7 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS #ifndef JSON_TEST_KEEP_MACROS @@ -23260,6 +23469,8 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #endif // #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2f6409c57..010c4950e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -123,6 +123,15 @@ foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() +# test legacy comparison of discarded values +json_test_set_test_options(test-comparison_legacy + COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 +) +json_test_add_test_for(src/unit-comparison.cpp + NAME test-comparison_legacy + MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} +) + # *DO NOT* use json_test_set_test_options() below this line ############################################################################# diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 95a958d1f..3d2677394 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -1454,17 +1454,17 @@ TEST_CASE("parser class") SECTION("filter specific element") { - json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept { // filter all number(2) elements - return j != json(2); + return event != json::parse_event_t::value || j != json(2); }); CHECK (j_object == json({{"bar", {{"baz", 1}}}})); - json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept { - return j != json(2); + return event != json::parse_event_t::value || j != json(2); }); CHECK (j_array == json({1, {3, 4, 5}, 4, 5})); diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index 6c94add60..f310a70d1 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -27,11 +27,49 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" +#define JSON_TESTS_PRIVATE #include 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 +{ + static String convert(const std::partial_ordering& order) + { + if (order == std::partial_ordering::less) + { + return "std::partial_ordering::less"; + } + if (order == std::partial_ordering::equivalent) + { + return "std::partial_ordering::equivalent"; + } + if (order == std::partial_ordering::greater) + { + return "std::partial_ordering::greater"; + } + if (order == std::partial_ordering::unordered) + { + return "std::partial_ordering::unordered"; + } + return "{?}"; + } +}; +} // namespace doctest +#endif + namespace { // helper function to check std::less @@ -45,6 +83,27 @@ bool f(A a, B b, U u = U()) TEST_CASE("lexicographical comparison operators") { + constexpr auto f_ = false; + constexpr auto _t = true; + constexpr auto nan = std::numeric_limits::quiet_NaN(); +#if JSON_HAS_THREE_WAY_COMPARISON + constexpr auto lt = std::partial_ordering::less; + constexpr auto gt = std::partial_ordering::greater; + constexpr auto eq = std::partial_ordering::equivalent; + constexpr auto un = std::partial_ordering::unordered; +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + INFO("using 3-way comparison"); +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + INFO("using legacy comparison"); +#endif + + //REQUIRE(std::numeric_limits::has_quiet_NaN); + REQUIRE(std::isnan(nan)); + SECTION("types") { std::vector j_types = @@ -57,97 +116,268 @@ TEST_CASE("lexicographical comparison operators") json::value_t::object, json::value_t::array, json::value_t::string, - json::value_t::binary + json::value_t::binary, + json::value_t::discarded + }; + + std::vector> 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") { - std::vector> expected = - { - {false, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, false, true, true, true}, - {false, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false, false, false} - }; - + REQUIRE(expected_lt.size() == j_types.size()); for (size_t i = 0; i < j_types.size(); ++i) { + REQUIRE(expected_lt[i].size() == j_types.size()); for (size_t j = 0; j < j_types.size(); ++j) { CAPTURE(i) CAPTURE(j) // check precomputed values - CHECK(operator<(j_types[i], j_types[j]) == expected[i][j]); - CHECK(f(j_types[i], j_types[j]) == expected[i][j]); +#if JSON_HAS_THREE_WAY_COMPARISON + // JSON_HAS_CPP_20 (do not remove; see note at top of file) + CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]); +#else + CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]); +#endif + CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]); } } } +#if JSON_HAS_THREE_WAY_COMPARISON + // JSON_HAS_CPP_20 (do not remove; see note at top of file) + SECTION("comparison: 3-way") + { + std::vector> 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") { json j_values = { - nullptr, nullptr, - -17, 42, - 8u, 13u, - 3.14159, 23.42, - "foo", "bar", - true, false, - {1, 2, 3}, {"one", "two", "three"}, - {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, - json::binary({1, 2, 3}), json::binary({1, 2, 4}) + 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 }; - SECTION("comparison: equal") + std::vector> 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> 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("compares unordered") { std::vector> expected = { - {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true} + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 0 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 1 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 3 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 4 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 5 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 7 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 8 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 19 + {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 20 + {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 21 }; + // check if two values compare unordered as expected + 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(json::compares_unordered(j_values[i], j_values[j]) == expected[i][j]); + } + } + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + SECTION("compares unordered (inverse)") + { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, 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 + }; + + // check that two values compare unordered as expected (with legacy-mode enabled) + 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) CAPTURE(j_values[i]) CAPTURE(j_values[j]) - // check precomputed values - CHECK( (j_values[i] == j_values[j]) == expected[i][j] ); + CHECK(json::compares_unordered(j_values[i], j_values[j], true) == expected[i][j]); } } + } +#endif - // comparison with discarded elements - json j_discarded(json::value_t::discarded); - for (const auto& v : j_values) + SECTION("comparison: equal") + { + // check that two values compare equal + REQUIRE(expected_eq.size() == j_values.size()); + for (size_t i = 0; i < j_values.size(); ++i) { - CHECK( (v == j_discarded) == false); - CHECK( (j_discarded == v) == false); - CHECK( (j_discarded == j_discarded) == false); + REQUIRE(expected_eq[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_eq[i][j]); + } } // compare with null pointer @@ -158,121 +388,229 @@ TEST_CASE("lexicographical comparison operators") SECTION("comparison: not equal") { + // check that two values compare unequal as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]) ); + + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + // if two values compare unordered, + // check that the boolean comparison result is always false + CHECK_FALSE(j_values[i] != j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of equal + CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j])); + } } } // compare with null pointer json j_null; - CHECK( (j_null != nullptr) == false); - CHECK( (nullptr != j_null) == false); - CHECK( (j_null != nullptr) == !(j_null == nullptr)); - CHECK( (nullptr != j_null) == !(nullptr == j_null)); + CHECK((j_null != nullptr) == false); + CHECK((nullptr != j_null) == false); + CHECK((j_null != nullptr) == !(j_null == nullptr)); + CHECK((nullptr != j_null) == !(nullptr == j_null)); } SECTION("comparison: less") { - std::vector> expected = - { - {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, false, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, true, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, false, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, true, true, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, true, true}, - {false, false, true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false} - }; - + // check that two values compare less than as expected + REQUIRE(expected_lt.size() == j_values.size()); for (size_t i = 0; i < j_values.size(); ++i) { + REQUIRE(expected_lt[i].size() == j_values.size()); for (size_t j = 0; j < j_values.size(); ++j) { - // Skip comparing indicies 12 and 13, and 13 and 12 in C++20 pending fix - // See issue #3207 -#if defined(JSON_HAS_CPP_20) || JSON_HAS_THREE_WAY_COMPARISON - if ((i == 12 && j == 13) || (i == 13 && j == 12)) - { - continue; - } -#endif CAPTURE(i) CAPTURE(j) - CAPTURE(j_values[i]) - CAPTURE(j_values[j]) - // check precomputed values - CHECK( (j_values[i] < j_values[j]) == expected[i][j] ); + CHECK((j_values[i] < j_values[j]) == expected_lt[i][j]); } } - - // comparison with discarded elements - json j_discarded(json::value_t::discarded); - for (size_t i = 0; i < j_values.size(); ++i) - { - CAPTURE(i) - CHECK( (j_values[i] < j_discarded) == false); - CHECK( (j_discarded < j_values[i]) == false); - CHECK( (j_discarded < j_discarded) == false); - } } SECTION("comparison: less than or equal equal") { + // check that two values compare less than or equal as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i]) ); + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + // if two values compare unordered, + // check that the boolean comparison result is always false + CHECK_FALSE(j_values[i] <= j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of less than with the operand order reversed + CHECK((j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i])); + } } } } SECTION("comparison: greater than") { + // check that two values compare greater than as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] > j_values[j]) == (j_values[j] < j_values[i]) ); + if (json::compares_unordered(j_values[i], j_values[j])) + { + // if two values compare unordered, + // check that the boolean comparison result is always false + CHECK_FALSE(j_values[i] > j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of less than or equal which is defined as + // the inverse of less than with the operand order reversed + CHECK((j_values[i] > j_values[j]) == !(j_values[i] <= j_values[j])); + CHECK((j_values[i] > j_values[j]) == !!(j_values[j] < j_values[i])); + } } } } SECTION("comparison: greater than or equal") { + // check that two values compare greater than or equal as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j]) ); + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + // if two values compare unordered, + // check that the boolean result is always false + CHECK_FALSE(j_values[i] >= j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of less than + CHECK((j_values[i] >= j_values[j]) == !(j_values[i] < j_values[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> 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 + SECTION("parser callback regression") + { + SECTION("filter specific element") + { + const auto* s_object = R"( + { + "foo": 2, + "bar": { + "baz": 1 + } + } + )"; + const auto* s_array = R"( + [1,2,[3,4,5],4,5] + )"; + + json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + { + // filter all number(2) elements + return j != json(2); + }); + + CHECK (j_object == json({{"bar", {{"baz", 1}}}})); + + json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + { + return j != json(2); + }); + + CHECK (j_array == json({1, {3, 4, 5}, 4, 5})); + } + } +#endif } diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index c61b249c2..bfa9bb98f 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -27,6 +27,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" #define JSON_TESTS_PRIVATE @@ -1582,12 +1589,4 @@ TEST_CASE("JSON to enum mapping") } } -#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 diff --git a/tests/src/unit-items.cpp b/tests/src/unit-items.cpp index fa40f46d9..64d92b59e 100644 --- a/tests/src/unit-items.cpp +++ b/tests/src/unit-items.cpp @@ -80,7 +80,7 @@ TEST_CASE("iterator_wrapper") json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -226,7 +226,7 @@ TEST_CASE("iterator_wrapper") const json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -361,7 +361,7 @@ TEST_CASE("iterator_wrapper") json j = { "A", "B" }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -507,7 +507,7 @@ TEST_CASE("iterator_wrapper") const json j = { "A", "B" }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -624,7 +624,7 @@ TEST_CASE("iterator_wrapper") json j = 1; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -693,7 +693,7 @@ TEST_CASE("iterator_wrapper") const json j = 1; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -777,7 +777,7 @@ TEST_CASE("items()") json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -939,7 +939,7 @@ TEST_CASE("items()") const json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -1074,7 +1074,7 @@ TEST_CASE("items()") json j = { "A", "B" }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -1220,7 +1220,7 @@ TEST_CASE("items()") const json j = { "A", "B" }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -1337,7 +1337,7 @@ TEST_CASE("items()") json j = 1; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -1406,7 +1406,7 @@ TEST_CASE("items()") const json j = 1; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -1448,13 +1448,5 @@ TEST_CASE("items()") } } -#ifdef JSON_HAS_CPP_17 - #undef JSON_HAS_CPP_17 -#endif - -#ifdef JSON_HAS_CPP_14 - #undef JSON_HAS_CPP_14 -#endif - DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-iterators2.cpp b/tests/src/unit-iterators2.cpp index 976d0bcda..b9c418c16 100644 --- a/tests/src/unit-iterators2.cpp +++ b/tests/src/unit-iterators2.cpp @@ -27,11 +27,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" #include using nlohmann::json; +#if JSON_HAS_RANGES + #include + #include +#endif + TEST_CASE("iterators 2") { SECTION("iterator comparisons") @@ -881,4 +893,101 @@ 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); + CHECK(std::input_iterator>); + + CHECK(std::is_same>::value); + CHECK(std::ranges::bidirectional_range); + + using nlohmann::detail::iteration_proxy; + using items_type = decltype(std::declval().items()); + CHECK(std::is_same>::value); + CHECK(std::is_same, std::ranges::iterator_t>::value); + CHECK(std::ranges::input_range); + } + + // libstdc++ algorithms don't work with Clang 15 (04/2022) +#if !defined(__clang__) || (defined(__clang__) && defined(__GLIBCXX__)) + 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); + } + } +#endif + + // libstdc++ views don't work with Clang 15 (04/2022) + // libc++ hides limited ranges implementation behind guard macro +#if !(defined(__clang__) && (defined(__GLIBCXX__) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES))) + 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"}; + + auto transformed = j.items() | std::views::transform([](const auto & item) + { + return item.key(); + }); + auto j_transformed = json::array(); + std::ranges::copy(transformed, std::back_inserter(j_transformed)); + + CHECK(j_transformed == j_expected); + } + } +#endif + } +#endif } diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 04cbac679..3ee234eee 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -27,6 +27,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" // for some reason including this after the json header leads to linker errors with VS 2017... @@ -48,7 +55,6 @@ using ordered_json = nlohmann::ordered_json; #endif #if JSON_HAS_EXPERIMENTAL_FILESYSTEM -// JSON_HAS_CPP_17 (magic keyword; do not remove) #include namespace nlohmann::detail { @@ -788,6 +794,7 @@ TEST_CASE("regression tests 2") } #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"); From 046927cc29e4c5ce56adb9777103755318a5e28a Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Fri, 3 Jun 2022 02:51:33 -0400 Subject: [PATCH 11/23] Fix nlohmann/json#3513, explain is_ndarray flag (#3514) * Fix nlohmann/json#3513, explain is_ndarray flag * add test for ndarray size following H --- .../nlohmann/detail/input/binary_reader.hpp | 28 +++++++++++-------- single_include/nlohmann/json.hpp | 28 +++++++++++-------- tests/src/unit-bjdata.cpp | 14 ++++++---- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index d259e0448..cbcb41588 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1938,9 +1938,9 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; - bool inside_ndarray = true; + bool no_ndarray = true; - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, inside_ndarray))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray))) { return false; } @@ -1953,7 +1953,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, size_and_type.second))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second))) { return false; } @@ -1965,7 +1965,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray))) { return false; } @@ -1977,7 +1977,7 @@ class binary_reader { while (current != ']') { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, current))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current))) { return false; } @@ -1990,12 +1990,16 @@ class binary_reader /*! @param[out] result determined size - @param[in,out] inside_ndarray whether the parser is parsing an ND array dimensional vector + @param[in,out] is_ndarray for input, `true` means already inside an ndarray vector + or ndarray dimension is not allowed; `false` means ndarray + is allowed; for output, `true` means an ndarray is found; + is_ndarray can only return `true` when its initial value + is `false` @param[in] prefix type marker if already read, otherwise set to 0 @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result, bool& inside_ndarray, char_int_type prefix = 0) + bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) { if (prefix == 0) { @@ -2130,9 +2134,9 @@ class binary_reader { break; } - if (inside_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimention vector can only contain integers", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimentional vector is not allowed", "size"), nullptr)); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -2169,7 +2173,7 @@ class binary_reader return false; } } - inside_ndarray = true; + is_ndarray = true; return sax->end_array(); } result = 0; @@ -2650,8 +2654,8 @@ class binary_reader { // get size of following number string std::size_t size{}; - bool inside_ndarray = false; - auto res = get_ubjson_size_value(size, inside_ndarray); + bool no_ndarray = true; + auto res = get_ubjson_size_value(size, no_ndarray); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 30386c5e8..91b31eda4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10528,9 +10528,9 @@ class binary_reader { std::pair size_and_type; size_t dimlen = 0; - bool inside_ndarray = true; + bool no_ndarray = true; - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, inside_ndarray))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray))) { return false; } @@ -10543,7 +10543,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, size_and_type.second))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second))) { return false; } @@ -10555,7 +10555,7 @@ class binary_reader { for (std::size_t i = 0; i < size_and_type.first; ++i) { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray))) { return false; } @@ -10567,7 +10567,7 @@ class binary_reader { while (current != ']') { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, inside_ndarray, current))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current))) { return false; } @@ -10580,12 +10580,16 @@ class binary_reader /*! @param[out] result determined size - @param[in,out] inside_ndarray whether the parser is parsing an ND array dimensional vector + @param[in,out] is_ndarray for input, `true` means already inside an ndarray vector + or ndarray dimension is not allowed; `false` means ndarray + is allowed; for output, `true` means an ndarray is found; + is_ndarray can only return `true` when its initial value + is `false` @param[in] prefix type marker if already read, otherwise set to 0 @return whether size determination completed */ - bool get_ubjson_size_value(std::size_t& result, bool& inside_ndarray, char_int_type prefix = 0) + bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) { if (prefix == 0) { @@ -10720,9 +10724,9 @@ class binary_reader { break; } - if (inside_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimention vector can only contain integers", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimentional vector is not allowed", "size"), nullptr)); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -10759,7 +10763,7 @@ class binary_reader return false; } } - inside_ndarray = true; + is_ndarray = true; return sax->end_array(); } result = 0; @@ -11240,8 +11244,8 @@ class binary_reader { // get size of following number string std::size_t size{}; - bool inside_ndarray = false; - auto res = get_ubjson_size_value(size, inside_ndarray); + bool no_ndarray = true; + auto res = get_ubjson_size_value(size, no_ndarray); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index c93a5de51..a2ea7820f 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2541,7 +2541,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(vI, true, false).is_discarded()); } - SECTION("do not accept NTFZ markers in ndarray optimized type") + SECTION("do not accept NTFZ markers in ndarray optimized type (with count)") { json _; std::vector v_N = {'[', '$', 'N', '#', '[', '#', 'i', 2, 'i', 1, 'i', 2}; @@ -2562,7 +2562,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v_Z, true, false).is_discarded()); } - SECTION("do not accept NTFZ markers in ndarray optimized type") + SECTION("do not accept NTFZ markers in ndarray optimized type (without count)") { json _; std::vector v_N = {'[', '$', 'N', '#', '[', 'i', 1, 'i', 2, ']'}; @@ -2746,7 +2746,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(vh, true, false).is_discarded()); std::vector vR = {'[', '$', 'i', '#', '[', 'i', 1, '[', ']', ']', 1}; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: ndarray dimention vector can only contain integers", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR), "[json.exception.parse_error.113] parse error at byte 8: syntax error while parsing BJData size: ndarray dimentional vector is not allowed", json::parse_error&); CHECK(json::from_bjdata(vR, true, false).is_discarded()); std::vector vRo = {'[', '$', 'i', '#', '[', 'i', 0, '{', '}', ']', 1}; @@ -2754,7 +2754,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(vRo, true, false).is_discarded()); std::vector vR1 = {'[', '$', 'i', '#', '[', '[', 'i', 1, ']', ']', 1}; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR1), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimention vector can only contain integers", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR1), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimentional vector is not allowed", json::parse_error&); CHECK(json::from_bjdata(vR1, true, false).is_discarded()); std::vector vR2 = {'[', '$', 'i', '#', '[', '#', '[', 'i', 1, ']', ']', 1}; @@ -2770,12 +2770,16 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(vR4, true, false).is_discarded()); std::vector vR5 = {'[', '$', 'i', '#', '[', '[', '[', ']', ']', ']'}; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR5), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimention vector can only contain integers", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR5), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimentional vector is not allowed", json::parse_error&); CHECK(json::from_bjdata(vR5, true, false).is_discarded()); std::vector vR6 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'}; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR6), "[json.exception.parse_error.112] parse error at byte 14: syntax error while parsing BJData size: ndarray can not be recursive", json::parse_error&); CHECK(json::from_bjdata(vR6, true, false).is_discarded()); + + std::vector vH = {'[', 'H', '[', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vH), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: ndarray dimentional vector is not allowed", json::parse_error&); + CHECK(json::from_bjdata(vH, true, false).is_discarded()); } SECTION("objects") From 6058d9a8b37568a3e857966adcb970f905639a4d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 3 Jun 2022 14:03:14 +0200 Subject: [PATCH 12/23] Add more macOS builders (#3485) * :construction_worker: add more macOS builders * :construction_worker: add more macOS builders * :green_heart: fix macOS build * :memo: document new Xcode versions --- .github/workflows/macos.yml | 21 +++++++++++++++++++-- README.md | 5 +++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 03c652a81..28e0b74fd 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: jobs: - xcode: + xcode_1: runs-on: macos-10.15 strategy: matrix: @@ -27,8 +27,25 @@ jobs: - name: test run: cd build ; ctest -j 10 --output-on-failure + xcode_2: + runs-on: macos-12 + strategy: + matrix: + xcode: [13.3.1, 13.3, 13.2.1, 13.2, 13.1] + env: + DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer + + steps: + - uses: actions/checkout@v3 + - name: cmake + run: cmake -S . -B build -D CMAKE_BUILD_TYPE=Debug -DJSON_BuildTests=On -DJSON_FastTests=ON + - name: build + run: cmake --build build --parallel 10 + - name: test + run: cd build ; ctest -j 10 --output-on-failure + xcode_standards: - runs-on: macos-10.15 + runs-on: macos-latest strategy: matrix: standard: [11, 14, 17, 20] diff --git a/README.md b/README.md index 96beb8899..bed9b4911 100644 --- a/README.md +++ b/README.md @@ -1087,6 +1087,11 @@ The following compilers are currently used in continuous integration at [AppVeyo | Apple Clang 12.0.0 (clang-1200.0.32.27); Xcode 12.2 | macOS 10.15.7 | GitHub Actions | | Apple Clang 12.0.0 (clang-1200.0.32.28); Xcode 12.3 | macOS 10.15.7 | GitHub Actions | | Apple Clang 12.0.0 (clang-1200.0.32.29); Xcode 12.4 | macOS 10.15.7 | GitHub Actions | +| Apple Clang 13.0.0 (clang-1300.0.29.3); Xcode 13.1 | macOS 12.3.1 | GitHub Actions | +| Apple Clang 13.0.0 (clang-1300.0.29.30); Xcode 13.2 | macOS 12.3.1 | GitHub Actions | +| Apple Clang 13.0.0 (clang-1300.0.29.30); Xcode 13.2.1 | macOS 12.3.1 | GitHub Actions | +| Apple Clang 13.1.6 (clang-1316.0.21.2); Xcode 13.3 | macOS 12.3.1 | GitHub Actions | +| Apple Clang 13.1.6 (clang-1316.0.21.2.3); Xcode 13.3.1 | macOS 12.3.1 | GitHub Actions | | Clang 3.5.2 (3.5.2-3ubuntu1) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 3.6.2 (3.6.2-3ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 3.7.1 (3.7.1-2ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | From 560cede06da882bb730a24363e5bf4eafaa484dc Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Fri, 3 Jun 2022 20:39:24 +0200 Subject: [PATCH 13/23] CI: Remove -Wstrict-overflow (#3516) --- cmake/ci.cmake | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmake/ci.cmake b/cmake/ci.cmake index ed26e02b5..a571c493b 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -343,8 +343,6 @@ set(GCC_CXXFLAGS -Wstrict-aliasing -Wstrict-aliasing=3 -Wstrict-null-sentinel - -Wstrict-overflow - -Wstrict-overflow=5 -Wstring-compare -Wstringop-overflow=4 -Wstringop-overread From 7a6e28acfaeb52ba322116e188cb2b7ce1603dc2 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Fri, 3 Jun 2022 21:05:44 +0200 Subject: [PATCH 14/23] Add assertion to converting constructor (#3517) The converting basic_json constructor can inadvertently change the value type of its parameter. Assert that both basic_json values are of the same value type after conversion. --- include/nlohmann/json.hpp | 1 + single_include/nlohmann/json.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 7a1030c76..629a3edef 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -916,6 +916,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } + JSON_ASSERT(m_type == val.type()); set_parents(); assert_invariant(); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 91b31eda4..c342172b4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -19190,6 +19190,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } + JSON_ASSERT(m_type == val.type()); set_parents(); assert_invariant(); } From 7c65b5cdbdb15805749175fa26ce0a6644e85221 Mon Sep 17 00:00:00 2001 From: ivanovmp Date: Mon, 6 Jun 2022 14:01:46 +0300 Subject: [PATCH 15/23] Update json.hpp (#3499) * Update json.hpp * Remove a space --- include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 629a3edef..8fb894ce7 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -33,7 +33,7 @@ SOFTWARE. * contains the most recent documentation and should also be applicable to * * previous versions; documentation for deprecated functions is not * * removed, but marked deprecated. See "Generate documentation" section in * - * file docs/README.md. * + * file docs/README.md. * \****************************************************************************/ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c342172b4..2837e74b9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -33,7 +33,7 @@ SOFTWARE. * contains the most recent documentation and should also be applicable to * * previous versions; documentation for deprecated functions is not * * removed, but marked deprecated. See "Generate documentation" section in * - * file docs/README.md. * + * file docs/README.md. * \****************************************************************************/ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ From b6d00d18974358ab1d55dcc3379d0ee3052deb4e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 6 Jun 2022 16:46:55 +0200 Subject: [PATCH 16/23] Small documentation fixes (#3520) --- README.md | 3 ++- docs/mkdocs/docs/features/json_pointer.md | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bed9b4911..1f4ea0e61 100644 --- a/README.md +++ b/README.md @@ -1622,6 +1622,7 @@ I deeply appreciate the help of the following people. - [Andrea Cocito](https://github.com/puffetto) added a clarification on macro usage to the documentation. - [Krzysiek Karbowiak](https://github.com/kkarbowiak) refactored the tests to use `CHECK_THROWS_WITH_AS`. - [Chaoqi Zhang](https://github.com/prncoprs) fixed a typo. +- [ivanovmp](https://github.com/ivanovmp) fixed a whitespace error. Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone. @@ -1656,7 +1657,7 @@ The library itself consists of a single header file licensed under the MIT licen ## Projects using JSON for Modern C++ -The library is currently used in Apple macOS Sierra and iOS 10. I am not sure what they are using the library for, but I am happy that it runs on so many devices. +The library is currently used in Apple macOS Sierra-Monterey and iOS 10-15. I am not sure what they are using the library for, but I am happy that it runs on so many devices. ## Notes diff --git a/docs/mkdocs/docs/features/json_pointer.md b/docs/mkdocs/docs/features/json_pointer.md index a7861ec7d..a3980b4c6 100644 --- a/docs/mkdocs/docs/features/json_pointer.md +++ b/docs/mkdocs/docs/features/json_pointer.md @@ -18,11 +18,11 @@ Consider the following JSON document } ``` -Then every value inside the JSON document can be idientified as follows: +Then every value inside the JSON document can be identified as follows: | JSON Pointer | JSON value | |-------------------|----------------------------------------------------------------------------------| -| `/` | `#!json {"array":["A","B","C"],"nested":{"one":1,"two":2,"three":[true,false]}}` | +| `` | `#!json {"array":["A","B","C"],"nested":{"one":1,"two":2,"three":[true,false]}}` | | `/array` | `#!json ["A","B","C"]` | | `/array/0` | `#!json A` | | `/array/1` | `#!json B` | @@ -34,6 +34,9 @@ Then every value inside the JSON document can be idientified as follows: | `/nested/three/0` | `#!json true` | | `/nested/three/1` | `#!json false` | +Note `/` does not identify the root (i.e., the whole document), but an object entry with empty key `""`. See +[RFC 6901](https://tools.ietf.org/html/rfc6901) for more information. + ## JSON Pointer creation JSON Pointers can be created from a string: From 48a102c2c5ef7c2552ea291f5527a8481c98a7a7 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Thu, 9 Jun 2022 08:22:58 +0200 Subject: [PATCH 17/23] Fix ndarray dimension signedness, fix ndarray length overflow (2); add 32bit unit test (#3523) * Fix ndarray dimension signness, fix ndarray length overflow, close #3519 * detect size overflow in ubjson and bjdata * force reformatting * Fix MSVC compiler warning * Add value_in_range_of trait * Use value_in_range_of trait * Correct 408 parse_errors to out_of_range * Add 32bit unit test The test can be enabled by setting JSON_32bitTest=ON. * Exclude unreachable lines from coverage Certain lines are unreachable in 64bit builds. Co-authored-by: Qianqian Fang --- .../nlohmann/detail/input/binary_reader.hpp | 18 ++- include/nlohmann/detail/meta/type_traits.hpp | 95 ++++++++++++++ single_include/nlohmann/json.hpp | 113 ++++++++++++++++- tests/CMakeLists.txt | 68 ++++++---- tests/src/unit-32bit.cpp | 117 +++++++++++++++++ tests/src/unit-bjdata.cpp | 120 +++++++++++++++++- 6 files changed, 503 insertions(+), 28 deletions(-) create mode 100644 tests/src/unit-32bit.cpp diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index cbcb41588..9332af3e6 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2079,6 +2079,12 @@ class binary_reader return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); } + if (!value_in_range_of(number)) + { + // undo coverage exclusion once the 32bit test is run as part of CI (#3524) + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE + exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + } result = static_cast(number); return true; } @@ -2124,6 +2130,12 @@ class binary_reader { return false; } + if (!value_in_range_of(number)) + { + // undo coverage exclusion once the 32bit test is run as part of CI (#3524) + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE + exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + } result = detail::conditional_static_cast(number); return true; } @@ -2168,7 +2180,11 @@ class binary_reader for (auto i : dim) { result *= i; - if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(static_cast(i)))) + if (result == 0) // because dim elements shall not have zeros, result = 0 means overflow happened + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast(i)))) { return false; } diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 5e3ea3737..0901b71bb 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -577,5 +577,100 @@ T conditional_static_cast(U value) return value; } +template +using all_integral = conjunction...>; + +template +using all_signed = conjunction...>; + +template +using all_unsigned = conjunction...>; + +// there's a disjunction trait in another PR; replace when merged +template +using same_sign = std::integral_constant < bool, + all_signed::value || all_unsigned::value >; + +template +using never_out_of_range = std::integral_constant < bool, + (std::is_signed::value && (sizeof(T) < sizeof(OfType))) + || (same_sign::value && sizeof(OfType) == sizeof(T)) >; + +template::value, + bool TSigned = std::is_signed::value> +struct value_in_range_of_impl2; + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return val >= 0 && static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return static_cast(val) >= static_cast(std::numeric_limits::min()) + && static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + +template::value, + typename = detail::enable_if_t::value>> +struct value_in_range_of_impl1; + +template +struct value_in_range_of_impl1 +{ + static constexpr bool test(T val) + { + return value_in_range_of_impl2::test(val); + } +}; + +template +struct value_in_range_of_impl1 +{ + static constexpr bool test(T /*val*/) + { + return true; + } +}; + +template +inline constexpr bool value_in_range_of(T val) +{ + return value_in_range_of_impl1::test(val); +} + } // namespace detail } // namespace nlohmann diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2837e74b9..3dcd48027 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3768,6 +3768,101 @@ T conditional_static_cast(U value) return value; } +template +using all_integral = conjunction...>; + +template +using all_signed = conjunction...>; + +template +using all_unsigned = conjunction...>; + +// there's a disjunction trait in another PR; replace when merged +template +using same_sign = std::integral_constant < bool, + all_signed::value || all_unsigned::value >; + +template +using never_out_of_range = std::integral_constant < bool, + (std::is_signed::value && (sizeof(T) < sizeof(OfType))) + || (same_sign::value && sizeof(OfType) == sizeof(T)) >; + +template::value, + bool TSigned = std::is_signed::value> +struct value_in_range_of_impl2; + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return val >= 0 && static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + + +template +struct value_in_range_of_impl2 +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type::type; + return static_cast(val) >= static_cast(std::numeric_limits::min()) + && static_cast(val) <= static_cast(std::numeric_limits::max()); + } +}; + +template::value, + typename = detail::enable_if_t::value>> +struct value_in_range_of_impl1; + +template +struct value_in_range_of_impl1 +{ + static constexpr bool test(T val) + { + return value_in_range_of_impl2::test(val); + } +}; + +template +struct value_in_range_of_impl1 +{ + static constexpr bool test(T /*val*/) + { + return true; + } +}; + +template +inline constexpr bool value_in_range_of(T val) +{ + return value_in_range_of_impl1::test(val); +} + } // namespace detail } // namespace nlohmann @@ -10669,6 +10764,12 @@ class binary_reader return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); } + if (!value_in_range_of(number)) + { + // undo coverage exclusion once the 32bit test is run as part of CI (#3524) + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE + exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + } result = static_cast(number); return true; } @@ -10714,6 +10815,12 @@ class binary_reader { return false; } + if (!value_in_range_of(number)) + { + // undo coverage exclusion once the 32bit test is run as part of CI (#3524) + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE + exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + } result = detail::conditional_static_cast(number); return true; } @@ -10758,7 +10865,11 @@ class binary_reader for (auto i : dim) { result *= i; - if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(static_cast(i)))) + if (result == 0) // because dim elements shall not have zeros, result = 0 means overflow happened + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast(i)))) { return false; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 010c4950e..ef87c4909 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.13) option(JSON_Valgrind "Execute test suite with Valgrind." OFF) option(JSON_FastTests "Skip expensive/slow tests." OFF) +option(JSON_32bitTest "Enable the 32bit unit test." OFF) set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.") @@ -33,35 +34,40 @@ endif() # test_main library with shared code to speed up build and common settings ############################################################################# -add_library(test_main OBJECT src/unit.cpp) -target_compile_definitions(test_main PUBLIC +set(test_main_SOURCES src/unit.cpp) +set(test_main_COMPILE_DEFINITIONS PUBLIC DOCTEST_CONFIG_SUPER_FAST_ASSERTS - JSON_TEST_KEEP_MACROS -) -target_compile_features(test_main PRIVATE cxx_std_11) -target_compile_options(test_main PUBLIC - $<$:/EHsc;$<$:/Od>> - # MSVC: Force to always compile with W4 - # Disable warning C4566: character represented by universal-character-name '\uFF01' - # cannot be represented in the current code page (1252) - # Disable warning C4996: 'nlohmann::basic_json<...>::operator <<': was declared deprecated - $<$:/W4 /wd4566 /wd4996> - # https://github.com/nlohmann/json/issues/1114 - $<$:/bigobj> $<$:-Wa,-mbig-obj> + JSON_TEST_KEEP_MACROS) +set(test_main_COMPILE_FEATURES PRIVATE cxx_std_11) +set(test_main_COMPILE_OPTIONS + PUBLIC + $<$:/EHsc;$<$:/Od>> + # MSVC: Force to always compile with W4 + # Disable warning C4566: character represented by universal-character-name '\uFF01' + # cannot be represented in the current code page (1252) + # Disable warning C4996: 'nlohmann::basic_json<...>::operator <<': was declared deprecated + $<$:/W4 /wd4566 /wd4996> + # https://github.com/nlohmann/json/issues/1114 + $<$:/bigobj> $<$:-Wa,-mbig-obj> - # https://github.com/nlohmann/json/pull/3229 - $<$:-diag-disable=2196> + # https://github.com/nlohmann/json/pull/3229 + $<$:-diag-disable=2196> - $<$>:-Wno-deprecated;-Wno-float-equal> - $<$:-Wno-deprecated-declarations> - $<$:-diag-disable=1786> -) -target_include_directories(test_main PUBLIC + $<$>:-Wno-deprecated;-Wno-float-equal> + $<$:-Wno-deprecated-declarations> + $<$:-diag-disable=1786>) +set(test_main_INCLUDE_DIRECTORIES PUBLIC thirdparty/doctest thirdparty/fifo_map - ${PROJECT_BINARY_DIR}/include -) -target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME}) + ${PROJECT_BINARY_DIR}/include) +set(test_main_LINK_LIBRARIES PUBLIC ${NLOHMANN_JSON_TARGET_NAME}) + +add_library(test_main OBJECT ${test_main_SOURCES}) +target_compile_definitions(test_main ${test_main_COMPILE_DEFINITIONS}) +target_compile_features(test_main ${test_main_COMPILE_FEATURES}) +target_compile_options(test_main ${test_main_COMPILE_OPTIONS}) +target_include_directories(test_main ${test_main_INCLUDE_DIRECTORIES}) +target_link_libraries(test_main ${test_main_LINK_LIBRARIES}) ############################################################################# # define test- and standard-specific build settings @@ -119,10 +125,24 @@ message(STATUS "${msg}") # *DO* use json_test_set_test_options() above this line file(GLOB files src/unit-*.cpp) +list(FILTER files EXCLUDE REGEX "src/unit-32bit.cpp") foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() +if(JSON_32bitTest) + add_library(test_main32 OBJECT ${test_main_SOURCES}) + target_compile_definitions(test_main32 ${test_main_COMPILE_DEFINITIONS}) + target_compile_features(test_main32 ${test_main_COMPILE_FEATURES}) + target_compile_options(test_main32 ${test_main_COMPILE_OPTIONS} -m32) + target_include_directories(test_main32 ${test_main_INCLUDE_DIRECTORIES}) + target_link_libraries(test_main32 ${test_main_LINK_LIBRARIES}) + target_link_options(test_main32 PUBLIC -m32) + + json_test_add_test_for("src/unit-32bit.cpp" MAIN test_main32 + CXX_STANDARDS ${test_cxx_standards} ${test_force}) +endif() + # test legacy comparison of discarded values json_test_set_test_options(test-comparison_legacy COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 diff --git a/tests/src/unit-32bit.cpp b/tests/src/unit-32bit.cpp new file mode 100644 index 000000000..711dda5a0 --- /dev/null +++ b/tests/src/unit-32bit.cpp @@ -0,0 +1,117 @@ +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +#include // SIZE_MAX +#include // numeric_limits + + +template +struct trait_test_arg +{ + using of_type = OfType; + using type = T; + static constexpr bool min_in_range = MinInRange; + static constexpr bool max_in_range = MaxInRange; +}; + +TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) +{ + using nlohmann::detail::value_in_range_of; + + using of_type = typename T::of_type; + using type = typename T::type; + constexpr bool min_in_range = T::min_in_range; + constexpr bool max_in_range = T::max_in_range; + + type val_min = std::numeric_limits::min(); + type val_min2 = val_min + 1; + type val_max = std::numeric_limits::max(); + type val_max2 = val_max - 1; + + REQUIRE(CHAR_BIT == 8); + + std::string of_type_str; + if (std::is_unsigned::value) + { + of_type_str += "u"; + } + of_type_str += "int"; + of_type_str += std::to_string(sizeof(of_type) * 8); + + INFO("of_type := ", of_type_str); + + std::string type_str; + if (std::is_unsigned::value) + { + type_str += "u"; + } + type_str += "int"; + type_str += std::to_string(sizeof(type) * 8); + + INFO("type := ", type_str); + + CAPTURE(val_min); + CAPTURE(min_in_range); + CAPTURE(val_max); + CAPTURE(max_in_range); + + if (min_in_range) + { + CHECK(value_in_range_of(val_min)); + CHECK(value_in_range_of(val_min2)); + } + else + { + CHECK_FALSE(value_in_range_of(val_min)); + CHECK_FALSE(value_in_range_of(val_min2)); + } + + if (max_in_range) + { + CHECK(value_in_range_of(val_max)); + CHECK(value_in_range_of(val_max2)); + } + else + { + CHECK_FALSE(value_in_range_of(val_max)); + CHECK_FALSE(value_in_range_of(val_max2)); + } +} + + +TEST_CASE("32bit") +{ + REQUIRE(SIZE_MAX == 0xffffffff); +} + +TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg); + +TEST_CASE("BJData") +{ + SECTION("parse errors") + { + SECTION("array") + { + SECTION("optimized array: no size following type") + { + std::vector v = {'[', '$', 'i', 2}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02", json::parse_error&); + } + + SECTION("optimized array: negative size") + { + std::vector vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'}; + + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + } + } + } +} diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index a2ea7820f..974ee3a0a 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -29,6 +29,8 @@ SOFTWARE. #include "doctest_compatibility.h" +#include +#include #include using nlohmann::json; @@ -116,6 +118,112 @@ class SaxCountdown }; } // namespace +// at some point in the future, a unit test dedicated to type traits might be a good idea +template +struct trait_test_arg +{ + using of_type = OfType; + using type = T; + static constexpr bool min_in_range = MinInRange; + static constexpr bool max_in_range = MaxInRange; +}; + +TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) +{ + using nlohmann::detail::value_in_range_of; + + using of_type = typename T::of_type; + using type = typename T::type; + constexpr bool min_in_range = T::min_in_range; + constexpr bool max_in_range = T::max_in_range; + + type val_min = std::numeric_limits::min(); + type val_min2 = val_min + 1; + type val_max = std::numeric_limits::max(); + type val_max2 = val_max - 1; + + REQUIRE(CHAR_BIT == 8); + + std::string of_type_str; + if (std::is_unsigned::value) + { + of_type_str += "u"; + } + of_type_str += "int"; + of_type_str += std::to_string(sizeof(of_type) * 8); + + INFO("of_type := ", of_type_str); + + std::string type_str; + if (std::is_unsigned::value) + { + type_str += "u"; + } + type_str += "int"; + type_str += std::to_string(sizeof(type) * 8); + + INFO("type := ", type_str); + + CAPTURE(val_min); + CAPTURE(min_in_range); + CAPTURE(val_max); + CAPTURE(max_in_range); + + if (min_in_range) + { + CHECK(value_in_range_of(val_min)); + CHECK(value_in_range_of(val_min2)); + } + else + { + CHECK_FALSE(value_in_range_of(val_min)); + CHECK_FALSE(value_in_range_of(val_min2)); + } + + if (max_in_range) + { + CHECK(value_in_range_of(val_max)); + CHECK(value_in_range_of(val_max2)); + } + else + { + CHECK_FALSE(value_in_range_of(val_max)); + CHECK_FALSE(value_in_range_of(val_max2)); + } +} + +TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg); + +#if SIZE_MAX == 0xffffffff +TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg); +#else +TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg, \ + trait_test_arg); +#endif + TEST_CASE("BJData") { SECTION("individual values") @@ -2511,6 +2619,7 @@ TEST_CASE("BJData") std::vector vI = {'[', '#', 'I', 0x00, 0xF1}; std::vector vl = {'[', '#', 'l', 0x00, 0x00, 0x00, 0xF2}; std::vector vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3}; + std::vector vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'}; json _; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); @@ -2535,10 +2644,17 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(vI, true, false).is_discarded()); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); - CHECK(json::from_bjdata(vI, true, false).is_discarded()); + CHECK(json::from_bjdata(vl, true, false).is_discarded()); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); - CHECK(json::from_bjdata(vI, true, false).is_discarded()); + CHECK(json::from_bjdata(vL, true, false).is_discarded()); + +#if SIZE_MAX == 0xffffffff + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); +#else + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&); +#endif + CHECK(json::from_bjdata(vM, true, false).is_discarded()); } SECTION("do not accept NTFZ markers in ndarray optimized type (with count)") From af34396129098e5702f6b611811c43f30827b247 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 12 Jun 2022 14:31:07 +0200 Subject: [PATCH 18/23] Use new CI image (#3528) * :arrow_up: use new CI image * :arrow_up: use new CI image * :rotating_light: fix warning * :wrench: remove duplicate warning flags * :green_heart: fix test * :memo: update compiler versions --- .clang-tidy | 1 + .github/workflows/ubuntu.yml | 18 +++++++++--------- README.md | 7 ++++--- cmake/ci.cmake | 16 ++++++++++++---- include/nlohmann/detail/meta/type_traits.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 3673930d4..b981348f3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -43,6 +43,7 @@ Checks: '*, -readability-identifier-length, -readability-magic-numbers, -readability-redundant-access-specifiers, + -readability-simplify-boolean-expr -readability-uppercase-literal-suffix' CheckOptions: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 39098e4d7..c24332628 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -12,7 +12,7 @@ on: jobs: ci_test_clang: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 steps: - uses: actions/checkout@v3 - name: cmake @@ -22,7 +22,7 @@ jobs: ci_test_gcc: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 steps: - uses: actions/checkout@v3 - name: cmake @@ -32,7 +32,7 @@ jobs: ci_static_analysis: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 strategy: matrix: target: [ci_clang_tidy, ci_cppcheck, ci_test_valgrind, ci_test_clang_sanitizer, ci_test_amalgamation, ci_clang_analyze, ci_cpplint, ci_cmake_flags, ci_single_binaries, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_infer] @@ -45,7 +45,7 @@ jobs: ci_cmake_options: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 strategy: matrix: target: [ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison] @@ -58,7 +58,7 @@ jobs: ci_test_coverage: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 steps: - uses: actions/checkout@v3 - name: cmake @@ -78,10 +78,10 @@ jobs: ci_test_compilers: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 strategy: matrix: - compiler: [g++-4.8, g++-4.9, g++-5, g++-6, g++-7, g++-8, g++-9, g++-10, clang++-3.5, clang++-3.6, clang++-3.7, clang++-3.8, clang++-3.9, clang++-4.0, clang++-5.0, clang++-6.0, clang++-7, clang++-8, clang++-9, clang++-10, clang++-11, clang++-12, clang++-13, clang++-14] + compiler: [g++-4.8, g++-4.9, g++-5, g++-6, g++-7, g++-8, g++-9, g++-10, g++-11, clang++-3.5, clang++-3.6, clang++-3.7, clang++-3.8, clang++-3.9, clang++-4.0, clang++-5.0, clang++-6.0, clang++-7, clang++-8, clang++-9, clang++-10, clang++-11, clang++-12, clang++-13, clang++-14] steps: - uses: actions/checkout@v3 - name: cmake @@ -91,7 +91,7 @@ jobs: ci_test_standards: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 strategy: matrix: standard: [11, 14, 17, 20] @@ -105,7 +105,7 @@ jobs: ci_cuda_example: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.3.0 + container: ghcr.io/nlohmann/json-ci:v2.4.0 steps: - uses: actions/checkout@v3 - name: cmake diff --git a/README.md b/README.md index 1f4ea0e61..220b3a9f1 100644 --- a/README.md +++ b/README.md @@ -1110,8 +1110,8 @@ The following compilers are currently used in continuous integration at [AppVeyo | Clang 11.0.0 (11.0.0-2~ubuntu20.04.1) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 12.0.0 (12.0.0-3ubuntu1~20.04.3) | Ubuntu 20.04.3 LTS | GitHub Actions | | Clang 13.0.1 (13.0.1-++20211015123032+cf15ccdeb6d5-1exp120211015003613.5) | Ubuntu 20.04.3 LTS | GitHub Actions | -| Clang 14.0.0 (14.0.0-++20211221052852+55c71c9eac9b-1exp120211221172954.95) | Ubuntu 20.04.3 LTS | GitHub Actions | -| Clang 15.0.0 (15.0.0-++20220403052648+896770c9a92e-1~exp1~20220403172744.209) | Ubuntu 20.04.3 LTS | GitHub Actions | +| Clang 14.0.5-++20220603124341+2f0a69c32a4c-1~exp1~20220603124352.149 | Ubuntu 20.04.3 LTS | GitHub Actions | +| Clang 15.0.0 (15.0.0-++20220530052901+b7d2b160c3ba-1~exp1~20220530172952.268) | Ubuntu 20.04.3 LTS | GitHub Actions | | GCC 4.8.5 (Ubuntu 4.8.5-4ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | | GCC 4.9.3 (Ubuntu 4.9.3-13ubuntu2) | Ubuntu 20.04.3 LTS | GitHub Actions | | GCC 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.12) | Ubuntu 20.04.3 LTS | GitHub Actions | @@ -1123,7 +1123,8 @@ The following compilers are currently used in continuous integration at [AppVeyo | GCC 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | | GCC 10.2.0 (Ubuntu 10.2.0-5ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | | GCC 11.1.0 | Ubuntu (aarch64) | Drone CI | -| GCC 12.0.0 20211219 (experimental) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 11.1.0 (Ubuntu 11.1.0-1ubuntu1~20.04) | Ubuntu 20.04.3 LTS | GitHub Actions | +| GCC 13.0.0 13.0.0 20220605 (experimental) | Ubuntu 20.04.3 LTS | GitHub Actions | | Intel C++ Compiler 2021.5.0.20211109 | Ubuntu 20.04.3 LTS | GitHub Actions | | NVCC 11.0.221 | Ubuntu 20.04.3 LTS | GitHub Actions | | Visual Studio 14 2015 MSVC 19.0.24241.7 (Build Engine version 14.0.25420.1) | Windows-6.3.9600 | AppVeyor | diff --git a/cmake/ci.cmake b/cmake/ci.cmake index a571c493b..d813a1773 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -111,7 +111,7 @@ set(CLANG_CXXFLAGS -Wno-reserved-identifier ) -# Warning flags determined for GCC 12.0 (experimental) with https://github.com/nlohmann/gcc_flags: +# Warning flags determined for GCC 13.0 (experimental) with https://github.com/nlohmann/gcc_flags: # Ignored GCC warnings: # -Wno-abi-tag We do not care about ABI tags. # -Wno-aggregate-return The library uses aggregate returns. @@ -161,10 +161,13 @@ set(GCC_CXXFLAGS -Wanalyzer-use-after-free -Wanalyzer-use-of-pointer-in-stale-stack-frame -Wanalyzer-use-of-uninitialized-value + -Wanalyzer-va-arg-type-mismatch + -Wanalyzer-va-list-exhausted + -Wanalyzer-va-list-leak + -Wanalyzer-va-list-use-after-va-end -Wanalyzer-write-to-const -Wanalyzer-write-to-string-literal -Warith-conversion - -Warray-bounds -Warray-bounds=2 -Warray-compare -Warray-parameter=2 @@ -209,6 +212,7 @@ set(GCC_CXXFLAGS -Wctad-maybe-unsupported -Wctor-dtor-privacy -Wdangling-else + -Wdangling-pointer=2 -Wdate-time -Wdelete-incomplete -Wdelete-non-virtual-dtor @@ -279,6 +283,7 @@ set(GCC_CXXFLAGS -Wmissing-include-dirs -Wmissing-profile -Wmissing-requires + -Wmissing-template-keyword -Wmultichar -Wmultiple-inheritance -Wmultistatement-macros @@ -340,9 +345,9 @@ set(GCC_CXXFLAGS -Wsizeof-pointer-div -Wsizeof-pointer-memaccess -Wstack-protector - -Wstrict-aliasing -Wstrict-aliasing=3 -Wstrict-null-sentinel + -Wno-strict-overflow -Wstring-compare -Wstringop-overflow=4 -Wstringop-overread @@ -371,6 +376,7 @@ set(GCC_CXXFLAGS -Wterminate -Wtrampolines -Wtrigraphs + -Wtrivial-auto-var-init -Wtsan -Wtype-limits -Wundef @@ -390,6 +396,7 @@ set(GCC_CXXFLAGS -Wunused-result -Wunused-value -Wunused-variable + -Wuse-after-free=3 -Wuseless-cast -Wvarargs -Wvariadic-macros @@ -710,6 +717,7 @@ add_custom_target(ci_offline_testdata ############################################################################### add_custom_target(ci_non_git_tests + COMMAND git config --global --add safe.directory ${PROJECT_SOURCE_DIR} COMMAND mkdir -p ${PROJECT_BINARY_DIR}/build_non_git_tests/sources COMMAND cd ${PROJECT_SOURCE_DIR} && for FILE in `${GIT_TOOL} ls-tree --name-only HEAD`\; do cp -r $$FILE ${PROJECT_BINARY_DIR}/build_non_git_tests/sources \; done COMMAND ${CMAKE_COMMAND} @@ -854,7 +862,7 @@ add_custom_target(ci_cmake_flags # Use more installed compilers. ############################################################################### -foreach(COMPILER g++-4.8 g++-4.9 g++-5 g++-6 g++-7 g++-8 g++-9 g++-10 clang++-3.5 clang++-3.6 clang++-3.7 clang++-3.8 clang++-3.9 clang++-4.0 clang++-5.0 clang++-6.0 clang++-7 clang++-8 clang++-9 clang++-10 clang++-11 clang++-12 clang++-13 clang++-14) +foreach(COMPILER g++-4.8 g++-4.9 g++-5 g++-6 g++-7 g++-8 g++-9 g++-10 g++-11 clang++-3.5 clang++-3.6 clang++-3.7 clang++-3.8 clang++-3.9 clang++-4.0 clang++-5.0 clang++-6.0 clang++-7 clang++-8 clang++-9 clang++-10 clang++-11 clang++-12 clang++-13 clang++-14) find_program(COMPILER_TOOL NAMES ${COMPILER}) if (COMPILER_TOOL) if ("${COMPILER}" STREQUAL "clang++-9") diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 0901b71bb..cc3483122 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -183,7 +183,7 @@ template struct conjunction : std::true_type { }; template struct conjunction : B { }; template struct conjunction -: std::conditional, B>::type {}; +: std::conditional(B::value), conjunction, B>::type {}; // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3dcd48027..c5fd3fd0b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3374,7 +3374,7 @@ template struct conjunction : std::true_type { }; template struct conjunction : B { }; template struct conjunction -: std::conditional, B>::type {}; +: std::conditional(B::value), conjunction, B>::type {}; // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; From 9c31d543890ce0b1b5fe5a4bb07465a289cc60ec Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Mon, 13 Jun 2022 13:21:55 +0200 Subject: [PATCH 19/23] Add to_json() for std::vector::reference (#3534) --- include/nlohmann/detail/conversions/to_json.hpp | 7 +++++++ single_include/nlohmann/json.hpp | 7 +++++++ tests/src/unit-constructor1.cpp | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index fde46ef7c..b59077338 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -272,6 +272,13 @@ inline void to_json(BasicJsonType& j, T b) noexcept external_constructor::construct(j, b); } +template::reference&, typename BasicJsonType::boolean_t>::value, int> = 0> +inline void to_json(BasicJsonType& j, const std::vector::reference& b) noexcept +{ + external_constructor::construct(j, static_cast(b)); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, const CompatibleString& s) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c5fd3fd0b..4a73ce5d3 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5253,6 +5253,13 @@ inline void to_json(BasicJsonType& j, T b) noexcept external_constructor::construct(j, b); } +template::reference&, typename BasicJsonType::boolean_t>::value, int> = 0> +inline void to_json(BasicJsonType& j, const std::vector::reference& b) noexcept +{ + external_constructor::construct(j, static_cast(b)); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, const CompatibleString& s) diff --git a/tests/src/unit-constructor1.cpp b/tests/src/unit-constructor1.cpp index 195521226..21f21e380 100644 --- a/tests/src/unit-constructor1.cpp +++ b/tests/src/unit-constructor1.cpp @@ -474,6 +474,13 @@ TEST_CASE("constructors") json j(false); CHECK(j.type() == json::value_t::boolean); } + + SECTION("from std::vector::refrence") + { + std::vector v{true}; + json j(v[0]); + CHECK(j.type() == json::value_t::boolean); + } } SECTION("create a binary (explicit)") From e80945da2cecab8ed1b6a35c4784614801222d58 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Tue, 14 Jun 2022 08:54:12 +0200 Subject: [PATCH 20/23] CI: Enable 32bit unit test (3) (#3532) * Enable JSON_MultipleHeaders by default * CI: Add single-header build * CI: Enable 32bit unit test * Fix "-Wuseless-cast" warnings * Remove coverage exclusion * Fix 32bit test case --- .github/workflows/ubuntu.yml | 10 +++ CMakeLists.txt | 2 +- cmake/ci.cmake | 39 ++++++++--- cmake/test.cmake | 29 ++++++++ .../nlohmann/detail/input/binary_reader.hpp | 33 +++++---- single_include/nlohmann/json.hpp | 33 +++++---- tests/CMakeLists.txt | 70 ++++++++----------- tests/src/unit-32bit.cpp | 12 ++-- tests/src/unit-bjdata.cpp | 19 ++++- 9 files changed, 152 insertions(+), 95 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index c24332628..440710427 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -43,6 +43,16 @@ jobs: - name: build run: cmake --build build --target ${{ matrix.target }} + ci_test_single_header: + runs-on: ubuntu-latest + container: ghcr.io/nlohmann/json-ci:v2.4.0 + steps: + - uses: actions/checkout@v3 + - name: cmake + run: cmake -S . -B build -DJSON_CI=On + - name: build + run: cmake --build build --target ci_test_single_header + ci_cmake_options: runs-on: ubuntu-latest container: ghcr.io/nlohmann/json-ci:v2.4.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 632e6dec2..aae5fb6e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ option(JSON_Diagnostics "Use extended diagnostic messages." O option(JSON_ImplicitConversions "Enable implicit conversions." ON) option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF) option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) -option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF) +option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON) option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) if (JSON_CI) diff --git a/cmake/ci.cmake b/cmake/ci.cmake index d813a1773..8704d4a54 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -416,7 +416,7 @@ set(GCC_CXXFLAGS add_custom_target(ci_test_gcc COMMAND CXX=${GCC_TOOL} CXXFLAGS="${GCC_CXXFLAGS}" ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON + -DJSON_BuildTests=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_gcc COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_gcc COMMAND cd ${PROJECT_BINARY_DIR}/build_gcc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure @@ -426,7 +426,7 @@ add_custom_target(ci_test_gcc add_custom_target(ci_test_clang COMMAND CXX=${CLANG_TOOL} CXXFLAGS="${CLANG_CXXFLAGS}" ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON + -DJSON_BuildTests=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_clang COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_clang COMMAND cd ${PROJECT_BINARY_DIR}/build_clang && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure @@ -468,7 +468,7 @@ endforeach() add_custom_target(ci_test_noexceptions COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON -DCMAKE_CXX_FLAGS=-DJSON_NOEXCEPTION -DDOCTEST_TEST_FILTER=--no-throw + -DJSON_BuildTests=ON -DCMAKE_CXX_FLAGS=-DJSON_NOEXCEPTION -DDOCTEST_TEST_FILTER=--no-throw -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_noexceptions COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_noexceptions COMMAND cd ${PROJECT_BINARY_DIR}/build_noexceptions && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure @@ -482,7 +482,7 @@ add_custom_target(ci_test_noexceptions add_custom_target(ci_test_noimplicitconversions COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON -DJSON_ImplicitConversions=OFF + -DJSON_BuildTests=ON -DJSON_ImplicitConversions=OFF -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_noimplicitconversions COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_noimplicitconversions COMMAND cd ${PROJECT_BINARY_DIR}/build_noimplicitconversions && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure @@ -496,7 +496,7 @@ add_custom_target(ci_test_noimplicitconversions add_custom_target(ci_test_diagnostics COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON -DJSON_Diagnostics=ON + -DJSON_BuildTests=ON -DJSON_Diagnostics=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_diagnostics COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_diagnostics COMMAND cd ${PROJECT_BINARY_DIR}/build_diagnostics && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure @@ -510,7 +510,7 @@ add_custom_target(ci_test_diagnostics add_custom_target(ci_test_legacycomparison COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON -DJSON_LegacyDiscardedValueComparison=ON + -DJSON_BuildTests=ON -DJSON_LegacyDiscardedValueComparison=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_legacycomparison COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_legacycomparison COMMAND cd ${PROJECT_BINARY_DIR}/build_legacycomparison && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure @@ -524,11 +524,18 @@ add_custom_target(ci_test_legacycomparison add_custom_target(ci_test_coverage COMMAND CXX=g++ ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage" - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON + -DJSON_BuildTests=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMAND CXX=g++ ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage" + -DJSON_BuildTests=ON -DJSON_32bitTest=ONLY + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage32 + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32 + COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc lcov_branch_coverage=1 COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc lcov_branch_coverage=1 COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept @@ -581,6 +588,20 @@ add_custom_target(ci_test_amalgamation COMMENT "Check amalgamation and indentation" ) +############################################################################### +# Build and test using the amalgamated header +############################################################################### + +add_custom_target(ci_test_single_header + COMMAND CXX=${GCC_TOOL} CXXFLAGS="${GCC_CXXFLAGS}" ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja + -DJSON_BuildTests=ON -DJSON_MultipleHeader=OFF -DJSON_FastTests=ON + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_single_header + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_single_header + COMMAND cd ${PROJECT_BINARY_DIR}/build_single_header && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMENT "Compile and test single-header version" +) + ############################################################################### # Valgrind. ############################################################################### @@ -664,7 +685,7 @@ add_custom_target(ci_clang_tidy COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_CLANG_TIDY=${CLANG_TIDY_TOOL} - -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON + -DJSON_BuildTests=ON -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_clang_tidy COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_clang_tidy COMMENT "Check code with Clang-Tidy" @@ -691,7 +712,7 @@ add_custom_target(ci_pvs_studio add_custom_target(ci_infer COMMAND mkdir -p ${PROJECT_BINARY_DIR}/build_infer - COMMAND cd ${PROJECT_BINARY_DIR}/build_infer && ${INFER_TOOL} compile -- ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${PROJECT_SOURCE_DIR} -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON + COMMAND cd ${PROJECT_BINARY_DIR}/build_infer && ${INFER_TOOL} compile -- ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${PROJECT_SOURCE_DIR} -DJSON_BuildTests=ON COMMAND cd ${PROJECT_BINARY_DIR}/build_infer && ${INFER_TOOL} run -- make COMMENT "Check code with Infer" ) diff --git a/cmake/test.cmake b/cmake/test.cmake index 89b7c6ec7..b8b1250fb 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -211,3 +211,32 @@ function(json_test_add_test_for file) _json_test_add_test(${test_name} ${file} ${args_MAIN} ${cxx_standard}) endforeach() endfunction() + +############################################################################# +# json_test_should_build_32bit_test( +# ) +# +# Check if the 32bit unit test should be built based on the value of +# and store the result in the variables and +# . +############################################################################# + +function(json_test_should_build_32bit_test build_32bit_var build_32bit_only_var input) + set(${build_32bit_only_var} OFF PARENT_SCOPE) + string(TOUPPER "${input}" ${build_32bit_var}) + if("${${build_32bit_var}}" STREQUAL AUTO) + # check if compiler is targeting 32bit by default + include(CheckTypeSize) + check_type_size("size_t" sizeof_size_t LANGUAGE CXX) + if(sizeof_size_t AND ${sizeof_size_t} EQUAL 4) + message(STATUS "Auto-enabling 32bit unit test.") + set(${build_32bit_var} ON) + else() + set(${build_32bit_var} OFF) + endif() + elseif("${${build_32bit_var}}" STREQUAL ONLY) + set(${build_32bit_only_var} ON PARENT_SCOPE) + endif() + + set(${build_32bit_var} "${${build_32bit_var}}" PARENT_SCOPE) +endfunction() diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 9332af3e6..f5871ddf6 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -618,7 +618,8 @@ class binary_reader case 0x95: case 0x96: case 0x97: - return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); + return get_cbor_array( + conditional_static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0x98: // array (one-byte uint8_t for n follows) { @@ -635,13 +636,13 @@ class binary_reader case 0x9A: // array (four-byte uint32_t for n follow) { std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast(len), tag_handler); } case 0x9B: // array (eight-byte uint64_t for n follow) { std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(detail::conditional_static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast(len), tag_handler); } case 0x9F: // array (indefinite length) @@ -672,7 +673,7 @@ class binary_reader case 0xB5: case 0xB6: case 0xB7: - return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); + return get_cbor_object(conditional_static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0xB8: // map (one-byte uint8_t for n follows) { @@ -689,13 +690,13 @@ class binary_reader case 0xBA: // map (four-byte uint32_t for n follow) { std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast(len), tag_handler); } case 0xBB: // map (eight-byte uint64_t for n follow) { std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(detail::conditional_static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast(len), tag_handler); } case 0xBF: // map (indefinite length) @@ -1342,7 +1343,7 @@ class binary_reader case 0x8D: case 0x8E: case 0x8F: - return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); + return get_msgpack_object(conditional_static_cast(static_cast(current) & 0x0Fu)); // fixarray case 0x90: @@ -1361,7 +1362,7 @@ class binary_reader case 0x9D: case 0x9E: case 0x9F: - return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); + return get_msgpack_array(conditional_static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: @@ -1498,7 +1499,7 @@ class binary_reader case 0xDD: // array 32 { std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + return get_number(input_format_t::msgpack, len) && get_msgpack_array(conditional_static_cast(len)); } case 0xDE: // map 16 @@ -1510,7 +1511,7 @@ class binary_reader case 0xDF: // map 32 { std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + return get_number(input_format_t::msgpack, len) && get_msgpack_object(conditional_static_cast(len)); } // negative fixint @@ -2081,9 +2082,8 @@ class binary_reader } if (!value_in_range_of(number)) { - // undo coverage exclusion once the 32bit test is run as part of CI (#3524) - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE - exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); } result = static_cast(number); return true; @@ -2115,7 +2115,7 @@ class binary_reader { return false; } - result = static_cast(number); + result = conditional_static_cast(number); return true; } @@ -2132,9 +2132,8 @@ class binary_reader } if (!value_in_range_of(number)) { - // undo coverage exclusion once the 32bit test is run as part of CI (#3524) - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE - exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); } result = detail::conditional_static_cast(number); return true; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4a73ce5d3..4a9c4bf83 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -9310,7 +9310,8 @@ class binary_reader case 0x95: case 0x96: case 0x97: - return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); + return get_cbor_array( + conditional_static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0x98: // array (one-byte uint8_t for n follows) { @@ -9327,13 +9328,13 @@ class binary_reader case 0x9A: // array (four-byte uint32_t for n follow) { std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast(len), tag_handler); } case 0x9B: // array (eight-byte uint64_t for n follow) { std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(detail::conditional_static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast(len), tag_handler); } case 0x9F: // array (indefinite length) @@ -9364,7 +9365,7 @@ class binary_reader case 0xB5: case 0xB6: case 0xB7: - return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); + return get_cbor_object(conditional_static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0xB8: // map (one-byte uint8_t for n follows) { @@ -9381,13 +9382,13 @@ class binary_reader case 0xBA: // map (four-byte uint32_t for n follow) { std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast(len), tag_handler); } case 0xBB: // map (eight-byte uint64_t for n follow) { std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(detail::conditional_static_cast(len), tag_handler); + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast(len), tag_handler); } case 0xBF: // map (indefinite length) @@ -10034,7 +10035,7 @@ class binary_reader case 0x8D: case 0x8E: case 0x8F: - return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); + return get_msgpack_object(conditional_static_cast(static_cast(current) & 0x0Fu)); // fixarray case 0x90: @@ -10053,7 +10054,7 @@ class binary_reader case 0x9D: case 0x9E: case 0x9F: - return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); + return get_msgpack_array(conditional_static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: @@ -10190,7 +10191,7 @@ class binary_reader case 0xDD: // array 32 { std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + return get_number(input_format_t::msgpack, len) && get_msgpack_array(conditional_static_cast(len)); } case 0xDE: // map 16 @@ -10202,7 +10203,7 @@ class binary_reader case 0xDF: // map 32 { std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + return get_number(input_format_t::msgpack, len) && get_msgpack_object(conditional_static_cast(len)); } // negative fixint @@ -10773,9 +10774,8 @@ class binary_reader } if (!value_in_range_of(number)) { - // undo coverage exclusion once the 32bit test is run as part of CI (#3524) - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE - exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); } result = static_cast(number); return true; @@ -10807,7 +10807,7 @@ class binary_reader { return false; } - result = static_cast(number); + result = conditional_static_cast(number); return true; } @@ -10824,9 +10824,8 @@ class binary_reader } if (!value_in_range_of(number)) { - // undo coverage exclusion once the 32bit test is run as part of CI (#3524) - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, // LCOV_EXCL_LINE - exception_message(input_format, "integer value overflow", "size"), nullptr)); // LCOV_EXCL_LINE + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); } result = detail::conditional_static_cast(number); return true; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ef87c4909..8f9240f1a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.13) option(JSON_Valgrind "Execute test suite with Valgrind." OFF) option(JSON_FastTests "Skip expensive/slow tests." OFF) -option(JSON_32bitTest "Enable the 32bit unit test." OFF) +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.") include(test) @@ -34,40 +34,32 @@ endif() # test_main library with shared code to speed up build and common settings ############################################################################# -set(test_main_SOURCES src/unit.cpp) -set(test_main_COMPILE_DEFINITIONS PUBLIC +add_library(test_main OBJECT src/unit.cpp) +target_compile_definitions(test_main PUBLIC DOCTEST_CONFIG_SUPER_FAST_ASSERTS JSON_TEST_KEEP_MACROS) -set(test_main_COMPILE_FEATURES PRIVATE cxx_std_11) -set(test_main_COMPILE_OPTIONS - PUBLIC - $<$:/EHsc;$<$:/Od>> - # MSVC: Force to always compile with W4 - # Disable warning C4566: character represented by universal-character-name '\uFF01' - # cannot be represented in the current code page (1252) - # Disable warning C4996: 'nlohmann::basic_json<...>::operator <<': was declared deprecated - $<$:/W4 /wd4566 /wd4996> - # https://github.com/nlohmann/json/issues/1114 - $<$:/bigobj> $<$:-Wa,-mbig-obj> +target_compile_features(test_main PRIVATE cxx_std_11) +target_compile_options(test_main PUBLIC + $<$:/EHsc;$<$:/Od>> + # MSVC: Force to always compile with W4 + # Disable warning C4566: character represented by universal-character-name '\uFF01' + # cannot be represented in the current code page (1252) + # Disable warning C4996: 'nlohmann::basic_json<...>::operator <<': was declared deprecated + $<$:/W4 /wd4566 /wd4996> + # https://github.com/nlohmann/json/issues/1114 + $<$:/bigobj> $<$:-Wa,-mbig-obj> - # https://github.com/nlohmann/json/pull/3229 - $<$:-diag-disable=2196> + # https://github.com/nlohmann/json/pull/3229 + $<$:-diag-disable=2196> - $<$>:-Wno-deprecated;-Wno-float-equal> - $<$:-Wno-deprecated-declarations> - $<$:-diag-disable=1786>) -set(test_main_INCLUDE_DIRECTORIES PUBLIC + $<$>:-Wno-deprecated;-Wno-float-equal> + $<$:-Wno-deprecated-declarations> + $<$:-diag-disable=1786>) +target_include_directories(test_main PUBLIC thirdparty/doctest thirdparty/fifo_map ${PROJECT_BINARY_DIR}/include) -set(test_main_LINK_LIBRARIES PUBLIC ${NLOHMANN_JSON_TARGET_NAME}) - -add_library(test_main OBJECT ${test_main_SOURCES}) -target_compile_definitions(test_main ${test_main_COMPILE_DEFINITIONS}) -target_compile_features(test_main ${test_main_COMPILE_FEATURES}) -target_compile_options(test_main ${test_main_COMPILE_OPTIONS}) -target_include_directories(test_main ${test_main_INCLUDE_DIRECTORIES}) -target_link_libraries(test_main ${test_main_LINK_LIBRARIES}) +target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME}) ############################################################################# # define test- and standard-specific build settings @@ -124,23 +116,21 @@ message(STATUS "${msg}") # *DO* use json_test_set_test_options() above this line +json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}") file(GLOB files src/unit-*.cpp) -list(FILTER files EXCLUDE REGEX "src/unit-32bit.cpp") +if(json_32bit_test_only) + set(files src/unit-32bit.cpp) +elseif(NOT json_32bit_test) + list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp) +endif() + foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() -if(JSON_32bitTest) - add_library(test_main32 OBJECT ${test_main_SOURCES}) - target_compile_definitions(test_main32 ${test_main_COMPILE_DEFINITIONS}) - target_compile_features(test_main32 ${test_main_COMPILE_FEATURES}) - target_compile_options(test_main32 ${test_main_COMPILE_OPTIONS} -m32) - target_include_directories(test_main32 ${test_main_INCLUDE_DIRECTORIES}) - target_link_libraries(test_main32 ${test_main_LINK_LIBRARIES}) - target_link_options(test_main32 PUBLIC -m32) - - json_test_add_test_for("src/unit-32bit.cpp" MAIN test_main32 - CXX_STANDARDS ${test_cxx_standards} ${test_force}) +if(json_32bit_test_only) + # Skip all other tests in this file + return() endif() # test legacy comparison of discarded values diff --git a/tests/src/unit-32bit.cpp b/tests/src/unit-32bit.cpp index 711dda5a0..25da44f4d 100644 --- a/tests/src/unit-32bit.cpp +++ b/tests/src/unit-32bit.cpp @@ -98,18 +98,14 @@ TEST_CASE("BJData") { SECTION("array") { - SECTION("optimized array: no size following type") - { - std::vector v = {'[', '$', 'i', 2}; - json _; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BJData size: expected '#' after type information; last byte: 0x02", json::parse_error&); - } - - SECTION("optimized array: negative size") + SECTION("optimized array: integer value overflow") { + std::vector vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F}; std::vector vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'}; json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); } } diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 974ee3a0a..ab046a602 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2649,14 +2649,27 @@ TEST_CASE("BJData") CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); CHECK(json::from_bjdata(vL, true, false).is_discarded()); -#if SIZE_MAX == 0xffffffff - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); -#else +#if SIZE_MAX != 0xffffffff CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&); #endif CHECK(json::from_bjdata(vM, true, false).is_discarded()); } + SECTION("optimized array: integer value overflow") + { + std::vector vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F}; + std::vector vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'}; + + json _; +#if SIZE_MAX == 0xffffffff + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); +#endif + +#if SIZE_MAX == 0xffffffff + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); +#endif + } + SECTION("do not accept NTFZ markers in ndarray optimized type (with count)") { json _; From f6acdbec2c99670cf932d83cd797f08662e0b564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Hoz=C3=A1k?= Date: Thu, 16 Jun 2022 19:34:32 +0200 Subject: [PATCH 21/23] Allow disabling default enum conversions (#3536) --- CMakeLists.txt | 6 + cmake/ci.cmake | 4 +- docs/docset/docSet.sql | 1 + docs/mkdocs/docs/api/macros/index.md | 1 + .../macros/json_disable_enum_serialization.md | 135 ++++++++++++++++++ .../macros/nlohmann_json_serialize_enum.md | 1 + docs/mkdocs/docs/features/enum_conversion.md | 1 + docs/mkdocs/docs/features/macros.md | 6 + docs/mkdocs/mkdocs.yml | 1 + .../nlohmann/detail/conversions/from_json.hpp | 2 + .../nlohmann/detail/conversions/to_json.hpp | 2 + include/nlohmann/detail/macro_scope.hpp | 4 + include/nlohmann/detail/macro_unscope.hpp | 1 + single_include/nlohmann/json.hpp | 9 ++ 14 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md diff --git a/CMakeLists.txt b/CMakeLists.txt index aae5fb6e3..f20d56137 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ option(JSON_BuildTests "Build the unit tests when BUILD_TEST option(JSON_CI "Enable CI build targets." OFF) option(JSON_Diagnostics "Use extended diagnostic messages." OFF) option(JSON_ImplicitConversions "Enable implicit conversions." ON) +option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF) option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF) option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON) @@ -78,6 +79,10 @@ if (NOT JSON_ImplicitConversions) message(STATUS "Implicit conversions are disabled") endif() +if (JSON_DisableEnumSerialization) + message(STATUS "Enum integer serialization is disabled") +endif() + if (JSON_LegacyDiscardedValueComparison) message(STATUS "Legacy discarded value comparison enabled") endif() @@ -106,6 +111,7 @@ target_compile_definitions( ${NLOHMANN_JSON_TARGET_NAME} INTERFACE $<$>:JSON_USE_IMPLICIT_CONVERSIONS=0> + $<$:JSON_DISABLE_ENUM_SERIALIZATION=1> $<$:JSON_DIAGNOSTICS=1> $<$:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> ) diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 8704d4a54..4fcff86aa 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -838,8 +838,8 @@ endfunction() ci_get_cmake(3.1.0 CMAKE_3_1_0_BINARY) ci_get_cmake(3.13.0 CMAKE_3_13_0_BINARY) -set(JSON_CMAKE_FLAGS_3_1_0 JSON_Diagnostics JSON_ImplicitConversions JSON_LegacyDiscardedValueComparison - JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind) +set(JSON_CMAKE_FLAGS_3_1_0 JSON_Diagnostics JSON_ImplicitConversions JSON_DisableEnumSerialization + JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind) set(JSON_CMAKE_FLAGS_3_13_0 JSON_BuildTests) function(ci_add_cmake_flags_targets flag min_version) diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 8bd4e0678..30c4a48de 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -140,6 +140,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('SAX Interface', 'Guide', 'fea INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'features/macros/index.html#json_assertx'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'features/macros/index.html#json_catch_userexception'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'features/macros/index.html#json_diagnostics'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'features/macros/index.html#json_disable_enum_serialization'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'features/macros/index.html#json_has_cpp_11-json_has_cpp_14-json_has_cpp_17-json_has_cpp_20'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'features/macros/index.html#json_has_cpp_11-json_has_cpp_14-json_has_cpp_17-json_has_cpp_20'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'features/macros/index.html#json_has_cpp_11-json_has_cpp_14-json_has_cpp_17-json_has_cpp_20'); diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index 5f0a7a194..d95869b93 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -29,6 +29,7 @@ header. See also the [macro overview page](../../features/macros.md). ## Type conversions +- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums - [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions diff --git a/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md b/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md new file mode 100644 index 000000000..ea291f9a0 --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md @@ -0,0 +1,135 @@ +# JSON_DISABLE_ENUM_SERIALIZATION + +```cpp +#define JSON_DISABLE_ENUM_SERIALIZATION +``` + +When defined, default serialization and deserialization functions for enums are excluded and have to be provided by the user, for example, using [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) (see [arbitrary type conversions](../../features/arbitrary_types.md) for more details). + +Parsing or serializing an enum will result in a compiler error. + +This works for both unscoped and scoped enums. + +## Default definition + +By default, `#!cpp JSON_DISABLE_ENUM_SERIALIZATION` is not defined. + +```cpp +#undef JSON_DISABLE_ENUM_SERIALIZATION +``` + +## Examples + +??? example "Example 1: Disabled behavior" + + The code below forces the library **not** to create default serialization/deserialization functions `from_json` and `to_json`, meaning the code below **does not** compile. + + ```cpp + #define JSON_DISABLE_ENUM_SERIALIZATION 1 + #include + + using json = nlohmann::json; + + enum class Choice + { + first, + second, + }; + + int main() + { + // normally invokes to_json serialization function but with JSON_DISABLE_ENUM_SERIALIZATION defined, it does not + const json j = Choice::first; + + // normally invokes from_json parse function but with JSON_DISABLE_ENUM_SERIALIZATION defined, it does not + Choice ch = j.get(); + } + ``` + +??? example "Example 2: Serialize enum macro" + + The code below forces the library **not** to create default serialization/deserialization functions `from_json` and `to_json`, but uses [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) to parse and serialize the enum. + + ```cpp + #define JSON_DISABLE_ENUM_SERIALIZATION 1 + #include + + using json = nlohmann::json; + + enum class Choice + { + first, + second, + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(Choice, + { + { Choice::first, "first" }, + { Choice::second, "second" }, + }) + + int main() + { + // uses user-defined to_json function defined by macro + const json j = Choice::first; + + // uses user-defined from_json function defined by macro + Choice ch = j.get(); + } + ``` + +??? example "Example 3: User-defined serialization/deserialization functions" + + The code below forces the library **not** to create default serialization/deserialization functions `from_json` and `to_json`, but uses user-defined functions to parse and serialize the enum. + + ```cpp + #define JSON_DISABLE_ENUM_SERIALIZATION 1 + #include + + using json = nlohmann::json; + + enum class Choice + { + first, + second, + }; + + void from_json(const json& j, Choice& ch) + { + auto value = j.get(); + if (value == "first") + { + ch = Choice::first; + } + else if (value == "second") + { + ch = Choice::second; + } + } + + void to_json(json& j, const Choice& ch) + { + auto value = j.get(); + if (value == "first") + { + ch = Choice::first; + } + else if (value == "second") + { + ch = Choice::second; + } + } + + int main() + { + // uses user-defined to_json function + const json j = Choice::first; + + // uses user-defined from_json function + Choice ch = j.get(); + } + ``` + +## See also + +- [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) \ No newline at end of file diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md index 7b0f89802..b7204a808 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md @@ -78,6 +78,7 @@ inline void from_json(const BasicJsonType& j, type& e); ## See also - [Specializing enum conversion](../../features/enum_conversion.md) +- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md) ## Version history diff --git a/docs/mkdocs/docs/features/enum_conversion.md b/docs/mkdocs/docs/features/enum_conversion.md index 7e61f67bb..59ffbd5f1 100644 --- a/docs/mkdocs/docs/features/enum_conversion.md +++ b/docs/mkdocs/docs/features/enum_conversion.md @@ -58,3 +58,4 @@ Other Important points: default pair carefully. - If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON. +- To disable the default serialization of enumerators as integers and force a compiler error instead, see [`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index e04a426a5..1be95d35d 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -54,6 +54,12 @@ Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`. See [full documentation of `JSON_NOEXCEPTION`](../api/macros/json_noexception.md). +## `JSON_DISABLE_ENUM_SERIALIZATION` + +When defined, default parse and serialize functions for enums are excluded and have to be provided by the user, for example, using [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md). + +See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). + ## `JSON_NO_IO` When defined, headers ``, ``, ``, ``, and `` are not included and parse functions diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 08ef336db..c583ed78f 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -241,6 +241,7 @@ nav: - 'JSON_ASSERT': api/macros/json_assert.md - 'JSON_CATCH_USER': api/macros/json_throw_user.md - 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md + - 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md - 'JSON_HAS_CPP_11': api/macros/json_has_cpp_11.md - 'JSON_HAS_CPP_14': api/macros/json_has_cpp_11.md - 'JSON_HAS_CPP_17': api/macros/json_has_cpp_11.md diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index f0a93f291..ed4e6de5f 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -139,6 +139,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_int get_arithmetic_value(j, val); } +#if !JSON_DISABLE_ENUM_SERIALIZATION template::value, int> = 0> inline void from_json(const BasicJsonType& j, EnumType& e) @@ -147,6 +148,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) get_arithmetic_value(j, val); e = static_cast(val); } +#endif // JSON_DISABLE_ENUM_SERIALIZATION // forward_list doesn't have an insert method template::construct(j, static_cast(val)); } +#if !JSON_DISABLE_ENUM_SERIALIZATION template::value, int> = 0> inline void to_json(BasicJsonType& j, EnumType e) noexcept @@ -320,6 +321,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } +#endif // JSON_DISABLE_ENUM_SERIALIZATION template inline void to_json(BasicJsonType& j, const std::vector& e) diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 18a527c19..706f1b971 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -456,3 +456,7 @@ #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 396fe1a33..09f677b3d 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -16,6 +16,7 @@ #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL #undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS +#undef JSON_DISABLE_ENUM_SERIALIZATION #ifndef JSON_TEST_KEEP_MACROS #undef JSON_CATCH diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4a9c4bf83..98c8dd664 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2684,6 +2684,10 @@ using is_detected_convertible = #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + #if JSON_HAS_THREE_WAY_COMPARISON #include // partial_ordering #endif @@ -4384,6 +4388,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_int get_arithmetic_value(j, val); } +#if !JSON_DISABLE_ENUM_SERIALIZATION template::value, int> = 0> inline void from_json(const BasicJsonType& j, EnumType& e) @@ -4392,6 +4397,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) get_arithmetic_value(j, val); e = static_cast(val); } +#endif // JSON_DISABLE_ENUM_SERIALIZATION // forward_list doesn't have an insert method template::construct(j, static_cast(val)); } +#if !JSON_DISABLE_ENUM_SERIALIZATION template::value, int> = 0> inline void to_json(BasicJsonType& j, EnumType e) noexcept @@ -5301,6 +5308,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } +#endif // JSON_DISABLE_ENUM_SERIALIZATION template inline void to_json(BasicJsonType& j, const std::vector& e) @@ -23580,6 +23588,7 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL #undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS +#undef JSON_DISABLE_ENUM_SERIALIZATION #ifndef JSON_TEST_KEEP_MACROS #undef JSON_CATCH From 13730235f2430e0071cdd5e61479eeb1e7eb0e4f Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Sat, 18 Jun 2022 13:12:22 -0400 Subject: [PATCH 22/23] BJData dimension length can not be string_t::npos, fix #3541 (#3543) * BJData dimension length can not be string_t::npos, fix #3541 * handle error messages on 32bit machine * add explanation to why size can not be string_t::npos * add test cases to 32bit unit test Co-authored-by: Florian Albrechtskirchinger --- include/nlohmann/detail/input/binary_reader.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- tests/src/unit-32bit.cpp | 15 +++++++++++++++ tests/src/unit-bjdata.cpp | 12 ++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index f5871ddf6..103f7a752 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2179,7 +2179,7 @@ class binary_reader for (auto i : dim) { result *= i; - if (result == 0) // because dim elements shall not have zeros, result = 0 means overflow happened + if (result == 0 || result == string_t::npos) // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be string_t::npos as it is used to initialize size in get_ubjson_size_type() { return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 98c8dd664..834c6fc87 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10879,7 +10879,7 @@ class binary_reader for (auto i : dim) { result *= i; - if (result == 0) // because dim elements shall not have zeros, result = 0 means overflow happened + if (result == 0 || result == string_t::npos) // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be string_t::npos as it is used to initialize size in get_ubjson_size_type() { return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); } diff --git a/tests/src/unit-32bit.cpp b/tests/src/unit-32bit.cpp index 25da44f4d..cbe07b49f 100644 --- a/tests/src/unit-32bit.cpp +++ b/tests/src/unit-32bit.cpp @@ -98,6 +98,19 @@ TEST_CASE("BJData") { SECTION("array") { + SECTION("optimized array: negative size") + { + std::vector vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'}; + std::vector vMX = {'[', '$', 'U', '#', '[', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'U', 0x01, ']'}; + + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vM, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vMX, true, false).is_discarded()); + } + SECTION("optimized array: integer value overflow") { std::vector vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F}; @@ -105,8 +118,10 @@ TEST_CASE("BJData") json _; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vL, true, false).is_discarded()); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vM, true, false).is_discarded()); } } } diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index ab046a602..7442ea588 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2620,6 +2620,7 @@ TEST_CASE("BJData") std::vector vl = {'[', '#', 'l', 0x00, 0x00, 0x00, 0xF2}; std::vector vL = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3}; std::vector vM = {'[', '$', 'M', '#', '[', 'I', 0x00, 0x20, 'M', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, ']'}; + std::vector vMX = {'[', '$', 'U', '#', '[', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'U', 0x01, ']'}; json _; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v1), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing BJData size: count in an optimized container must be positive", json::parse_error&); @@ -2651,8 +2652,17 @@ TEST_CASE("BJData") #if SIZE_MAX != 0xffffffff CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&); +#else + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); #endif CHECK(json::from_bjdata(vM, true, false).is_discarded()); + +#if SIZE_MAX != 0xffffffff + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&); +#else + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); +#endif + CHECK(json::from_bjdata(vMX, true, false).is_discarded()); } SECTION("optimized array: integer value overflow") @@ -2663,10 +2673,12 @@ TEST_CASE("BJData") json _; #if SIZE_MAX == 0xffffffff CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vL, true, false).is_discarded()); #endif #if SIZE_MAX == 0xffffffff CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vM, true, false).is_discarded()); #endif } From 87cda1d6646592ac5866dc703c8e1839046a6806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=B0=91=E9=94=9F?= Date: Sun, 19 Jun 2022 01:14:03 +0800 Subject: [PATCH 23/23] Use `std::iterator_traits` to extract `iterator_category` (#3544) * Use `std::iterator_traits` to extract `iterator_category` In third-party STL implementations, `array_t::iterator` might be a pointer (e.g., `vector` in [EASTL](https://github.com/electronicarts/EASTL)) rather than a class, in which case directly using `array_t::iterator::iterator_category` is invalid. This commit fixes it with `std::iterator_traits`, which handles pointers correctly. * add the changes to the single-header version --- include/nlohmann/detail/iterators/iter_impl.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index 81c61b3e0..028de4047 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -53,7 +53,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci "iter_impl only accepts (const) basic_json"); // superficial check for the LegacyBidirectionalIterator named requirement static_assert(std::is_base_of::value - && std::is_base_of::value, + && std::is_base_of::iterator_category>::value, "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); public: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 834c6fc87..50e5c805c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -12393,7 +12393,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci "iter_impl only accepts (const) basic_json"); // superficial check for the LegacyBidirectionalIterator named requirement static_assert(std::is_base_of::value - && std::is_base_of::value, + && std::is_base_of::iterator_category>::value, "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); public: