mirror of
https://github.com/nlohmann/json.git
synced 2026-10-11 17:07:15 +00:00
Split the dump and comparison tests off unit-json_view.cpp
The MinGW linker of the Windows clang jobs cannot link object files with more
than 32767 sections ("relocation truncated to fit: IMAGE_REL_AMD64_REL32
against `.rdata'"). unit-json_view.cpp reaches that limit as the stack
grows, so its "json_view dump" and "json_view comparison" test cases move
into unit-json_view_dump.cpp. The test generator and has_duplicate_keys()
that both files use move into json_view_test_helpers.hpp.
The new file mentions JSON_HAS_CPP_17, so it is built for C++17 like the file
it was split from.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
4 files changed
+369
-305
No files matched your search
@@ -51,6 +51,7 @@ labels:
|
|||||||
- "include/nlohmann/detail/view/.*"
|
- "include/nlohmann/detail/view/.*"
|
||||||
- "single_include/nlohmann/json_view\\.hpp"
|
- "single_include/nlohmann/json_view\\.hpp"
|
||||||
- "tests/src/unit-json_view.*"
|
- "tests/src/unit-json_view.*"
|
||||||
|
- "tests/src/json_view_test_helpers\\.hpp"
|
||||||
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
||||||
- "tools/amalgamate/config_json_view\\.json"
|
- "tools/amalgamate/config_json_view\\.json"
|
||||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// helpers shared by the unit-json_view*.cpp files, which are split to keep every
|
||||||
|
// object file small enough for the MinGW linker (it fails to link objects with
|
||||||
|
// more than 32767 sections)
|
||||||
|
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#include <algorithm> // any_of
|
||||||
|
#include <random> // mt19937
|
||||||
|
#include <string> // string
|
||||||
|
|
||||||
|
namespace json_view_test
|
||||||
|
{
|
||||||
|
// a small deterministic generator of documents
|
||||||
|
struct generator
|
||||||
|
{
|
||||||
|
std::mt19937 rng{5295}; // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
|
||||||
|
int r(int n)
|
||||||
|
{
|
||||||
|
return static_cast<int>(rng() % static_cast<unsigned>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
void str(std::string& o)
|
||||||
|
{
|
||||||
|
static const char* const pieces[] = {"a", "Z", " ", "\\n", "\\\"", "\\u00e9", "\\ud83d\\ude00", "\xc3\xa9", "\xe3\x81\x82", "long text beyond the first sixteen bytes"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
o += '"';
|
||||||
|
for (int n = r(5); n > 0; --n)
|
||||||
|
{
|
||||||
|
o += pieces[r(10)];
|
||||||
|
}
|
||||||
|
o += '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
void value(std::string& o, int depth)
|
||||||
|
{
|
||||||
|
static const char* const scalars[] = {"0", "-1", "123456789012", "18446744073709551615", "18446744073709551616", "-9223372036854775809", // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
"1.5", "-2.25e-3", "1E2", "0.1", "true", "false", "null"
|
||||||
|
};
|
||||||
|
const int k = depth > 5 ? 2 + r(4) : r(6);
|
||||||
|
if (k < 2)
|
||||||
|
{
|
||||||
|
const bool object = k == 0;
|
||||||
|
o += object ? '{' : '[';
|
||||||
|
for (int i = r(5); i > 0; --i)
|
||||||
|
{
|
||||||
|
if (object)
|
||||||
|
{
|
||||||
|
str(o);
|
||||||
|
o += r(2) == 0 ? ":" : " : ";
|
||||||
|
}
|
||||||
|
value(o, depth + 1);
|
||||||
|
o += i > 1 ? ", " : "";
|
||||||
|
}
|
||||||
|
o += object ? '}' : ']';
|
||||||
|
}
|
||||||
|
else if (k < 4)
|
||||||
|
{
|
||||||
|
str(o);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o += scalars[r(13)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// whether an object of the view repeats a key
|
||||||
|
inline bool has_duplicate_keys(const nlohmann::ordered_json_view& v)
|
||||||
|
{
|
||||||
|
if (v.is_object() && v.size() != v.materialize().size())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return std::any_of(v.begin(), v.end(), [](const nlohmann::ordered_json_view e)
|
||||||
|
{
|
||||||
|
return e.is_structured() && has_duplicate_keys(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // namespace json_view_test
|
||||||
@@ -16,6 +16,10 @@ using nlohmann::json_view;
|
|||||||
using nlohmann::ordered_json_document;
|
using nlohmann::ordered_json_document;
|
||||||
using nlohmann::ordered_json_view;
|
using nlohmann::ordered_json_view;
|
||||||
|
|
||||||
|
#include "json_view_test_helpers.hpp"
|
||||||
|
using json_view_test::generator;
|
||||||
|
using json_view_test::has_duplicate_keys;
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -121,60 +125,6 @@ std::string view_exception(const std::string& text, bool comments = false, bool
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// a small deterministic generator of documents
|
|
||||||
struct generator
|
|
||||||
{
|
|
||||||
std::mt19937 rng{5295}; // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
|
|
||||||
int r(int n)
|
|
||||||
{
|
|
||||||
return static_cast<int>(rng() % static_cast<unsigned>(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
void str(std::string& o)
|
|
||||||
{
|
|
||||||
static const char* const pieces[] = {"a", "Z", " ", "\\n", "\\\"", "\\u00e9", "\\ud83d\\ude00", "\xc3\xa9", "\xe3\x81\x82", "long text beyond the first sixteen bytes"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
o += '"';
|
|
||||||
for (int n = r(5); n > 0; --n)
|
|
||||||
{
|
|
||||||
o += pieces[r(10)];
|
|
||||||
}
|
|
||||||
o += '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
void value(std::string& o, int depth)
|
|
||||||
{
|
|
||||||
static const char* const scalars[] = {"0", "-1", "123456789012", "18446744073709551615", "18446744073709551616", "-9223372036854775809", // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
"1.5", "-2.25e-3", "1E2", "0.1", "true", "false", "null"
|
|
||||||
};
|
|
||||||
const int k = depth > 5 ? 2 + r(4) : r(6);
|
|
||||||
if (k < 2)
|
|
||||||
{
|
|
||||||
const bool object = k == 0;
|
|
||||||
o += object ? '{' : '[';
|
|
||||||
for (int i = r(5); i > 0; --i)
|
|
||||||
{
|
|
||||||
if (object)
|
|
||||||
{
|
|
||||||
str(o);
|
|
||||||
o += r(2) == 0 ? ":" : " : ";
|
|
||||||
}
|
|
||||||
value(o, depth + 1);
|
|
||||||
o += i > 1 ? ", " : "";
|
|
||||||
}
|
|
||||||
o += object ? '}' : ']';
|
|
||||||
}
|
|
||||||
else if (k < 4)
|
|
||||||
{
|
|
||||||
str(o);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o += scalars[r(13)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("json_view")
|
TEST_CASE("json_view")
|
||||||
@@ -1020,18 +970,6 @@ std::uint32_t bits(float x)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_duplicate_keys(const ordered_json_view& v)
|
|
||||||
{
|
|
||||||
if (v.is_object() && v.size() != v.materialize().size())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return std::any_of(v.begin(), v.end(), [](const ordered_json_view e)
|
|
||||||
{
|
|
||||||
return e.is_structured() && has_duplicate_keys(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// compares the conversions of a view with those of ordered_json
|
// compares the conversions of a view with those of ordered_json
|
||||||
void check_values(const ordered_json_view& v, const ordered_json& j, const std::string& text)
|
void check_values(const ordered_json_view& v, const ordered_json& j, const std::string& text)
|
||||||
{
|
{
|
||||||
@@ -1376,242 +1314,3 @@ TEST_CASE("json_view JSON pointers")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("json_view dump")
|
|
||||||
{
|
|
||||||
SECTION("the output of ordered_json::dump()")
|
|
||||||
{
|
|
||||||
generator g;
|
|
||||||
for (int i = 0; i < 2000; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
g.value(text, 0);
|
|
||||||
const ordered_json_document d = ordered_json_document::parse(text);
|
|
||||||
if (has_duplicate_keys(d.root()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CAPTURE(text)
|
|
||||||
const ordered_json j = ordered_json::parse(text);
|
|
||||||
for (const int indent :
|
|
||||||
{
|
|
||||||
-1, 0, 2
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const bool ensure_ascii :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CHECK(d.root().dump(indent, i % 2 == 0 ? ' ' : '\t', ensure_ascii) == j.dump(indent, i % 2 == 0 ? ' ' : '\t', ensure_ascii));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// also of each element
|
|
||||||
for (const ordered_json_view e : d.root())
|
|
||||||
{
|
|
||||||
CHECK(e.dump() == e.materialize().dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("strings")
|
|
||||||
{
|
|
||||||
const std::string text = R"(["plain", "\u0000\u0001\u001f\u007f\u0080é€😀", "\"\\\/\b\f\n\r\t", "aéあ😀b", "long text beyond the eight bytes of a word \n with an escape in the middle"])";
|
|
||||||
const ordered_json_document d = ordered_json_document::parse(text);
|
|
||||||
const ordered_json j = ordered_json::parse(text);
|
|
||||||
CHECK(d.root().dump() == j.dump());
|
|
||||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true));
|
|
||||||
const std::string key_text = R"({"é\n": {"\"": [], "": {}}})";
|
|
||||||
const ordered_json_document keys = ordered_json_document::parse(key_text);
|
|
||||||
const ordered_json key_json = ordered_json::parse(key_text);
|
|
||||||
CHECK(keys.root().dump(2, ' ', true) == key_json.dump(2, ' ', true));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("numbers")
|
|
||||||
{
|
|
||||||
const std::string text = "[1.50, 1E2, -0, -0.0, 123456789012345678901234567890, 18446744073709551615, -9223372036854775808, 0.1, 1e-7, 5e-324]";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
CHECK(d.root().dump() == json::parse(text).dump());
|
|
||||||
CHECK(d.root().dump() == "[1.5,100.0,0,-0.0,1.2345678901234568e+29,18446744073709551615,-9223372036854775808,0.1,1e-07,5e-324]");
|
|
||||||
CHECK(d.root().dump(-1, ' ', false, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
|
|
||||||
|
|
||||||
// random doubles, written as parse() and dump() would
|
|
||||||
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
std::string many = "[";
|
|
||||||
for (int i = 0; i < 5000; ++i)
|
|
||||||
{
|
|
||||||
const std::uint64_t bits = rng();
|
|
||||||
double x = 0;
|
|
||||||
std::memcpy(&x, &bits, sizeof(x));
|
|
||||||
if (std::isfinite(x))
|
|
||||||
{
|
|
||||||
many += (many.size() > 1 ? "," : "") + json(x).dump();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
many += ']';
|
|
||||||
const json_document many_document = json_document::parse(many);
|
|
||||||
CHECK(many_document.root().dump() == json::parse(many).dump());
|
|
||||||
|
|
||||||
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
|
||||||
const nlohmann::basic_json_document<json_float> float_document = nlohmann::basic_json_document<json_float>::parse("[0.1, 1.5e10, 3.4028235e38]");
|
|
||||||
CHECK(float_document.root().dump() == json_float::parse("[0.1, 1.5e10, 3.4028235e38]").dump());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("members in document order, all of them")
|
|
||||||
{
|
|
||||||
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
|
||||||
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
|
||||||
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
|
||||||
// dump() writes all members, though the lookup finds the first
|
|
||||||
CHECK(d.root()["b"].dump() == "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
|
||||||
{
|
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
|
||||||
const json_document deep_document = json_document::parse(deep);
|
|
||||||
CHECK(deep_document.root().dump() == deep);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the output buffer of a small value is small")
|
|
||||||
{
|
|
||||||
// an escaped key after the value: its node lies in the arena, so the
|
|
||||||
// source extent of the value cannot be read from the next node
|
|
||||||
const std::string big(100000, 'a');
|
|
||||||
const std::string text = R"({"small":1,"list":[1,2,3],"k\n":")" + big + R"("})";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const auto small = d.root()["small"].dump();
|
|
||||||
CHECK(small == "1");
|
|
||||||
CHECK(small.capacity() < 4096);
|
|
||||||
const auto list = d.root()["list"].dump();
|
|
||||||
CHECK(list == "[1,2,3]");
|
|
||||||
CHECK(list.capacity() < 4096);
|
|
||||||
CHECK(d.root()["list"].dump(2).capacity() < 4096);
|
|
||||||
|
|
||||||
// the whole document and the large value are unaffected
|
|
||||||
CHECK(d.root().dump() == ordered_json::parse(text).dump());
|
|
||||||
CHECK(d.root()["k\n"].dump() == "\"" + big + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("output that outgrows the estimate")
|
|
||||||
{
|
|
||||||
// ensure_ascii writes six bytes for each two-byte character
|
|
||||||
std::string chars;
|
|
||||||
for (int i = 0; i < 5000; ++i)
|
|
||||||
{
|
|
||||||
chars += "\xC3\xA9";
|
|
||||||
}
|
|
||||||
const json_document d = json_document::parse(R"({"a":")" + chars + R"(","k\n":1})");
|
|
||||||
const json expected = json::parse("\"" + chars + "\"");
|
|
||||||
CHECK(d.root()["a"].dump(-1, ' ', true) == expected.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("streams and discarded views")
|
|
||||||
{
|
|
||||||
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
|
||||||
std::ostringstream compact;
|
|
||||||
compact << d.root();
|
|
||||||
CHECK(compact.str() == R"({"a":[1,2]})");
|
|
||||||
std::ostringstream pretty;
|
|
||||||
pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"];
|
|
||||||
CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]");
|
|
||||||
CHECK(json_view().dump() == json(json::value_t::discarded).dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view comparison")
|
|
||||||
{
|
|
||||||
SECTION("equality of the values parse() produces")
|
|
||||||
{
|
|
||||||
generator g;
|
|
||||||
std::vector<std::string> texts;
|
|
||||||
for (int i = 0; i < 600; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
g.value(text, 0);
|
|
||||||
texts.push_back(text);
|
|
||||||
// the same value written differently: sorted keys, canonical numbers
|
|
||||||
texts.push_back(json::parse(text).dump(1));
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
|
||||||
{
|
|
||||||
for (std::size_t k = i; k < i + 3; ++k)
|
|
||||||
{
|
|
||||||
CAPTURE(texts[i])
|
|
||||||
CAPTURE(texts[k])
|
|
||||||
const json_document a = json_document::parse(texts[i]);
|
|
||||||
const json_document b = json_document::parse(texts[k]);
|
|
||||||
const json ja = json::parse(texts[i]);
|
|
||||||
const json jb = json::parse(texts[k]);
|
|
||||||
CHECK((a.root() == b.root()) == (ja == jb));
|
|
||||||
CHECK((a.root() != b.root()) == (ja != jb));
|
|
||||||
CHECK((a.root() == jb) == (ja == jb));
|
|
||||||
CHECK((jb == a.root()) == (ja == jb));
|
|
||||||
CHECK((a.root() != jb) == (ja != jb));
|
|
||||||
CHECK((jb != a.root()) == (ja != jb));
|
|
||||||
|
|
||||||
// ordered_json compares members in order
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
|
||||||
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
|
||||||
const ordered_json oja = ordered_json::parse(texts[i]);
|
|
||||||
const ordered_json ojb = ordered_json::parse(texts[k]);
|
|
||||||
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
|
||||||
CHECK((oa.root() == ojb) == (oja == ojb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("numbers, duplicate keys, member order")
|
|
||||||
{
|
|
||||||
const auto same = [](const char* x, const char* y)
|
|
||||||
{
|
|
||||||
const json_document dx = json_document::parse(x);
|
|
||||||
const json_document dy = json_document::parse(y);
|
|
||||||
return dx.root() == dy.root();
|
|
||||||
};
|
|
||||||
CHECK(same("1", "1.0"));
|
|
||||||
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
|
||||||
CHECK(!same("1", "1.5"));
|
|
||||||
CHECK(same("18446744073709551615", "18446744073709551615"));
|
|
||||||
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
|
||||||
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
|
||||||
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
|
||||||
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
|
||||||
CHECK(!same("[1, 2]", "[2, 1]"));
|
|
||||||
CHECK(!same("\"a\"", "\"b\""));
|
|
||||||
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
|
||||||
CHECK(!same("null", "false"));
|
|
||||||
CHECK(!same("[]", "{}"));
|
|
||||||
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
|
||||||
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
|
||||||
CHECK(dup.root() == last.root());
|
|
||||||
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
|
||||||
CHECK(dup.root()["a"].materialize() == 1);
|
|
||||||
CHECK(dup.root()["a"] != last.root()["a"]);
|
|
||||||
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
|
||||||
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
|
||||||
CHECK(ab.root() != ba.root());
|
|
||||||
|
|
||||||
// discarded values compare as basic_json's do
|
|
||||||
const json discarded(json::value_t::discarded);
|
|
||||||
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
|
||||||
CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty)
|
|
||||||
const json_document null_document = json_document::parse("null");
|
|
||||||
CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty)
|
|
||||||
CHECK(!(null_document.root() == discarded));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
|
||||||
{
|
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
|
||||||
const json_document a = json_document::parse(deep);
|
|
||||||
const json_document b = json_document::parse(deep);
|
|
||||||
CHECK(a.root() == b.root());
|
|
||||||
CHECK(a.root() == json::parse(deep));
|
|
||||||
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
|
||||||
const json_document c = json_document::parse(other);
|
|
||||||
CHECK(a.root() != c.root());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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_view.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
using nlohmann::ordered_json_document;
|
||||||
|
using nlohmann::ordered_json_view;
|
||||||
|
|
||||||
|
#include "json_view_test_helpers.hpp"
|
||||||
|
using json_view_test::generator;
|
||||||
|
using json_view_test::has_duplicate_keys;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// These tests were split off unit-json_view.cpp, whose object file got too large
|
||||||
|
// for the MinGW linker (see json_view_test_helpers.hpp). Like that file, this one
|
||||||
|
// is also built with C++17, as it mentions JSON_HAS_CPP_17.
|
||||||
|
|
||||||
|
TEST_CASE("json_view dump")
|
||||||
|
{
|
||||||
|
SECTION("the output of ordered_json::dump()")
|
||||||
|
{
|
||||||
|
generator g;
|
||||||
|
for (int i = 0; i < 2000; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
g.value(text, 0);
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
if (has_duplicate_keys(d.root()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CAPTURE(text)
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
-1, 0, 2
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const bool ensure_ascii :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(d.root().dump(indent, i % 2 == 0 ? ' ' : '\t', ensure_ascii) == j.dump(indent, i % 2 == 0 ? ' ' : '\t', ensure_ascii));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// also of each element
|
||||||
|
for (const ordered_json_view e : d.root())
|
||||||
|
{
|
||||||
|
CHECK(e.dump() == e.materialize().dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("strings")
|
||||||
|
{
|
||||||
|
const std::string text = R"(["plain", "\u0000\u0001\u001f\u007f\u0080é€😀", "\"\\\/\b\f\n\r\t", "aéあ😀b", "long text beyond the eight bytes of a word \n with an escape in the middle"])";
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true));
|
||||||
|
const std::string key_text = R"({"é\n": {"\"": [], "": {}}})";
|
||||||
|
const ordered_json_document keys = ordered_json_document::parse(key_text);
|
||||||
|
const ordered_json key_json = ordered_json::parse(key_text);
|
||||||
|
CHECK(keys.root().dump(2, ' ', true) == key_json.dump(2, ' ', true));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("numbers")
|
||||||
|
{
|
||||||
|
const std::string text = "[1.50, 1E2, -0, -0.0, 123456789012345678901234567890, 18446744073709551615, -9223372036854775808, 0.1, 1e-7, 5e-324]";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
CHECK(d.root().dump() == json::parse(text).dump());
|
||||||
|
CHECK(d.root().dump() == "[1.5,100.0,0,-0.0,1.2345678901234568e+29,18446744073709551615,-9223372036854775808,0.1,1e-07,5e-324]");
|
||||||
|
CHECK(d.root().dump(-1, ' ', false, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
|
||||||
|
|
||||||
|
// random doubles, written as parse() and dump() would
|
||||||
|
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
std::string many = "[";
|
||||||
|
for (int i = 0; i < 5000; ++i)
|
||||||
|
{
|
||||||
|
const std::uint64_t bits = rng();
|
||||||
|
double x = 0;
|
||||||
|
std::memcpy(&x, &bits, sizeof(x));
|
||||||
|
if (std::isfinite(x))
|
||||||
|
{
|
||||||
|
many += (many.size() > 1 ? "," : "") + json(x).dump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
many += ']';
|
||||||
|
const json_document many_document = json_document::parse(many);
|
||||||
|
CHECK(many_document.root().dump() == json::parse(many).dump());
|
||||||
|
|
||||||
|
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
const nlohmann::basic_json_document<json_float> float_document = nlohmann::basic_json_document<json_float>::parse("[0.1, 1.5e10, 3.4028235e38]");
|
||||||
|
CHECK(float_document.root().dump() == json_float::parse("[0.1, 1.5e10, 3.4028235e38]").dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("members in document order, all of them")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
||||||
|
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
||||||
|
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
||||||
|
// dump() writes all members, though the lookup finds the first
|
||||||
|
CHECK(d.root()["b"].dump() == "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
const json_document deep_document = json_document::parse(deep);
|
||||||
|
CHECK(deep_document.root().dump() == deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the output buffer of a small value is small")
|
||||||
|
{
|
||||||
|
// an escaped key after the value: its node lies in the arena, so the
|
||||||
|
// source extent of the value cannot be read from the next node
|
||||||
|
const std::string big(100000, 'a');
|
||||||
|
const std::string text = R"({"small":1,"list":[1,2,3],"k\n":")" + big + R"("})";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const auto small = d.root()["small"].dump();
|
||||||
|
CHECK(small == "1");
|
||||||
|
CHECK(small.capacity() < 4096);
|
||||||
|
const auto list = d.root()["list"].dump();
|
||||||
|
CHECK(list == "[1,2,3]");
|
||||||
|
CHECK(list.capacity() < 4096);
|
||||||
|
CHECK(d.root()["list"].dump(2).capacity() < 4096);
|
||||||
|
|
||||||
|
// the whole document and the large value are unaffected
|
||||||
|
CHECK(d.root().dump() == ordered_json::parse(text).dump());
|
||||||
|
CHECK(d.root()["k\n"].dump() == "\"" + big + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("output that outgrows the estimate")
|
||||||
|
{
|
||||||
|
// ensure_ascii writes six bytes for each two-byte character
|
||||||
|
std::string chars;
|
||||||
|
for (int i = 0; i < 5000; ++i)
|
||||||
|
{
|
||||||
|
chars += "\xC3\xA9";
|
||||||
|
}
|
||||||
|
const json_document d = json_document::parse(R"({"a":")" + chars + R"(","k\n":1})");
|
||||||
|
const json expected = json::parse("\"" + chars + "\"");
|
||||||
|
CHECK(d.root()["a"].dump(-1, ' ', true) == expected.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("streams and discarded views")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
||||||
|
std::ostringstream compact;
|
||||||
|
compact << d.root();
|
||||||
|
CHECK(compact.str() == R"({"a":[1,2]})");
|
||||||
|
std::ostringstream pretty;
|
||||||
|
pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"];
|
||||||
|
CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]");
|
||||||
|
CHECK(json_view().dump() == json(json::value_t::discarded).dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view comparison")
|
||||||
|
{
|
||||||
|
SECTION("equality of the values parse() produces")
|
||||||
|
{
|
||||||
|
generator g;
|
||||||
|
std::vector<std::string> texts;
|
||||||
|
for (int i = 0; i < 600; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
g.value(text, 0);
|
||||||
|
texts.push_back(text);
|
||||||
|
// the same value written differently: sorted keys, canonical numbers
|
||||||
|
texts.push_back(json::parse(text).dump(1));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = i; k < i + 3; ++k)
|
||||||
|
{
|
||||||
|
CAPTURE(texts[i])
|
||||||
|
CAPTURE(texts[k])
|
||||||
|
const json_document a = json_document::parse(texts[i]);
|
||||||
|
const json_document b = json_document::parse(texts[k]);
|
||||||
|
const json ja = json::parse(texts[i]);
|
||||||
|
const json jb = json::parse(texts[k]);
|
||||||
|
CHECK((a.root() == b.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != b.root()) == (ja != jb));
|
||||||
|
CHECK((a.root() == jb) == (ja == jb));
|
||||||
|
CHECK((jb == a.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != jb) == (ja != jb));
|
||||||
|
CHECK((jb != a.root()) == (ja != jb));
|
||||||
|
|
||||||
|
// ordered_json compares members in order
|
||||||
|
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
||||||
|
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
||||||
|
const ordered_json oja = ordered_json::parse(texts[i]);
|
||||||
|
const ordered_json ojb = ordered_json::parse(texts[k]);
|
||||||
|
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
||||||
|
CHECK((oa.root() == ojb) == (oja == ojb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("numbers, duplicate keys, member order")
|
||||||
|
{
|
||||||
|
const auto same = [](const char* x, const char* y)
|
||||||
|
{
|
||||||
|
const json_document dx = json_document::parse(x);
|
||||||
|
const json_document dy = json_document::parse(y);
|
||||||
|
return dx.root() == dy.root();
|
||||||
|
};
|
||||||
|
CHECK(same("1", "1.0"));
|
||||||
|
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
||||||
|
CHECK(!same("1", "1.5"));
|
||||||
|
CHECK(same("18446744073709551615", "18446744073709551615"));
|
||||||
|
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
||||||
|
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
||||||
|
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
||||||
|
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
||||||
|
CHECK(!same("[1, 2]", "[2, 1]"));
|
||||||
|
CHECK(!same("\"a\"", "\"b\""));
|
||||||
|
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
||||||
|
CHECK(!same("null", "false"));
|
||||||
|
CHECK(!same("[]", "{}"));
|
||||||
|
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
|
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
||||||
|
CHECK(dup.root() == last.root());
|
||||||
|
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
||||||
|
CHECK(dup.root()["a"].materialize() == 1);
|
||||||
|
CHECK(dup.root()["a"] != last.root()["a"]);
|
||||||
|
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
||||||
|
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
||||||
|
CHECK(ab.root() != ba.root());
|
||||||
|
|
||||||
|
// discarded values compare as basic_json's do
|
||||||
|
const json discarded(json::value_t::discarded);
|
||||||
|
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
||||||
|
CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty)
|
||||||
|
const json_document null_document = json_document::parse("null");
|
||||||
|
CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty)
|
||||||
|
CHECK(!(null_document.root() == discarded));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
const json_document a = json_document::parse(deep);
|
||||||
|
const json_document b = json_document::parse(deep);
|
||||||
|
CHECK(a.root() == b.root());
|
||||||
|
CHECK(a.root() == json::parse(deep));
|
||||||
|
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
||||||
|
const json_document c = json_document::parse(other);
|
||||||
|
CHECK(a.root() != c.root());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user