mirror of
https://github.com/nlohmann/json.git
synced 2026-10-04 21:50:33 +00:00
Split unit-conversions.cpp so MinGW can link it
clang 18 with the MinGW linker failed to link test-conversions_cpp17
("relocation truncated to fit: IMAGE_REL_AMD64_REL32"). As windows.yml
recommends, keep the objects small by splitting the test file.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -0,0 +1,868 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 };
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
#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
|
||||
Reference in New Issue
Block a user