mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 02:37:13 +00:00
The vector-returning to_cbor(), to_msgpack(), ... wrote through output_vector_sink, which appended each write with push_back() or insert(). A number's 1 to 8 payload bytes went through vector::insert(), which libc++ and libstdc++ both run as an out-of-line insert-anywhere routine; on integer arrays that call cost more than encoding the number, so the devirtualization of #5286 brought nothing there, and with GCC the writer was slower than in 3.12.0. output_vector_sink now keeps its own write position: it grows the vector geometrically ahead of the position (resize(), using any capacity the vector already has), copies each write there, and shrinks the vector to the written length in its destructor, also when a write throws. Writes of 256 characters or more that do not fit are appended with insert(), so a large string or binary value is copied once instead of zero-filled and copied. The sink is move-only, as it owns that trim. output_vector_adapter (the to_*(j, vector) overloads) appends with push_back()/insert() again, so a caller holding the adapter never sees the space kept ahead. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
229 lines
7.7 KiB
C++
229 lines
7.7 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
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<json> 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;
|
|
}
|
|
|
|
// large strings and binary values between small ones: a write too large
|
|
// for the space the vector sink has ahead takes a different path than a
|
|
// small one, and the small writes after it must still land behind it
|
|
json large_payloads = json::array();
|
|
for (int i = 0; i < 50; ++i)
|
|
{
|
|
large_payloads.push_back(std::string(300, static_cast<char>('a' + (i % 26))));
|
|
large_payloads.push_back(i);
|
|
large_payloads.push_back(json::binary(std::vector<std::uint8_t>(static_cast<std::size_t>(100 * i), static_cast<std::uint8_t>(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, large_payloads,
|
|
json::binary(std::vector<std::uint8_t>(5000, 0x42))
|
|
};
|
|
}
|
|
|
|
// BON8 has no integers above the int64 range, so to_bon8() rejects them
|
|
bool bon8_representable(const json& j)
|
|
{
|
|
return !j.is_number_unsigned() || j.get<std::uint64_t>() <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)());
|
|
}
|
|
|
|
// values to_bson() accepts: the document must be an object
|
|
std::vector<json> 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<std::uint8_t> cbor;
|
|
json::to_cbor(j, cbor);
|
|
CHECK(json::to_cbor(j) == cbor);
|
|
|
|
std::vector<std::uint8_t> msgpack;
|
|
json::to_msgpack(j, msgpack);
|
|
CHECK(json::to_msgpack(j) == msgpack);
|
|
|
|
if (bon8_representable(j))
|
|
{
|
|
std::vector<std::uint8_t> bon8;
|
|
json::to_bon8(j, bon8);
|
|
CHECK(json::to_bon8(j) == bon8);
|
|
}
|
|
|
|
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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> expected = json::to_cbor(j);
|
|
std::vector<char> as_char;
|
|
json::to_cbor(j, as_char);
|
|
|
|
REQUIRE(as_char.size() == expected.size());
|
|
std::vector<std::uint8_t> as_bytes;
|
|
as_bytes.reserve(as_char.size());
|
|
for (const char c : as_char)
|
|
{
|
|
as_bytes.push_back(static_cast<std::uint8_t>(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());
|
|
if (bon8_representable(j))
|
|
{
|
|
CHECK(hint <= json::to_bon8(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);
|
|
}
|
|
}
|