mirror of
https://github.com/nlohmann/json.git
synced 2026-10-09 07:57:13 +00:00
* Use the with_*_t aliases in tests, examples, and docs Replace spelled-out basic_json<...> instantiations that only change one or two template parameters with nlohmann::json::with_*_t (or ordered_json::with_*_t when the object type is ordered_map). Types that change all three number types chain with_integers_t and with_float_t. The raw basic_json<...> spelling stays where the template parameter list itself is the subject: the alias tests in unit-udt.cpp, explicit instantiations, and the ordered_json/compile-time docs. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix unit-large_json for clang and JSON_DIAGNOSTICS Two test problems from #5781 broke CI on develop: CAPTURE(depth); trips clang's -Wextra-semi-stmt, and the type_error.321 messages did not account for the diagnostics path prefix. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use static_cast in unit-hash for clang-tidy #5772 added functional casts that clang-tidy reports as C-style casts (google-readability-casting). Also append a char instead of a one-character string in unit-large_json. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Declare the expected message prefix const in unit-large_json Without JSON_DIAGNOSTICS the prefix was never modified, which clang-tidy reports (misc-const-correctness). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
370 lines
16 KiB
C++
370 lines
16 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 <cmath>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "make_test_data_available.hpp"
|
|
|
|
TEST_CASE("Binary Formats")
|
|
{
|
|
SECTION("canada.json")
|
|
{
|
|
const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json";
|
|
const json j = json::parse(std::ifstream(filename));
|
|
|
|
const auto json_size = j.dump().size();
|
|
const auto bjdata_1_size = json::to_bjdata(j).size();
|
|
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
|
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
|
const auto bon8_size = json::to_bon8(j).size();
|
|
const auto bson_size = json::to_bson(j).size();
|
|
const auto cbor_size = json::to_cbor(j).size();
|
|
const auto msgpack_size = json::to_msgpack(j).size();
|
|
const auto ubjson_1_size = json::to_ubjson(j).size();
|
|
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
|
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
|
|
|
CHECK(json_size == 2090303);
|
|
CHECK(bjdata_1_size == 1112030);
|
|
CHECK(bjdata_2_size == 1224148);
|
|
CHECK(bjdata_3_size == 1224148);
|
|
CHECK(bon8_size == 1055792);
|
|
CHECK(bson_size == 1794522);
|
|
CHECK(cbor_size == 1055552);
|
|
CHECK(msgpack_size == 1056145);
|
|
CHECK(ubjson_1_size == 1112030);
|
|
CHECK(ubjson_2_size == 1224148);
|
|
CHECK(ubjson_3_size == 1169069);
|
|
|
|
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
|
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199));
|
|
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563));
|
|
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563));
|
|
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(50.509));
|
|
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849));
|
|
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497));
|
|
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526));
|
|
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(53.199));
|
|
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(58.563));
|
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(55.928));
|
|
}
|
|
|
|
SECTION("twitter.json")
|
|
{
|
|
const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json";
|
|
const json j = json::parse(std::ifstream(filename));
|
|
|
|
const auto json_size = j.dump().size();
|
|
const auto bjdata_1_size = json::to_bjdata(j).size();
|
|
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
|
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
|
const auto bon8_size = json::to_bon8(j).size();
|
|
const auto bson_size = json::to_bson(j).size();
|
|
const auto cbor_size = json::to_cbor(j).size();
|
|
const auto msgpack_size = json::to_msgpack(j).size();
|
|
const auto ubjson_1_size = json::to_ubjson(j).size();
|
|
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
|
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
|
|
|
CHECK(json_size == 466906);
|
|
CHECK(bjdata_1_size == 425342);
|
|
CHECK(bjdata_2_size == 429970);
|
|
CHECK(bjdata_3_size == 429970);
|
|
CHECK(bon8_size == 391396);
|
|
CHECK(bson_size == 444568);
|
|
CHECK(cbor_size == 402814);
|
|
CHECK(msgpack_size == 401510);
|
|
CHECK(ubjson_1_size == 426160);
|
|
CHECK(ubjson_2_size == 430788);
|
|
CHECK(ubjson_3_size == 430798);
|
|
|
|
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
|
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(91.097));
|
|
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(92.089));
|
|
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(92.089));
|
|
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(83.828));
|
|
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.215));
|
|
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(86.273));
|
|
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(85.993));
|
|
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(91.273));
|
|
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(92.264));
|
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(92.266));
|
|
}
|
|
|
|
SECTION("citm_catalog.json")
|
|
{
|
|
const auto* filename = TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json";
|
|
const json j = json::parse(std::ifstream(filename));
|
|
|
|
const auto json_size = j.dump().size();
|
|
const auto bjdata_1_size = json::to_bjdata(j).size();
|
|
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
|
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
|
const auto bon8_size = json::to_bon8(j).size();
|
|
const auto bson_size = json::to_bson(j).size();
|
|
const auto cbor_size = json::to_cbor(j).size();
|
|
const auto msgpack_size = json::to_msgpack(j).size();
|
|
const auto ubjson_1_size = json::to_ubjson(j).size();
|
|
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
|
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
|
|
|
CHECK(json_size == 500299);
|
|
CHECK(bjdata_1_size == 390781);
|
|
CHECK(bjdata_2_size == 433557);
|
|
CHECK(bjdata_3_size == 432964);
|
|
CHECK(bon8_size == 317879);
|
|
CHECK(bson_size == 479430);
|
|
CHECK(cbor_size == 342373);
|
|
CHECK(msgpack_size == 342473);
|
|
CHECK(ubjson_1_size == 391463);
|
|
CHECK(ubjson_2_size == 434239);
|
|
CHECK(ubjson_3_size == 425073);
|
|
|
|
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
|
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(78.109));
|
|
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(86.659));
|
|
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(86.541));
|
|
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(63.538));
|
|
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.828));
|
|
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(68.433));
|
|
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(68.453));
|
|
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(78.245));
|
|
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(86.795));
|
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(84.963));
|
|
}
|
|
|
|
SECTION("sample.json")
|
|
{
|
|
const auto* filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
|
const json j = json::parse(std::ifstream(filename));
|
|
|
|
const auto json_size = j.dump().size();
|
|
const auto bjdata_1_size = json::to_bjdata(j).size();
|
|
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
|
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
|
const auto bon8_size = json::to_bon8(j).size();
|
|
// BSON cannot process the file as it contains code point U+0000
|
|
const auto cbor_size = json::to_cbor(j).size();
|
|
const auto msgpack_size = json::to_msgpack(j).size();
|
|
const auto ubjson_1_size = json::to_ubjson(j).size();
|
|
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
|
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
|
|
|
CHECK(json_size == 168677);
|
|
CHECK(bjdata_1_size == 148695);
|
|
CHECK(bjdata_2_size == 150569);
|
|
CHECK(bjdata_3_size == 150569);
|
|
CHECK(bon8_size == 144477);
|
|
CHECK(cbor_size == 147095);
|
|
CHECK(msgpack_size == 147017);
|
|
CHECK(ubjson_1_size == 148695);
|
|
CHECK(ubjson_2_size == 150569);
|
|
CHECK(ubjson_3_size == 150883);
|
|
|
|
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
|
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(88.153));
|
|
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(89.264));
|
|
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(89.264));
|
|
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(85.653));
|
|
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.205));
|
|
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.158));
|
|
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(88.153));
|
|
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(89.264));
|
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
|
|
}
|
|
}
|
|
|
|
// jeopardy.json is 52 MB and produces ~500 MB of serialization output, so it
|
|
// is kept apart from the cheap corpus files above (#5418)
|
|
TEST_CASE("Binary Formats (jeopardy.json)" * doctest::skip())
|
|
{
|
|
const auto* filename = TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json";
|
|
json j = json::parse(std::ifstream(filename));
|
|
|
|
const auto json_size = j.dump().size();
|
|
const auto bjdata_1_size = json::to_bjdata(j).size();
|
|
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
|
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
|
const auto bon8_size = json::to_bon8(j).size();
|
|
const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON
|
|
const auto cbor_size = json::to_cbor(j).size();
|
|
const auto msgpack_size = json::to_msgpack(j).size();
|
|
const auto ubjson_1_size = json::to_ubjson(j).size();
|
|
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
|
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
|
|
|
CHECK(json_size == 52508728);
|
|
CHECK(bjdata_1_size == 50710965);
|
|
CHECK(bjdata_2_size == 51144830);
|
|
CHECK(bjdata_3_size == 51144830);
|
|
CHECK(bon8_size == 45942080);
|
|
CHECK(bson_size == 56008520);
|
|
CHECK(cbor_size == 46187320);
|
|
CHECK(msgpack_size == 46158575);
|
|
CHECK(ubjson_1_size == 50710965);
|
|
CHECK(ubjson_2_size == 51144830);
|
|
CHECK(ubjson_3_size == 49861422);
|
|
|
|
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
|
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576));
|
|
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402));
|
|
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402));
|
|
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(87.494));
|
|
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665));
|
|
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961));
|
|
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906));
|
|
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(96.576));
|
|
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(97.402));
|
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(94.958));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
// the binary formats as function pointers for "Binary formats with narrow number types";
|
|
// named functions rather than lambdas, because clang 3.5 cannot convert a lambda
|
|
// to a function pointer in the braced initializer of the format table
|
|
using narrow_json = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>::with_float_t<float>;
|
|
using bytes = std::vector<std::uint8_t>;
|
|
|
|
bytes encode_cbor(const json& j)
|
|
{
|
|
return json::to_cbor(j);
|
|
}
|
|
narrow_json decode_cbor(const bytes& v, bool allow_exceptions)
|
|
{
|
|
return narrow_json::from_cbor(v, true, allow_exceptions);
|
|
}
|
|
|
|
bytes encode_msgpack(const json& j)
|
|
{
|
|
return json::to_msgpack(j);
|
|
}
|
|
narrow_json decode_msgpack(const bytes& v, bool allow_exceptions)
|
|
{
|
|
return narrow_json::from_msgpack(v, true, allow_exceptions);
|
|
}
|
|
|
|
bytes encode_ubjson(const json& j)
|
|
{
|
|
return json::to_ubjson(j);
|
|
}
|
|
narrow_json decode_ubjson(const bytes& v, bool allow_exceptions)
|
|
{
|
|
return narrow_json::from_ubjson(v, true, allow_exceptions);
|
|
}
|
|
|
|
bytes encode_bjdata(const json& j)
|
|
{
|
|
return json::to_bjdata(j);
|
|
}
|
|
narrow_json decode_bjdata(const bytes& v, bool allow_exceptions)
|
|
{
|
|
return narrow_json::from_bjdata(v, true, allow_exceptions);
|
|
}
|
|
|
|
// BSON can only store numbers as object members
|
|
bytes encode_bson(const json& j)
|
|
{
|
|
return json::to_bson(json{{"a", j}});
|
|
}
|
|
narrow_json decode_bson(const bytes& v, bool allow_exceptions)
|
|
{
|
|
const auto result = narrow_json::from_bson(v, true, allow_exceptions);
|
|
return result.is_discarded() ? result : result.at("a");
|
|
}
|
|
|
|
bytes encode_bon8(const json& j)
|
|
{
|
|
return json::to_bon8(j);
|
|
}
|
|
narrow_json decode_bon8(const bytes& v, bool allow_exceptions)
|
|
{
|
|
return narrow_json::from_bon8(v, true, allow_exceptions);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Binary formats with narrow number types")
|
|
{
|
|
// Numbers that do not fit the number types are handled like the lexer
|
|
// handles them in JSON text: an integer that fits neither integer type is
|
|
// stored as a floating-point number, and a finite floating-point number
|
|
// that overflows number_float_t is rejected with out_of_range.406.
|
|
struct binary_format
|
|
{
|
|
const char* name;
|
|
bytes (*encode)(const json&);
|
|
narrow_json (*decode)(const bytes&, bool);
|
|
};
|
|
|
|
const std::vector<binary_format> formats =
|
|
{
|
|
{"CBOR", encode_cbor, decode_cbor},
|
|
{"MessagePack", encode_msgpack, decode_msgpack},
|
|
{"UBJSON", encode_ubjson, decode_ubjson},
|
|
{"BJData", encode_bjdata, decode_bjdata},
|
|
{"BSON", encode_bson, decode_bson},
|
|
{"BON8", encode_bon8, decode_bon8},
|
|
};
|
|
|
|
for (const auto& format : formats)
|
|
{
|
|
const std::string name = format.name;
|
|
INFO("format := ", name);
|
|
const auto roundtrip = [&format](const json & j)
|
|
{
|
|
return format.decode(format.encode(j), true);
|
|
};
|
|
|
|
// integers that fit keep their type
|
|
CHECK(roundtrip(json(-5)).is_number_integer());
|
|
CHECK(roundtrip(json(-5)).get<std::int32_t>() == -5);
|
|
CHECK(roundtrip(json(3000000000u)).is_number_unsigned());
|
|
CHECK(roundtrip(json(3000000000u)).get<std::uint32_t>() == 3000000000u);
|
|
|
|
// integers that fit neither integer type are stored as float
|
|
CHECK(roundtrip(json(5000000000u)).is_number_float());
|
|
CHECK(roundtrip(json(5000000000u)).get<float>() == 5000000000.0f);
|
|
if (name != "BON8") // BON8 cannot encode integers above INT64_MAX
|
|
{
|
|
CHECK(roundtrip(json(10000000000000000000u)).is_number_float());
|
|
CHECK(roundtrip(json(10000000000000000000u)).get<float>() == 10000000000000000000.0f);
|
|
}
|
|
CHECK(roundtrip(json(-3000000000LL)).is_number_float());
|
|
CHECK(roundtrip(json(-3000000000LL)).get<float>() == -3000000000.0f);
|
|
CHECK(roundtrip(json(-5000000000LL)).is_number_float());
|
|
CHECK(roundtrip(json(-5000000000LL)).get<float>() == -5000000000.0f);
|
|
|
|
// floating-point numbers that fit
|
|
CHECK(roundtrip(json(1.5)).get<float>() == 1.5f);
|
|
const auto just_above_max = std::nextafter(static_cast<double>((std::numeric_limits<float>::max)()),
|
|
std::numeric_limits<double>::infinity());
|
|
CHECK(roundtrip(json(just_above_max)).get<float>() == (std::numeric_limits<float>::max)());
|
|
|
|
// infinity and NaN are passed on
|
|
CHECK(std::isinf(roundtrip(json(std::numeric_limits<double>::infinity())).get<float>()));
|
|
CHECK(std::isnan(roundtrip(json(std::numeric_limits<double>::quiet_NaN())).get<float>()));
|
|
|
|
// finite floating-point numbers that overflow number_float_t are rejected
|
|
const std::string message = "[json.exception.out_of_range.406] syntax error while parsing " + name
|
|
+ " value: number overflow";
|
|
CHECK_THROWS_WITH_AS(roundtrip(json(1e300)), message.c_str(), narrow_json::out_of_range&);
|
|
CHECK_THROWS_WITH_AS(roundtrip(json(-1e300)), message.c_str(), narrow_json::out_of_range&);
|
|
CHECK(format.decode(format.encode(json(1e300)), false).is_discarded());
|
|
}
|
|
}
|