Two changes roughly halve the CPU time needed to build the unit tests (clang 21: ~300 s -> ~150 s, GCC 16: ~750-820 s -> ~395-420 s, Debug): - Move the C++14/17/20-dependent tests into separate unit-<name>-cpp<N>.cpp files. A test file is built for every standard whose JSON_HAS_CPP_<N> macro it mentions, so far whole large files were rebuilt for C++14/17/20 because of a few #ifdef sections. The main files are now built for C++11 only; the ci_test_*_cxx<N> jobs still build every file for every standard. - Add the CMake option JSON_TestUnityBuild (ON by default, OFF with MinGW): compatible test files are compiled in batches as one translation unit so they share the template instantiations of the library. Each file keeps its own CTest test, which runs the batch executable filtered to that file's test cases. The binary-format tests form an explicit group; the rest is batched by JSON_TestUnityBatchSize. Fix the name clashes that merging files exposed, document the rules for test files in tests/README.md, and point CONTRIBUTING.md to it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
664 lines
32 KiB
C++
664 lines
32 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | 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 builds a unit test with C++ standards beyond C++11 only if the
|
|
// source file mentions the corresponding version macro. To avoid rebuilding this
|
|
// large file for every standard, tests that depend on the standard version (e.g.,
|
|
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
|
// go into a separate file unit-conversions2-cpp<NN>.cpp. This file stays C++11-only.
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
// 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")
|
|
|
|
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&);
|
|
}
|
|
}
|
|
|
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|