// __ _____ _____ _____ // __| | __| | | | 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 #include // split from unit-large_json.cpp so that both run in parallel and stay below // the ctest timeout under Valgrind namespace { // The text of a value nested depth levels deep around the number 0. Level i is // an array if pattern[i % pattern.size()] is '[', and otherwise an object with // the single member "a", which every object type enumerates in the same order. std::string nested_text(std::size_t depth, const std::string& pattern) { std::string text; std::string closing; for (std::size_t i = 0; i < depth; ++i) { const bool array = pattern[i % pattern.size()] == '['; text += array ? "[" : "{\"a\":"; closing += array ? ']' : '}'; } text += '0'; text.append(closing.rbegin(), closing.rend()); return text; } } // namespace TEST_CASE("issue #5650 - stack overflow converting between specializations") { // deep enough to exhaust the call stack, but small enough to stay cheap: // parsing is iterative, so building the values below costs little const std::size_t depth = 100000; const std::vector patterns = {"[", "{", "[{"}; SECTION("json to ordered_json") { for (const auto& pattern : patterns) { CAPTURE(pattern) const std::string text = nested_text(depth, pattern); const json j = json::parse(text); const nlohmann::ordered_json converted = j; CHECK(converted.dump() == text); } } SECTION("ordered_json to json") { for (const auto& pattern : patterns) { CAPTURE(pattern) const std::string text = nested_text(depth, pattern); const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text); const json converted = o; CHECK(converted.dump() == text); } } SECTION("get()") { for (const auto& pattern : patterns) { CAPTURE(pattern) const std::string text = nested_text(depth, pattern); const json j = json::parse(text); CHECK(j.get().dump() == text); } } SECTION("depths around the bound of the recursive descent") { for (std::size_t d = 1; d <= 300; ++d) { CAPTURE(d) for (const auto& pattern : patterns) { CAPTURE(pattern) const std::string text = nested_text(d, pattern); const json j = json::parse(text); const nlohmann::ordered_json converted = j; CHECK(converted.dump() == text); const json back = converted; CHECK(back.dump() == text); } } } SECTION("values below the bound are converted as values above it") { // Bury a value below the bound, where it is converted without the // call stack, and compare it with the same value converted on its // own by the containers' range constructors. Its objects have // members that the two object types enumerate in different orders. const auto bury = [](nlohmann::ordered_json value) { for (std::size_t i = 0; i < 200; ++i) { value = nlohmann::ordered_json::array({std::move(value)}); } return value; }; const auto dig = [](const json & value) { const json* current = &value; for (std::size_t i = 0; i < 200; ++i) { current = ¤t->at(0); } return current; }; nlohmann::ordered_json value = nlohmann::ordered_json::object(); value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9), nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object() }; value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}}; value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)}); const json converted_above = value; const json buried = bury(value); const json& converted_below = *dig(buried); CHECK(converted_below.dump() == converted_above.dump()); CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9); CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype()); CHECK(converted_below.at("r").at(0).is_discarded()); // a discarded value is never equal to anything, so compare the rest value.erase("r"); const json without_discarded_above = value; const json without_discarded_buried = bury(value); CHECK(*dig(without_discarded_buried) == without_discarded_above); } }