// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #include "doctest_compatibility.h" #include #include #include #include #include #include // basic_json with the same type for number_integer_t and number_unsigned_t // (#2573): every integer is stored as number_integer namespace { template using same_types_json = nlohmann::json::with_integers_t; using json64 = same_types_json; using json32 = same_types_json; } // namespace TEST_CASE_TEMPLATE("same type for number_integer_t and number_unsigned_t", Json, json64, json32) { using integer_t = typename Json::number_integer_t; using limits = std::numeric_limits; SECTION("parsing stores integers as number_integer") { const Json j = Json::parse("[0, 1, -1, 2147483647, -2147483648]"); for (const auto& element : j) { CHECK(element.is_number_integer()); CHECK(!element.is_number_unsigned()); CHECK(element.type() == nlohmann::detail::value_t::number_integer); } CHECK(j[1].template get() == 1); CHECK(j[2].template get() == -1); CHECK(j[3].template get() == 2147483647); CHECK(j[4].template get() == -2147483648LL); } SECTION("integers above the maximum are parsed as floats") { // a positive literal is parsed as unsigned first; it must not wrap // around to a negative number_integer_t const auto above_max = std::to_string(static_cast(limits::max()) + 1); const Json j1 = Json::parse(above_max); CHECK(j1.is_number_float()); CHECK(j1.template get() == static_cast(static_cast(limits::max()) + 1)); const Json j2 = Json::parse("18446744073709551615"); CHECK(j2.is_number_float()); CHECK(j2.template get() == 18446744073709551615.0); const Json j3 = Json::parse(std::to_string(limits::max())); CHECK(j3.is_number_integer()); CHECK(j3.template get() == limits::max()); } SECTION("serialization of the extremes") { CHECK(Json(limits::max()).dump() == std::to_string(limits::max())); // the absolute value of the minimum does not fit into number_unsigned_t CHECK(Json(limits::min()).dump() == std::to_string(limits::min())); CHECK(Json::parse(std::to_string(limits::min())) == Json(limits::min())); CHECK(Json(0).dump() == "0"); } SECTION("construction from unsigned types") { const Json j1 = 5u; CHECK(j1.is_number_integer()); CHECK(!j1.is_number_unsigned()); CHECK(j1 == 5); const Json j2 = static_cast(200); CHECK(j2.is_number_integer()); CHECK(j2.template get() == 200); const Json j3 = std::vector {1, 2, 3}; CHECK(j3.dump() == "[1,2,3]"); CHECK(j3[0].is_number_integer()); const Json j4 = true; CHECK(j4.is_boolean()); } SECTION("value_t::number_unsigned creates a number_integer") { const Json j(nlohmann::detail::value_t::number_unsigned); CHECK(j.is_number_integer()); CHECK(!j.is_number_unsigned()); CHECK(j == 0); CHECK(j.dump() == "0"); } SECTION("conversions") { Json j = 42; CHECK(j.template get() == 42u); CHECK(j.template get() == 42u); CHECK(j.template get() == 42); CHECK(j.template get() == 42.0); CHECK(j.template get_ptr() != nullptr); CHECK(*j.template get_ptr() == 42); CHECK(j.template get_ref() == 42); j.template get_ref() = 43; CHECK(j == 43); integer_t i = 0; j.get_to(i); CHECK(i == 43); const Json object = {{"a", 1}}; CHECK(object.value("a", 0u) == 1u); CHECK(object.value("b", 7u) == 7u); } SECTION("conversion from a basic_json with distinct types") { const nlohmann::json j = {1, 2u, -3, 4.5}; CHECK(j[1].is_number_unsigned()); const Json converted = j; CHECK(converted.dump() == "[1,2,-3,4.5]"); CHECK(converted[1].is_number_integer()); CHECK(!converted[1].is_number_unsigned()); const nlohmann::json back = converted; CHECK(back == j); } SECTION("comparison and hashing") { const Json j1 = 1; const Json j2 = 2u; CHECK(j1 < j2); CHECK(j1 != j2); CHECK(j2 == 2); CHECK(j2 == 2u); CHECK(j1 < 1.5); CHECK(Json(-1) < Json(0u)); CHECK(std::hash {}(Json(5u)) == std::hash {}(Json(5))); } SECTION("JSON Patch, diff, and flatten") { const Json source = Json::parse(R"({"a": [1, 2, 3], "b": 4})"); const Json target = Json::parse(R"({"a": [1, 5], "c": 6})"); CHECK(source.patch(Json::diff(source, target)) == target); CHECK(source.flatten().unflatten() == source); } SECTION("binary formats") { const Json j = {{"min", limits::min()}, {"max", limits::max()}, {"zero", 0}, {"small", 100}, {"negative", -100}, {"float", 1.5}}; CHECK(Json::from_cbor(Json::to_cbor(j)) == j); CHECK(Json::from_msgpack(Json::to_msgpack(j)) == j); CHECK(Json::from_ubjson(Json::to_ubjson(j)) == j); CHECK(Json::from_bjdata(Json::to_bjdata(j)) == j); CHECK(Json::from_bson(Json::to_bson(j)) == j); CHECK(Json::from_bon8(Json::to_bon8(j)) == j); for (const auto& element : Json::from_cbor(Json::to_cbor(j))) { CHECK(!element.is_number_unsigned()); } // CBOR unsigned integer 2^64-1 does not fit and is read as float const std::vector cbor_max = {0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; const Json from_cbor = Json::from_cbor(cbor_max); CHECK(from_cbor.is_number_float()); CHECK(from_cbor.template get() == 18446744073709551615.0); // MessagePack uint 8 is read as number_integer const std::vector msgpack_uint8 = {0xCC, 0xC8}; const Json from_msgpack = Json::from_msgpack(msgpack_uint8); CHECK(from_msgpack.is_number_integer()); CHECK(from_msgpack == 200); } } TEST_CASE("same type for number_integer_t and number_unsigned_t: type traits") { CHECK(std::is_same, nlohmann::detail::same_as_number_integer_type>::value); CHECK(std::is_same, std::uint64_t>::value); CHECK(nlohmann::detail::is_compatible_number_integer_type::value); CHECK(!nlohmann::detail::is_compatible_number_integer_type::value); CHECK(!nlohmann::detail::is_compatible_number_integer_type::value); }