// __ _____ _____ _____ // __| | __| | | | 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 using nlohmann::json; #include #include #include namespace { // a spread of values exercising every writer path: scalars of each width, the // float paths, strings, binary, and containers big enough to reallocate std::vector test_values() { json big_array = json::array(); for (int i = 0; i < 5000; ++i) { big_array.push_back(i); } json big_object = json::object(); for (int i = 0; i < 1000; ++i) { big_object[std::to_string(i)] = i; } return { json(nullptr), json(true), json(false), json(0), json(-1), json(255), json(-129), json(65535), json(-32769), json(4294967295U), json(-2147483649LL), json(18446744073709551615ULL), json(0.0), json(-0.5), json(3.1415926535897932), json(""), json("hello"), json(std::string(1000, 'x')), json::binary({0x00, 0x01, 0x02}, 42), json::array(), json::object(), json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}), json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}), big_array, big_object }; } // values to_bson() accepts: the document must be an object std::vector bson_values() { json big_object = json::object(); for (int i = 0; i < 1000; ++i) { big_object[std::to_string(i)] = i; } return { json::object(), json({{"a", 1}, {"b", nullptr}, {"c", true}, {"d", 2.5}, {"e", "text"}}), json({{"arr", json::array({1, 2, 3})}, {"obj", {{"k", "v"}}}}), big_object }; } } // namespace // The vector-returning to_*(j) overloads write through the non-virtual // output_vector_sink, while to_*(j, adapter) goes through output_adapter_sink. // The two are separate code paths that must stay byte-for-byte identical; these // checks fail if either overload is ever changed without the other. TEST_CASE("binary writer output sinks") { SECTION("vector sink and adapter sink agree") { // note: no SUBCASE inside these loops - doctest keys subcases by // name/file/line, so a subcase in a loop body would only ever run for // the first iteration for (const auto& j : test_values()) { CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace)); std::vector cbor; json::to_cbor(j, cbor); CHECK(json::to_cbor(j) == cbor); std::vector msgpack; json::to_msgpack(j, msgpack); CHECK(json::to_msgpack(j) == msgpack); for (const bool use_size : { false, true }) { for (const bool use_type : { false, true }) { if (use_type && !use_size) { continue; // not a supported combination } CAPTURE(use_size); CAPTURE(use_type); std::vector ubjson; json::to_ubjson(j, ubjson, use_size, use_type); CHECK(json::to_ubjson(j, use_size, use_type) == ubjson); } } for (const auto version : { json::bjdata_version_t::draft2, json::bjdata_version_t::draft3 }) { std::vector bjdata; json::to_bjdata(j, bjdata, false, false, version); CHECK(json::to_bjdata(j, false, false, version) == bjdata); } } for (const auto& j : bson_values()) { CAPTURE(j.dump()); std::vector bson; json::to_bson(j, bson); CHECK(json::to_bson(j) == bson); } } SECTION("the char adapter produces the same bytes") { for (const auto& j : test_values()) { CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace)); const std::vector expected = json::to_cbor(j); std::vector as_char; json::to_cbor(j, as_char); REQUIRE(as_char.size() == expected.size()); std::vector as_bytes; as_bytes.reserve(as_char.size()); for (const char c : as_char) { as_bytes.push_back(static_cast(c)); } CHECK(as_bytes == expected); } } } // binary_reserve_hint() is documented as a *lower* bound on the serialized size, // so that reserving it up front can never leave the returned vector holding // capacity beyond what the value actually needs. TEST_CASE("binary_reserve_hint never over-reserves") { for (const auto& j : test_values()) { CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace)); const std::size_t hint = nlohmann::detail::binary_reserve_hint(j); CHECK(hint <= json::to_cbor(j).size()); CHECK(hint <= json::to_msgpack(j).size()); CHECK(hint <= json::to_ubjson(j).size()); CHECK(hint <= json::to_ubjson(j, true, true).size()); CHECK(hint <= json::to_bjdata(j).size()); } for (const auto& j : bson_values()) { CAPTURE(j.dump()); CHECK(nlohmann::detail::binary_reserve_hint(j) <= json::to_bson(j).size()); } SECTION("scalars get no hint") { CHECK(nlohmann::detail::binary_reserve_hint(json(nullptr)) == 0); CHECK(nlohmann::detail::binary_reserve_hint(json(42)) == 0); CHECK(nlohmann::detail::binary_reserve_hint(json("a string")) == 0); CHECK(nlohmann::detail::binary_reserve_hint(json::binary({0x01})) == 0); } SECTION("containers are hinted from their element count") { CHECK(nlohmann::detail::binary_reserve_hint(json::array()) == 1); CHECK(nlohmann::detail::binary_reserve_hint(json::array({1, 2, 3})) == 4); CHECK(nlohmann::detail::binary_reserve_hint(json::object()) == 1); CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5); } }