| // __ _____ _____ _____ | |
| // __| | __| | | | JSON for Modern C++ (supporting code) | |
| // | | |__ | | | | | | version 3.12.0 | |
| // |_____|_____|_____|_|___| https://github.com/nlohmann/json | |
| // | |
| // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> | |
| // SPDX-License-Identifier: MIT | |
| // cmake/test.cmake selects the C++ standard versions with which to build a | |
| // unit test based on the presence of JSON_HAS_CPP_<VERSION> macros. | |
| // When using macros that are only defined for particular versions of the standard | |
| // (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding | |
| // version macro in a comment close by, like this: | |
| // JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file) | |
| #include "doctest_compatibility.h" | |
| // skip tests if JSON_DisableEnumSerialization=ON (#4384) | |
| #if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1) | |
| #define SKIP_TESTS_FOR_ENUM_SERIALIZATION | |
| #endif | |
| #define JSON_TESTS_PRIVATE | |
| #include <nlohmann/json.hpp> | |
| using nlohmann::json; | |
| #include <deque> | |
| #include <forward_list> | |
| #include <list> | |
| #include <set> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <valarray> | |
| // NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair | |
| DOCTEST_CLANG_SUPPRESS_WARNING_PUSH | |
| DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") | |
| #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 | |
| #define JSON_HAS_CPP_17 | |
| #define JSON_HAS_CPP_14 | |
| #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) | |
| #define JSON_HAS_CPP_14 | |
| #endif | |
| #ifdef JSON_HAS_CPP_17 | |
| #if __has_include(<optional>) | |
| #include <optional> | |
| #elif __has_include(<experimental/optional>) | |
| #include <experimental/optional> | |
| #endif | |
| #endif | |
| #if defined(JSON_HAS_CPP_17) | |
| #include <string_view> | |
| #endif | |
| TEST_CASE("value conversion") | |
| { | |
| SECTION("get a binary value (explicit)") | |
| { | |
| json::binary_t const n_reference{{1, 2, 3}}; | |
| json j(n_reference); | |
| SECTION("binary_t") | |
| { | |
| json::binary_t const b = j.get<json::binary_t>(); | |
| CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); | |
| } | |
| SECTION("get_binary()") | |
| { | |
| SECTION("non-const") | |
| { | |
| auto& b = j.get_binary(); | |
| CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); | |
| } | |
| SECTION("non-const") | |
| { | |
| const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization) | |
| const auto& b = j_const.get_binary(); | |
| CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); | |
| } | |
| } | |
| SECTION("exception in case of a non-string type") | |
| { | |
| json j_null(json::value_t::null); | |
| json j_object(json::value_t::object); | |
| json j_array(json::value_t::array); | |
| json j_string(json::value_t::string); | |
| json j_boolean(json::value_t::boolean); | |
| const json j_null_const(json::value_t::null); | |
| const json j_object_const(json::value_t::object); | |
| const json j_array_const(json::value_t::array); | |
| const json j_string_const(json::value_t::string); | |
| const json j_boolean_const(json::value_t::boolean); | |
| CHECK_THROWS_WITH_AS(j_null.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is null", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is object", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_array.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is array", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_string.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is string", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_boolean.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is boolean", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_null_const.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is null", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object_const.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is object", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_array_const.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is array", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_string_const.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is string", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_boolean_const.get<json::binary_t>(), | |
| "[json.exception.type_error.302] type must be binary, but is boolean", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_null.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is null", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is object", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_array.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is array", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_string.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is string", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_boolean.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is boolean", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_null_const.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is null", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_object_const.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is object", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_array_const.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is array", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_string_const.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is string", | |
| json::type_error&); | |
| CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(), | |
| "[json.exception.type_error.302] type must be binary, but is boolean", | |
| json::type_error&); | |
| } | |
| } | |
| #if JSON_USE_IMPLICIT_CONVERSIONS | |
| SECTION("get a binary value (implicit)") | |
| { | |
| json::binary_t const n_reference{{1, 2, 3}}; | |
| json const j(n_reference); | |
| SECTION("binary_t") | |
| { | |
| json::binary_t const b = j; | |
| CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); | |
| } | |
| } | |
| #endif | |
| #ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION | |
| SECTION("get an enum") | |
| { | |
| enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class) | |
| enum class cpp_enum { value_1, value_2 }; | |
| CHECK(json(value_1).get<c_enum>() == value_1); | |
| CHECK(json(cpp_enum::value_1).get<cpp_enum>() == cpp_enum::value_1); | |
| } | |
| SECTION("get an enum with underlying type bool (#5671)") | |
| { | |
| enum class bool_enum : bool { off, on }; | |
| // the extra parentheses keep doctest from printing the enum via its | |
| // underlying type, which MSVC 2015 reports as C4800 | |
| CHECK((json(bool_enum::off).get<bool_enum>() == bool_enum::off)); | |
| CHECK((json(bool_enum::on).get<bool_enum>() == bool_enum::on)); | |
| } | |
| #endif | |
| SECTION("more involved conversions") | |
| { | |
| SECTION("object-like STL containers") | |
| { | |
| json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}}; | |
| json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}}; | |
| json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; | |
| json const j4 = {{"one", true}, {"two", false}, {"three", true}}; | |
| json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; | |
| SECTION("std::map") | |
| { | |
| CHECK(j1.get<std::map<std::string, int>>() == (std::map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}})); | |
| CHECK(j2.get<std::map<std::string, unsigned int>>() == (std::map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}})); | |
| CHECK(j3.get<std::map<std::string, double>>() == (std::map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); | |
| CHECK(j4.get<std::map<std::string, bool>>() == (std::map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}})); | |
| CHECK(j5.get<std::map<std::string, std::string>>() == (std::map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); | |
| } | |
| SECTION("std::unordered_map") | |
| { | |
| CHECK(j1.get<std::unordered_map<std::string, int>>() == (std::unordered_map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}})); | |
| CHECK(j2.get<std::unordered_map<std::string, unsigned int>>() == (std::unordered_map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}})); | |
| CHECK(j3.get<std::unordered_map<std::string, double>>() == (std::unordered_map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); | |
| CHECK(j4.get<std::unordered_map<std::string, bool>>() == (std::unordered_map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}})); | |
| const auto m5 = j5.get<std::unordered_map<std::string, std::string>>(); | |
| CHECK(m5 == (std::unordered_map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); | |
| CHECK(m5.at("one") == "eins"); | |
| } | |
| SECTION("reserve is called on containers that support it (#5406)") | |
| { | |
| // build a larger object so that a missing/incorrect reserve() | |
| // call would be more likely to corrupt or drop elements | |
| json j_large; | |
| for (int i = 0; i < 100; ++i) | |
| { | |
| j_large[std::to_string(i)] = i; | |
| } | |
| SECTION("std::unordered_map (supports reserve)") | |
| { | |
| const auto m = j_large.get<std::unordered_map<std::string, int>>(); | |
| CHECK(m.size() == 100); | |
| for (int i = 0; i < 100; ++i) | |
| { | |
| CHECK(m.at(std::to_string(i)) == i); | |
| } | |
| } | |
| SECTION("std::map (no reserve, fallback path)") | |
| { | |
| const auto m = j_large.get<std::map<std::string, int>>(); | |
| CHECK(m.size() == 100); | |
| for (int i = 0; i < 100; ++i) | |
| { | |
| CHECK(m.at(std::to_string(i)) == i); | |
| } | |
| } | |
| } | |
| SECTION("std::multimap") | |
| { | |
| CHECK(j1.get<std::multimap<std::string, int>>() == (std::multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}})); | |
| CHECK(j2.get<std::multimap<std::string, unsigned int>>() == (std::multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}})); | |
| CHECK(j3.get<std::multimap<std::string, double>>() == (std::multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); | |
| CHECK(j4.get<std::multimap<std::string, bool>>() == (std::multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}})); | |
| const auto m5 = j5.get<std::multimap<std::string, std::string>>(); | |
| CHECK(m5 == (std::multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); | |
| CHECK(m5.find("one")->second == "eins"); | |
| } | |
| SECTION("std::unordered_multimap") | |
| { | |
| CHECK(j1.get<std::unordered_multimap<std::string, int>>() == (std::unordered_multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}})); | |
| CHECK(j2.get<std::unordered_multimap<std::string, unsigned int>>() == (std::unordered_multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}})); | |
| CHECK(j3.get<std::unordered_multimap<std::string, double>>() == (std::unordered_multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); | |
| CHECK(j4.get<std::unordered_multimap<std::string, bool>>() == (std::unordered_multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}})); | |
| const auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>(); | |
| CHECK(m5 == (std::unordered_multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); | |
| CHECK(m5.find("one")->second == "eins"); | |
| } | |
| SECTION("exception in case of a non-object type") | |
| { | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::map<std::string, int>>()), | |
| "[json.exception.type_error.302] type must be object, but is null", json::type_error&); | |
| } | |
| } | |
| SECTION("array-like STL containers") | |
| { | |
| json const j1 = {1, 2, 3, 4}; | |
| json const j2 = {1u, 2u, 3u, 4u}; | |
| json const j3 = {1.2, 2.3, 3.4, 4.5}; | |
| json const j4 = {true, false, true}; | |
| json const j5 = {"one", "two", "three"}; | |
| SECTION("std::list") | |
| { | |
| CHECK(j1.get<std::list<int>>() == (std::list<int> {1, 2, 3, 4})); | |
| CHECK(j2.get<std::list<unsigned int>>() == (std::list<unsigned int> {1u, 2u, 3u, 4u})); | |
| CHECK(j3.get<std::list<double>>() == (std::list<double> {1.2, 2.3, 3.4, 4.5})); | |
| CHECK(j4.get<std::list<bool>>() == (std::list<bool> {true, false, true})); | |
| CHECK(j5.get<std::list<std::string>>() == (std::list<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::forward_list") | |
| { | |
| CHECK(j1.get<std::forward_list<int>>() == (std::forward_list<int> {1, 2, 3, 4})); | |
| CHECK(j2.get<std::forward_list<unsigned int>>() == (std::forward_list<unsigned int> {1u, 2u, 3u, 4u})); | |
| CHECK(j3.get<std::forward_list<double>>() == (std::forward_list<double> {1.2, 2.3, 3.4, 4.5})); | |
| CHECK(j4.get<std::forward_list<bool>>() == (std::forward_list<bool> {true, false, true})); | |
| CHECK(j5.get<std::forward_list<std::string>>() == (std::forward_list<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::array") | |
| { | |
| CHECK(j1.get<std::array<int, 4>>() == (std::array<int, 4> {{1, 2, 3, 4}})); | |
| // only the first 3 elements of j2 are converted, since the target array is smaller | |
| CHECK(j2.get<std::array<unsigned int, 3>>() == (std::array<unsigned int, 3> {{1u, 2u, 3u}})); | |
| CHECK(j3.get<std::array<double, 4>>() == (std::array<double, 4> {{1.2, 2.3, 3.4, 4.5}})); | |
| CHECK(j4.get<std::array<bool, 3>>() == (std::array<bool, 3> {{true, false, true}})); | |
| CHECK(j5.get<std::array<std::string, 3>>() == (std::array<std::string, 3> {{"one", "two", "three"}})); | |
| SECTION("std::array is larger than JSON") | |
| { | |
| std::array<int, 6> arr6 = {{1, 2, 3, 4, 5, 6}}; | |
| CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] " | |
| "array index 4 is out of range", json::out_of_range&); | |
| } | |
| SECTION("std::array is smaller than JSON") | |
| { | |
| std::array<int, 2> arr2 = {{8, 9}}; | |
| j1.get_to(arr2); | |
| CHECK(arr2[0] == 1); | |
| CHECK(arr2[1] == 2); | |
| } | |
| } | |
| SECTION("std::valarray") | |
| { | |
| // valarray has no operator== that returns bool, so compare via a vector copy | |
| const auto v1 = j1.get<std::valarray<int>>(); | |
| CHECK((std::vector<int>(std::begin(v1), std::end(v1)) == std::vector<int> {1, 2, 3, 4})); | |
| const auto v2 = j2.get<std::valarray<unsigned int>>(); | |
| CHECK((std::vector<unsigned int>(std::begin(v2), std::end(v2)) == std::vector<unsigned int> {1u, 2u, 3u, 4u})); | |
| const auto v3 = j3.get<std::valarray<double>>(); | |
| CHECK((std::vector<double>(std::begin(v3), std::end(v3)) == std::vector<double> {1.2, 2.3, 3.4, 4.5})); | |
| const auto v4 = j4.get<std::valarray<bool>>(); | |
| CHECK((std::vector<bool>(std::begin(v4), std::end(v4)) == std::vector<bool> {true, false, true})); | |
| const auto v5 = j5.get<std::valarray<std::string>>(); | |
| CHECK((std::vector<std::string>(std::begin(v5), std::end(v5)) == std::vector<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::vector") | |
| { | |
| CHECK(j1.get<std::vector<int>>() == (std::vector<int> {1, 2, 3, 4})); | |
| CHECK(j2.get<std::vector<unsigned int>>() == (std::vector<unsigned int> {1u, 2u, 3u, 4u})); | |
| CHECK(j3.get<std::vector<double>>() == (std::vector<double> {1.2, 2.3, 3.4, 4.5})); | |
| CHECK(j4.get<std::vector<bool>>() == (std::vector<bool> {true, false, true})); | |
| CHECK(j5.get<std::vector<std::string>>() == (std::vector<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::deque") | |
| { | |
| CHECK(j1.get<std::deque<int>>() == (std::deque<int> {1, 2, 3, 4})); | |
| CHECK(j2.get<std::deque<unsigned int>>() == (std::deque<unsigned int> {1u, 2u, 3u, 4u})); | |
| CHECK(j3.get<std::deque<double>>() == (std::deque<double> {1.2, 2.3, 3.4, 4.5})); | |
| CHECK(j4.get<std::deque<bool>>() == (std::deque<bool> {true, false, true})); | |
| CHECK(j5.get<std::deque<std::string>>() == (std::deque<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::set") | |
| { | |
| CHECK(j1.get<std::set<int>>() == (std::set<int> {1, 2, 3, 4})); | |
| CHECK(j2.get<std::set<unsigned int>>() == (std::set<unsigned int> {1u, 2u, 3u, 4u})); | |
| CHECK(j3.get<std::set<double>>() == (std::set<double> {1.2, 2.3, 3.4, 4.5})); | |
| CHECK(j4.get<std::set<bool>>() == (std::set<bool> {true, false, true})); | |
| CHECK(j5.get<std::set<std::string>>() == (std::set<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::unordered_set") | |
| { | |
| CHECK(j1.get<std::unordered_set<int>>() == (std::unordered_set<int> {1, 2, 3, 4})); | |
| CHECK(j2.get<std::unordered_set<unsigned int>>() == (std::unordered_set<unsigned int> {1u, 2u, 3u, 4u})); | |
| CHECK(j3.get<std::unordered_set<double>>() == (std::unordered_set<double> {1.2, 2.3, 3.4, 4.5})); | |
| CHECK(j4.get<std::unordered_set<bool>>() == (std::unordered_set<bool> {true, false, true})); | |
| CHECK(j5.get<std::unordered_set<std::string>>() == (std::unordered_set<std::string> {"one", "two", "three"})); | |
| } | |
| SECTION("std::map (array of pairs)") | |
| { | |
| const std::map<int, int> m{{0, 1}, {1, 2}, {2, 3}}; | |
| json const j6 = m; | |
| auto m2 = j6.get<std::map<int, int>>(); | |
| CHECK(m == m2); | |
| json const j7 = {0, 1, 2, 3}; | |
| json const j8 = 2; | |
| #if JSON_DIAGNOSTICS | |
| CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()), | |
| "[json.exception.type_error.302] (/0) type must be array, " | |
| "but is number", json::type_error&); | |
| #else | |
| CHECK_THROWS_WITH_AS((j7.get<std::map<int, int>>()), | |
| "[json.exception.type_error.302] type must be array, " | |
| "but is number", json::type_error&); | |
| #endif | |
| CHECK_THROWS_WITH_AS((j8.get<std::map<int, int>>()), | |
| "[json.exception.type_error.302] type must be array, " | |
| "but is number", json::type_error&); | |
| SECTION("superfluous entries") | |
| { | |
| json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; | |
| m2 = j9.get<std::map<int, int>>(); | |
| CHECK(m == m2); | |
| } | |
| } | |
| SECTION("std::unordered_map (array of pairs)") | |
| { | |
| const std::unordered_map<int, int> m{{0, 1}, {1, 2}, {2, 3}}; | |
| json const j6 = m; | |
| auto m2 = j6.get<std::unordered_map<int, int>>(); | |
| CHECK(m == m2); | |
| json const j7 = {0, 1, 2, 3}; | |
| json const j8 = 2; | |
| #if JSON_DIAGNOSTICS | |
| CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()), | |
| "[json.exception.type_error.302] (/0) type must be array, " | |
| "but is number", json::type_error&); | |
| #else | |
| CHECK_THROWS_WITH_AS((j7.get<std::unordered_map<int, int>>()), | |
| "[json.exception.type_error.302] type must be array, " | |
| "but is number", json::type_error&); | |
| #endif | |
| CHECK_THROWS_WITH_AS((j8.get<std::unordered_map<int, int>>()), | |
| "[json.exception.type_error.302] type must be array, " | |
| "but is number", json::type_error&); | |
| SECTION("superfluous entries") | |
| { | |
| json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; | |
| m2 = j9.get<std::unordered_map<int, int>>(); | |
| CHECK(m == m2); | |
| } | |
| } | |
| SECTION("exception in case of a non-object type") | |
| { | |
| // does type really must be an array? or it rather must not be null? | |
| // that's what I thought when other test like this one broke | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::list<int>>()), | |
| "[json.exception.type_error.302] type must be array, but is null", json::type_error&); | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::vector<int>>()), | |
| "[json.exception.type_error.302] type must be array, but is null", json::type_error&); | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::vector<json>>()), | |
| "[json.exception.type_error.302] type must be array, but is null", json::type_error&); | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::list<json>>()), | |
| "[json.exception.type_error.302] type must be array, but is null", json::type_error&); | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::valarray<int>>()), | |
| "[json.exception.type_error.302] type must be array, but is null", json::type_error&); | |
| CHECK_THROWS_WITH_AS( | |
| (json().get<std::map<int, int>>()), | |
| "[json.exception.type_error.302] type must be array, but is null", json::type_error&); | |
| } | |
| } | |
| } | |
| } | |
| enum class cards {kreuz, pik, herz, karo}; | |
| // NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive | |
| NLOHMANN_JSON_SERIALIZE_ENUM(cards, | |
| { | |
| {cards::kreuz, "kreuz"}, | |
| {cards::pik, "pik"}, | |
| {cards::pik, "puk"}, // second entry for cards::puk; will not be used | |
| {cards::herz, "herz"}, | |
| {cards::karo, "karo"} | |
| }) | |
| enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) | |
| { | |
| TS_STOPPED, | |
| TS_RUNNING, | |
| TS_COMPLETED, | |
| TS_INVALID = -1, | |
| }; | |
| // NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive | |
| NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, | |
| { | |
| {TS_INVALID, nullptr}, | |
| {TS_STOPPED, "stopped"}, | |
| {TS_RUNNING, "running"}, | |
| {TS_COMPLETED, "completed"}, | |
| }) | |
| TEST_CASE("JSON to enum mapping") | |
| { | |
| SECTION("enum class") | |
| { | |
| // enum -> json | |
| CHECK(json(cards::kreuz) == "kreuz"); | |
| CHECK(json(cards::pik) == "pik"); | |
| CHECK(json(cards::herz) == "herz"); | |
| CHECK(json(cards::karo) == "karo"); | |
| // json -> enum | |
| CHECK(cards::kreuz == json("kreuz")); | |
| CHECK(cards::pik == json("pik")); | |
| CHECK(cards::herz == json("herz")); | |
| CHECK(cards::karo == json("karo")); | |
| // invalid json -> first enum | |
| CHECK(cards::kreuz == json("what?").get<cards>()); | |
| } | |
| SECTION("traditional enum") | |
| { | |
| // enum -> json | |
| CHECK(json(TS_STOPPED) == "stopped"); | |
| CHECK(json(TS_RUNNING) == "running"); | |
| CHECK(json(TS_COMPLETED) == "completed"); | |
| CHECK(json(TS_INVALID) == json()); | |
| // json -> enum | |
| CHECK(TS_STOPPED == json("stopped")); | |
| CHECK(TS_RUNNING == json("running")); | |
| CHECK(TS_COMPLETED == json("completed")); | |
| CHECK(TS_INVALID == json()); | |
| // invalid json -> first enum | |
| CHECK(TS_INVALID == json("what?").get<TaskState>()); | |
| } | |
| } | |
| enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping | |
| // NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive | |
| NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards, | |
| { | |
| {strict_cards::kreuz, "kreuz"}, | |
| {strict_cards::pik, "pik"}, | |
| {strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used | |
| {strict_cards::herz, "herz"}, | |
| {strict_cards::karo, "karo"} | |
| }) | |
| enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) | |
| { | |
| STRICT_TS_STOPPED, | |
| STRICT_TS_RUNNING, | |
| STRICT_TS_COMPLETED, | |
| STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping | |
| STRICT_TS_INVALID = -1, | |
| }; | |
| // NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive | |
| NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, | |
| { | |
| {STRICT_TS_INVALID, nullptr}, | |
| {STRICT_TS_STOPPED, "stopped"}, | |
| {STRICT_TS_RUNNING, "running"}, | |
| {STRICT_TS_COMPLETED, "completed"}, | |
| }) | |
| // regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on | |
| // unqualified lookup of a helper name that a user's own namespace may also declare | |
| namespace ns_with_colliding_name | |
| { | |
| // NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name | |
| inline void templated_json_throw(int /*unused*/) {} | |
| enum class colliding_enum { a, b }; | |
| // NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive | |
| NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum, | |
| { | |
| {colliding_enum::a, "a"}, | |
| {colliding_enum::b, "b"} | |
| }) | |
| } // namespace ns_with_colliding_name | |
| TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") | |
| { | |
| using ns_with_colliding_name::colliding_enum; | |
| CHECK(json(colliding_enum::a) == "a"); | |
| CHECK(colliding_enum::b == json("b")); | |
| json _; | |
| CHECK_THROWS_WITH_AS(_ = json("nope").get<colliding_enum>(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); | |
| } | |
| TEST_CASE("Strict JSON to enum mapping") | |
| { | |
| SECTION("enum class") | |
| { | |
| // enum -> json | |
| CHECK(json(strict_cards::kreuz) == "kreuz"); | |
| CHECK(json(strict_cards::pik) == "pik"); | |
| CHECK(json(strict_cards::herz) == "herz"); | |
| CHECK(json(strict_cards::karo) == "karo"); | |
| // json -> enum | |
| CHECK(json("kreuz").get<strict_cards>() == strict_cards::kreuz); | |
| CHECK(json("pik").get<strict_cards>() == strict_cards::pik); | |
| CHECK(json("herz").get<strict_cards>() == strict_cards::herz); | |
| CHECK(json("karo").get<strict_cards>() == strict_cards::karo); | |
| // comparison of enum and json | |
| CHECK(strict_cards::kreuz == json("kreuz")); | |
| CHECK(strict_cards::pik == json("pik")); | |
| CHECK(strict_cards::herz == json("herz")); | |
| CHECK(strict_cards::karo == json("karo")); | |
| // invalid json -> exception thrown | |
| json _; | |
| CHECK_THROWS_WITH_AS(_ = json("what?").get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); | |
| // conversion of unmapped enum -> exception thrown | |
| CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); | |
| // comparing an unmapped enum with json throws the same exception | |
| // (the scalar comparison operators used to be noexcept, so this | |
| // called std::terminate) | |
| CHECK_THROWS_WITH_AS(static_cast<void>(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); | |
| CHECK_THROWS_WITH_AS(static_cast<void>(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); | |
| // invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the | |
| // message (regression test for #5667); such strings can reach get<Enum>() unvalidated, | |
| // e.g. from from_cbor()/from_msgpack() (#5529) | |
| const json j_invalid_utf8 = "\xFF"; | |
| CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get<strict_cards>(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&); | |
| } | |
| SECTION("traditional enum") | |
| { | |
| // enum -> json | |
| CHECK(json(STRICT_TS_STOPPED) == "stopped"); | |
| CHECK(json(STRICT_TS_RUNNING) == "running"); | |
| CHECK(json(STRICT_TS_COMPLETED) == "completed"); | |
| CHECK(json(STRICT_TS_INVALID) == json()); | |
| // json -> enum | |
| CHECK(json("stopped").get<StrictTaskState>() == STRICT_TS_STOPPED); | |
| CHECK(json("running").get<StrictTaskState>() == STRICT_TS_RUNNING); | |
| CHECK(json("completed").get<StrictTaskState>() == STRICT_TS_COMPLETED); | |
| CHECK(json().get<StrictTaskState>() == STRICT_TS_INVALID); | |
| // comparison of enum and json | |
| CHECK(STRICT_TS_STOPPED == json("stopped")); | |
| CHECK(STRICT_TS_RUNNING == json("running")); | |
| CHECK(STRICT_TS_COMPLETED == json("completed")); | |
| CHECK(STRICT_TS_INVALID == json()); | |
| // invalid json -> exception thrown | |
| json _; | |
| CHECK_THROWS_WITH_AS(_ = json("what?").get<StrictTaskState>(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&); | |
| // conversion of unmapped enum -> exception thrown | |
| CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); | |
| // comparing an unmapped enum with json throws the same exception | |
| CHECK_THROWS_WITH_AS(static_cast<void>(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); | |
| } | |
| } | |
| #ifdef JSON_HAS_CPP_17 | |
| #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM | |
| TEST_CASE("std::filesystem::path") | |
| { | |
| SECTION("ascii") | |
| { | |
| json const j_string = "Path"; | |
| auto p = j_string.template get<nlohmann::detail::std_fs::path>(); | |
| json const j_path = p; | |
| CHECK(j_path.template get<std::string>() == | |
| j_string.template get<std::string>()); | |
| } | |
| SECTION("utf-8") | |
| { | |
| json const j_string = "P\xc4\x9b\xc5\xa1ina"; | |
| auto p = j_string.template get<nlohmann::detail::std_fs::path>(); | |
| json const j_path = p; | |
| CHECK(j_path.template get<std::string>() == | |
| j_string.template get<std::string>()); | |
| } | |
| } | |
| #endif | |
| // the ADL to_json overload for std::u8string only exists under the same guard | |
| // as std::filesystem::path support (it is otherwise only reached indirectly, | |
| // via std::filesystem::path::u8string()) -- mirror both #if conditions from | |
| // include/nlohmann/detail/conversions/to_json.hpp exactly | |
| #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM | |
| #if defined(__cpp_lib_char8_t) | |
| TEST_CASE("std::u8string") | |
| { | |
| SECTION("ascii") | |
| { | |
| const std::u8string s = u8"Path"; | |
| json const j = s; | |
| CHECK(j.template get<std::string>() == "Path"); | |
| } | |
| SECTION("utf-8") | |
| { | |
| // use \u universal-character-names (rather than raw \x byte escapes | |
| // or literal non-ASCII source bytes) to compose the multi-byte UTF-8 | |
| // encoding -- MSVC treats \x escapes used that way inside a u8 | |
| // literal as a nonstandard extension (warning C5321), which some of | |
| // our CI configs promote to an error; \u is portable and produces | |
| // the exact same encoded bytes without depending on the source | |
| // file's encoding | |
| const std::u8string s = u8"P\u011B\u0161ina"; | |
| json const j = s; | |
| CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina"); | |
| } | |
| } | |
| #endif | |
| #endif | |
| #if !defined(JSON_NOEXCEPTION) | |
| namespace | |
| { | |
| // a type whose to_json reports an error by throwing, used below to check that | |
| // converting a std::optional<T> to JSON propagates an exception thrown while | |
| // converting its contained value instead of calling std::terminate (#5642) | |
| struct throwing_to_json_type {}; | |
| [[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/) | |
| { | |
| throw std::runtime_error("cannot serialize throwing_to_json_type"); | |
| } | |
| } // namespace | |
| #endif | |
| TEST_CASE("std::optional") | |
| { | |
| SECTION("null") | |
| { | |
| const json j_null; | |
| const std::optional<std::string> opt_null; | |
| CHECK(json(opt_null) == j_null); | |
| CHECK(j_null.get<std::optional<std::string>>() == std::nullopt); | |
| // Constructing std::optional<T> directly from JSON null throws because | |
| // std::optional's own converting constructor is chosen over basic_json's | |
| // operator T(). This is a language-level limitation (std::optional<T> is | |
| // constructible from T, and T is constructible from basic_json via the | |
| // operator); there is no SFINAE path that distinguishes "call from inside | |
| // std::optional's constructor" from "direct call". Use get<std::optional<T>>() | |
| // or get_to() instead for correct null handling. See #4864 and #5246. | |
| CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null), | |
| "[json.exception.type_error.302] type must be string, but is null", json::type_error&); | |
| CHECK_THROWS_WITH_AS(std::optional<int>(j_null), | |
| "[json.exception.type_error.302] type must be number, but is null", json::type_error&); | |
| // Assignment goes through the same overload resolution as direct | |
| // construction, so it throws for the same reason. This relies on | |
| // basic_json's implicit conversion operator, so it only applies | |
| // when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default). | |
| #if JSON_USE_IMPLICIT_CONVERSIONS | |
| std::optional<std::string> opt_assign; | |
| CHECK_THROWS_WITH_AS(opt_assign = j_null, | |
| "[json.exception.type_error.302] type must be string, but is null", json::type_error&); | |
| #endif | |
| // get_to() is the correct way to obtain std::nullopt from a JSON null. | |
| std::optional<std::string> opt_get_to = "placeholder"; | |
| j_null.get_to(opt_get_to); | |
| CHECK(opt_get_to == std::nullopt); | |
| } | |
| SECTION("string") | |
| { | |
| json j_string = "string"; | |
| std::optional<std::string> opt_string = "string"; | |
| CHECK(json(opt_string) == j_string); | |
| CHECK(std::optional<std::string>(j_string) == opt_string); | |
| // false positive: Infer attributes the destruction of the temporaries above to opt_string | |
| // @infer-ignore USE_AFTER_DELETE | |
| } | |
| SECTION("bool") | |
| { | |
| json j_bool = true; | |
| std::optional<bool> opt_bool = true; | |
| CHECK(json(opt_bool) == j_bool); | |
| CHECK(std::optional<bool>(j_bool) == opt_bool); | |
| } | |
| SECTION("number") | |
| { | |
| json j_number = 1; | |
| std::optional<int> opt_int = 1; | |
| CHECK(json(opt_int) == j_number); | |
| CHECK(j_number.get<std::optional<int>>() == opt_int); | |
| } | |
| SECTION("array") | |
| { | |
| json j_array = {1, 2, nullptr}; | |
| std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}}; | |
| CHECK(json(opt_array) == j_array); | |
| CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array); | |
| } | |
| SECTION("object") | |
| { | |
| json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}}; | |
| std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; | |
| CHECK(json(opt_object) == j_object); | |
| CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object); | |
| } | |
| #if !defined(JSON_NOEXCEPTION) | |
| SECTION("exception from contained value's to_json propagates (#5642)") | |
| { | |
| // to_json(BasicJsonType&, const std::optional<T>&) must not be | |
| // noexcept: it calls T's to_json, which may throw (a user-defined | |
| // to_json that reports an error, or std::bad_alloc for T = | |
| // std::string/vector/json). Before the fix, this called | |
| // std::terminate() instead of letting the exception propagate. | |
| const std::optional<throwing_to_json_type> opt = throwing_to_json_type{}; | |
| CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); | |
| // the conversion is noexcept exactly when converting the contained value is | |
| // (except with MSVC 2017, where it is never noexcept, see to_json.hpp) | |
| #if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920 | |
| static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value); | |
| static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value); | |
| #endif | |
| } | |
| #endif | |
| } | |
| #endif | |
| #ifdef JSON_HAS_CPP_17 | |
| #undef JSON_HAS_CPP_17 | |
| #endif | |
| #ifdef JSON_HAS_CPP_14 | |
| #undef JSON_HAS_CPP_14 | |
| #endif | |
| DOCTEST_CLANG_SUPPRESS_WARNING_POP |