mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 19:20:30 +00:00
Test json_document and json_view
unit-json_view.cpp: type queries, size, and empty against basic_json; materialize() against parse() (json and ordered_json, generated documents, duplicate keys, 100,000 levels of nesting, parent pointers with JSON_DIAGNOSTICS); parse errors and their messages equal to parse() for malformed inputs and all option combinations; NUL and BOM; borrowed and owned inputs (strings, C strings, literals, vectors, string_view, streams, wide strings, parse_copy, and iterator ranges over pointers, vectors, strings, and lists); reuse with read(); moves; shrink_to_fit() of the index and of the decoded strings; source offsets. unit-json_view_macros.cpp includes the header without JSON_TEST_KEEP_MACROS, as users do: the view must not depend on the macros json.hpp undefines, and must not leak its own. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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;
|
||||
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// the exception parse() throws for a text, or "" if it accepts it
|
||||
std::string parse_exception(const std::string& text, bool comments = false, bool trailing_commas = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
const json j = json::parse(text, nullptr, true, comments, trailing_commas);
|
||||
static_cast<void>(j);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string view_exception(const std::string& text, bool comments = false, bool trailing_commas = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
const json_document d = json_document::parse(text, true, comments, trailing_commas);
|
||||
static_cast<void>(d);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// a small deterministic generator of documents
|
||||
struct generator
|
||||
{
|
||||
std::mt19937 rng{5295};
|
||||
|
||||
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"};
|
||||
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",
|
||||
"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
|
||||
|
||||
TEST_CASE("json_view")
|
||||
{
|
||||
SECTION("types and capacity")
|
||||
{
|
||||
for (const char* text :
|
||||
{"null", "true", "false", "0", "-1", "18446744073709551615", "-9223372036854775808", "18446744073709551616", "1.5",
|
||||
"\"\"", "\"text\"", "[]", "[1,2,3]", "{}", "{\"a\":1,\"b\":2}"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
const json j = json::parse(text);
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
CHECK(v.type() == j.type());
|
||||
CHECK(v.is_null() == j.is_null());
|
||||
CHECK(v.is_boolean() == j.is_boolean());
|
||||
CHECK(v.is_number() == j.is_number());
|
||||
CHECK(v.is_number_integer() == j.is_number_integer());
|
||||
CHECK(v.is_number_unsigned() == j.is_number_unsigned());
|
||||
CHECK(v.is_number_float() == j.is_number_float());
|
||||
CHECK(v.is_string() == j.is_string());
|
||||
CHECK(v.is_array() == j.is_array());
|
||||
CHECK(v.is_object() == j.is_object());
|
||||
CHECK(v.is_binary() == j.is_binary());
|
||||
CHECK(v.is_primitive() == j.is_primitive());
|
||||
CHECK(v.is_structured() == j.is_structured());
|
||||
CHECK(!v.is_discarded());
|
||||
CHECK(static_cast<bool>(v));
|
||||
CHECK(v.size() == j.size());
|
||||
CHECK(v.empty() == j.empty());
|
||||
CHECK(v.materialize() == j);
|
||||
}
|
||||
|
||||
const json_view invalid;
|
||||
CHECK(invalid.is_discarded());
|
||||
CHECK(!static_cast<bool>(invalid));
|
||||
CHECK(invalid.type() == json::value_t::discarded);
|
||||
CHECK(invalid.size() == 0);
|
||||
CHECK(invalid.empty());
|
||||
CHECK(invalid.materialize().is_discarded());
|
||||
CHECK(invalid.source_offset() == static_cast<std::size_t>(-1));
|
||||
}
|
||||
|
||||
SECTION("materialize")
|
||||
{
|
||||
generator g;
|
||||
for (int i = 0; i < 2000; ++i)
|
||||
{
|
||||
std::string text;
|
||||
g.value(text, 0);
|
||||
CAPTURE(text);
|
||||
CHECK(json_document::parse(text).root().materialize() == json::parse(text));
|
||||
// member order as ordered_json::parse keeps it
|
||||
CHECK(ordered_json_document::parse(text).root().materialize().dump() == ordered_json::parse(text).dump());
|
||||
}
|
||||
// duplicate keys: the last value, at the position of the first key
|
||||
CHECK(json_document::parse(R"({"a":1,"b":2,"a":3})").root().materialize() == json::parse(R"({"a":1,"b":2,"a":3})"));
|
||||
CHECK(ordered_json_document::parse(R"({"a":1,"b":2,"a":3})").root().materialize().dump() == R"({"a":3,"b":2})");
|
||||
// very deep nesting (iterative, as parse())
|
||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||
CHECK(json_document::parse(deep).root().materialize() == json::parse(deep));
|
||||
#if JSON_DIAGNOSTICS
|
||||
// the parents are set, so errors name the path
|
||||
const json m = json_document::parse(R"({"a":{"b":[1]}})").root().materialize();
|
||||
CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), "[json.exception.type_error.304] (/a/b/0) cannot use at() with number", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("parse errors are those of parse()")
|
||||
{
|
||||
for (const char* text :
|
||||
{
|
||||
"", " ", "[", "]", "{", "[1,]", "{\"a\":1,}", "[1 2]", "{\"a\" 1}", "{1:2}", "tru", "nul", "fals", "truex", "-", "01", "1.", ".5", "1e",
|
||||
"\"", "\"abc", "\"\\x\"", "\"\\u12\"", "\"\\ud800\"", "\"\\udc00\"", "\"\x01\"", "\"\xff\"", "\"\xc3\"", "[1]x", "/", "/*", "[\n 1,\n x\n]",
|
||||
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
const std::string expected = parse_exception(text);
|
||||
REQUIRE(!expected.empty());
|
||||
CHECK(view_exception(text) == expected);
|
||||
CHECK(!json_document::accept(text));
|
||||
const json_document d = json_document::parse(text, false);
|
||||
CHECK(d.is_discarded());
|
||||
CHECK(d.root().is_discarded());
|
||||
CHECK(d.node_count() == 0);
|
||||
}
|
||||
// the exception types
|
||||
CHECK_THROWS_AS(json_document::parse("[1,"), json::parse_error&);
|
||||
CHECK_THROWS_AS(json_document::parse("1e400"), json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("parse options")
|
||||
{
|
||||
for (const char* text :
|
||||
{"// c\n[1]", "[1, /* c */ 2]", "[1,]", "{\"a\":1,}", "[1,/* c */]", "/", "/* ", "[1,,]"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
for (int options = 0; options < 4; ++options)
|
||||
{
|
||||
const bool comments = (options & 1) != 0;
|
||||
const bool trailing_commas = (options & 2) != 0;
|
||||
CHECK(json_document::accept(text, comments, trailing_commas) == json::accept(text, comments, trailing_commas));
|
||||
CHECK(view_exception(text, comments, trailing_commas) == parse_exception(text, comments, trailing_commas));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("NUL and BOM")
|
||||
{
|
||||
const std::string with_nul("[1]\0garbage", 11);
|
||||
CHECK(json_document::accept(with_nul) == json::accept(with_nul));
|
||||
const std::string nul_in_comment("[1, // c\0\n2]", 12);
|
||||
CHECK(json_document::accept(nul_in_comment, true) == json::accept(nul_in_comment, true));
|
||||
CHECK(json_document::parse("\xEF\xBB\xBF[1]").root().materialize() == json::parse("\xEF\xBB\xBF[1]"));
|
||||
CHECK(view_exception("\xEF\xBB") == parse_exception("\xEF\xBB"));
|
||||
}
|
||||
|
||||
SECTION("inputs")
|
||||
{
|
||||
const std::string text = R"([1, "two", {"three": 3.5}])";
|
||||
const json expected = json::parse(text);
|
||||
|
||||
// borrowed: the text must outlive the document
|
||||
const json_document borrowed = json_document::parse(text);
|
||||
CHECK(!borrowed.owns_source());
|
||||
CHECK(borrowed.source().data() == text.data());
|
||||
CHECK(borrowed.root().materialize() == expected);
|
||||
CHECK(json_document::parse(text.c_str()).root().materialize() == expected);
|
||||
CHECK(json_document::parse(R"([1, "two", {"three": 3.5}])").root().materialize() == expected);
|
||||
CHECK(json_document::parse(text.data(), text.data() + text.size()).root().materialize() == expected);
|
||||
const std::vector<char> chars(text.begin(), text.end());
|
||||
CHECK(!json_document::parse(chars).owns_source());
|
||||
CHECK(json_document::parse(chars).root().materialize() == expected);
|
||||
const std::vector<std::uint8_t> bytes(text.begin(), text.end());
|
||||
CHECK(json_document::parse(bytes).root().materialize() == expected);
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
const std::string_view sv = text;
|
||||
CHECK(!json_document::parse(sv).owns_source());
|
||||
CHECK(json_document::parse(sv).root().materialize() == expected);
|
||||
#endif
|
||||
|
||||
// owned
|
||||
std::string moved = text;
|
||||
const json_document from_rvalue = json_document::parse(std::move(moved));
|
||||
CHECK(from_rvalue.owns_source());
|
||||
CHECK(from_rvalue.root().materialize() == expected);
|
||||
CHECK(json_document::parse(std::vector<char>(text.begin(), text.end())).owns_source());
|
||||
CHECK(json_document::parse_copy(text).owns_source());
|
||||
CHECK(json_document::parse_copy(text).root().materialize() == expected);
|
||||
std::istringstream stream(text);
|
||||
const json_document from_stream = json_document::parse(stream);
|
||||
CHECK(from_stream.owns_source());
|
||||
CHECK(from_stream.root().materialize() == expected);
|
||||
const std::list<char> list(text.begin(), text.end());
|
||||
CHECK(json_document::parse(list.begin(), list.end()).owns_source());
|
||||
CHECK(json_document::parse(list.begin(), list.end()).root().materialize() == expected);
|
||||
|
||||
// iterator pairs: pointers are borrowed, and so are contiguous library
|
||||
// iterators where the input adapter detects them (C++20)
|
||||
CHECK(!json_document::parse(text.data(), text.data() + text.size()).owns_source());
|
||||
const bool contiguous = nlohmann::detail::iterator_input_adapter<std::vector<char>::const_iterator>::supports_bulk_scan;
|
||||
const json_document from_iterators = json_document::parse(chars.cbegin(), chars.cend());
|
||||
CHECK(from_iterators.owns_source() != contiguous);
|
||||
CHECK((from_iterators.source().data() == chars.data()) == contiguous);
|
||||
CHECK(from_iterators.root().materialize() == expected);
|
||||
const std::string padded = "x" + text + "x";
|
||||
CHECK(json_document::parse(padded.begin() + 1, padded.end() - 1).root().materialize() == expected);
|
||||
CHECK(json_document::parse(chars.cbegin(), chars.cbegin(), false).is_discarded());
|
||||
const std::wstring wide = L"[\"\u00e4\u20ac\", 1]";
|
||||
CHECK(json_document::parse(wide).root().materialize() == json::parse(wide));
|
||||
CHECK(json_document::parse(static_cast<const char*>(nullptr), false).is_discarded());
|
||||
CHECK(json_document::parse("", false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("document lifetime and reuse")
|
||||
{
|
||||
json_document d;
|
||||
CHECK(d.is_discarded());
|
||||
CHECK(d.root().is_discarded());
|
||||
CHECK(d.node_count() == 0);
|
||||
CHECK(d.memory_usage() == 0);
|
||||
CHECK(d.source().empty());
|
||||
|
||||
const std::string a = "[1,2,3]";
|
||||
const std::string b = "{\"x\":[true]}";
|
||||
d.read(a);
|
||||
CHECK(d.node_count() == 4);
|
||||
CHECK(d.root().materialize() == json::parse(a));
|
||||
d.read(b);
|
||||
CHECK(d.node_count() == 4);
|
||||
CHECK(d.root().materialize() == json::parse(b));
|
||||
d.read("[", false);
|
||||
CHECK(d.is_discarded());
|
||||
|
||||
// views stay valid when the document moves
|
||||
json_document first = json_document::parse(a);
|
||||
const json_view root = first.root();
|
||||
const json_document second = std::move(first);
|
||||
CHECK(root.materialize() == json::parse(a));
|
||||
CHECK(second.root().materialize() == json::parse(a));
|
||||
}
|
||||
|
||||
SECTION("memory")
|
||||
{
|
||||
std::string big = "[";
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
big += (i != 0 ? ",\"" : "\"") + std::to_string(i) + "\"";
|
||||
}
|
||||
big += "]";
|
||||
json_document d = json_document::parse(big);
|
||||
CHECK(d.node_count() == 10001);
|
||||
const std::size_t before = d.memory_usage();
|
||||
d.shrink_to_fit(); // (invalidates views, like std::vector::shrink_to_fit)
|
||||
CHECK(d.memory_usage() <= before);
|
||||
CHECK(d.node_count() == 10001);
|
||||
CHECK(d.root().materialize() == json::parse(big));
|
||||
CHECK(d.root().size() == 10000);
|
||||
d.shrink_to_fit(); // nothing left to release
|
||||
|
||||
// after reading a smaller text, both the index and the decoded strings
|
||||
// shrink, and the strings are found in their new place
|
||||
std::string escaped = "[";
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
escaped += (i != 0 ? ",\"a\\n" : "\"a\\n") + std::to_string(i) + "\"";
|
||||
}
|
||||
escaped += "]";
|
||||
json_document reused = json_document::parse(escaped);
|
||||
const std::string smaller = "[\"x\\ty\", [true, \"\\u00e4\"]]";
|
||||
reused.read(smaller);
|
||||
const std::size_t grown = reused.memory_usage();
|
||||
reused.shrink_to_fit();
|
||||
CHECK(reused.memory_usage() < grown);
|
||||
CHECK(reused.root().materialize() == json::parse(smaller));
|
||||
|
||||
// a small document stays in the storage block of the header
|
||||
json_document small = json_document::parse("[1,[2,3],{\"a\":\"b\\n\"}]");
|
||||
small.shrink_to_fit();
|
||||
CHECK(small.root().materialize() == json::parse("[1,[2,3],{\"a\":\"b\\n\"}]"));
|
||||
}
|
||||
|
||||
SECTION("source offsets")
|
||||
{
|
||||
const std::string text = R"( {"key": "value", "escaped": "a\nb", "n": 42})";
|
||||
const json_document d = json_document::parse(text);
|
||||
CHECK(d.root().source_offset() == 2);
|
||||
// (element access comes with a later change; the offsets of the
|
||||
// string nodes are checked through materialize() above)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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"
|
||||
|
||||
// All other tests keep the library's macros (JSON_TEST_KEEP_MACROS). This one
|
||||
// includes json_view.hpp as users do, so that json.hpp undefines its macros
|
||||
// (JSON_HAS_CPP_17, JSON_STRICT_NUL_HANDLING, ...) before the view is compiled.
|
||||
#undef JSON_TEST_KEEP_MACROS
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||
#include <string_view>
|
||||
#define JSON_VIEW_TEST_HAS_STRING_VIEW 1
|
||||
#else
|
||||
#define JSON_VIEW_TEST_HAS_STRING_VIEW 0
|
||||
#endif
|
||||
|
||||
// the view's own macros do not leak
|
||||
#if defined(NLOHMANN_VIEW_LIKELY) || defined(NLOHMANN_VIEW_UNLIKELY) || defined(NLOHMANN_VIEW_ALWAYS_INLINE) || defined(NLOHMANN_VIEW_NOINLINE) \
|
||||
|| defined(NLOHMANN_VIEW_NODISCARD) || defined(NLOHMANN_VIEW_THROW) || defined(NLOHMANN_VIEW_HAS_CPP_17) || defined(NLOHMANN_VIEW_LITTLE_ENDIAN) \
|
||||
|| defined(NLOHMANN_VIEW_REPEAT16)
|
||||
#error "json_view.hpp leaks a macro"
|
||||
#endif
|
||||
|
||||
TEST_CASE("json_view without the library's macros")
|
||||
{
|
||||
// (this file also gets C++17 builds: it mentions JSON_HAS_CPP_17)
|
||||
#if JSON_VIEW_TEST_HAS_STRING_VIEW
|
||||
CHECK(std::is_same<nlohmann::json_view::string_view_t, std::string_view>::value);
|
||||
#endif
|
||||
const std::string text = "[1, 2.5, \"x\"]";
|
||||
const nlohmann::json_document d = nlohmann::json_document::parse(text);
|
||||
CHECK(d.root().size() == 3);
|
||||
CHECK(d.root().materialize() == nlohmann::json::parse(text));
|
||||
// the NUL handling of the library's configuration
|
||||
const std::string with_nul("[1]\0x", 5);
|
||||
CHECK(nlohmann::json_document::accept(with_nul) == nlohmann::json::accept(with_nul));
|
||||
}
|
||||
Reference in New Issue
Block a user