mirror of
https://github.com/nlohmann/json.git
synced 2026-11-02 10:55:35 +00:00
Tests: cover the remaining lines of the library headers
Add tests for the lines of include/nlohmann that were not covered: the binary readers and writers, the lexer and locale paths, the json_view modules, the custom number, key and array types, json_pointer, json_patch, diff and the iterators. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
33 files changed
+1627
-1
No files matched your search
@@ -14,7 +14,9 @@ using nlohmann::json;
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -767,3 +769,69 @@ TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
|
||||
// an adapter whose every write fails
|
||||
class failing_adapter : public nlohmann::detail::output_adapter_protocol<char>
|
||||
{
|
||||
public:
|
||||
[[noreturn]] void write_character(char /*c*/) override
|
||||
{
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
|
||||
[[noreturn]] void write_characters(const char* /*s*/, std::size_t /*length*/) override
|
||||
{
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("output adapters")
|
||||
{
|
||||
SECTION("writing a single character")
|
||||
{
|
||||
// the writers pass whole buffers on, but an adapter takes single
|
||||
// characters as well
|
||||
std::vector<char> as_vector;
|
||||
nlohmann::detail::output_vector_adapter<char> vector_adapter(as_vector);
|
||||
vector_adapter.write_character('a');
|
||||
vector_adapter.write_character('b');
|
||||
CHECK(as_vector == std::vector<char>({'a', 'b'}));
|
||||
|
||||
std::ostringstream as_stream;
|
||||
nlohmann::detail::output_stream_adapter<char> stream_adapter(as_stream);
|
||||
stream_adapter.write_character('c');
|
||||
stream_adapter.write_character('d');
|
||||
CHECK(as_stream.str() == "cd");
|
||||
|
||||
std::string as_string;
|
||||
nlohmann::detail::output_string_adapter<char> string_adapter(as_string);
|
||||
string_adapter.write_character('e');
|
||||
string_adapter.write_character('f');
|
||||
CHECK(as_string == "ef");
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("a sink that is destroyed with characters left over")
|
||||
{
|
||||
// the destructor passes the pending characters on, as a write that
|
||||
// threw leaves them there; the adapter failing then must not escape
|
||||
{
|
||||
nlohmann::detail::output_adapter_sink<char> sink(std::make_shared<failing_adapter>());
|
||||
sink.write_character('x');
|
||||
}
|
||||
|
||||
// while flush() reports the failure
|
||||
nlohmann::detail::output_adapter_sink<char> sink(std::make_shared<failing_adapter>());
|
||||
sink.write_character('x');
|
||||
CHECK_THROWS_AS(sink.flush(), std::runtime_error&);
|
||||
// (the character counts as written, so there is nothing left for the destructor)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -2312,6 +2312,23 @@ TEST_CASE("BJData")
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("every event of an ndarray of chars")
|
||||
{
|
||||
// the elements of an ndarray of 'C' are read one by one
|
||||
std::vector<uint8_t> const v = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 'd'};
|
||||
// start_object, key, string, key, start_array, 2 numbers, end_array,
|
||||
// key, start_array, 4 chars, end_array, end_object
|
||||
constexpr int events = 16;
|
||||
for (int allowed = 0; allowed < events; ++allowed)
|
||||
{
|
||||
CAPTURE(allowed)
|
||||
SaxCountdown scp(allowed);
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
SaxCountdown scp(events);
|
||||
CHECK(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("start_array() in ndarray _ArrayData_")
|
||||
{
|
||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1};
|
||||
@@ -3095,6 +3112,15 @@ TEST_CASE("BJData")
|
||||
json _;
|
||||
CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82");
|
||||
}
|
||||
|
||||
SECTION("ndarray of chars cut off by the end of the input")
|
||||
{
|
||||
// 2x2 chars announced, three present
|
||||
std::vector<uint8_t> const v = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c'};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 16: syntax error while parsing BJData char: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("byte")
|
||||
|
||||
@@ -329,6 +329,9 @@ TEST_CASE("BON8")
|
||||
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
||||
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
||||
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
||||
// an array that starts with a number: -1, 0, and 1 have bytes of their own
|
||||
check_bon8({-1.0, 0.0, 1.0}, {0x83, 0xFB, 0xFC, 0xFD});
|
||||
check_bon8({1.0, -1.0, -0.0}, {0x83, 0xFD, 0xFB, 0x8E, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
||||
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
||||
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
||||
|
||||
@@ -81,8 +81,20 @@ class huge_string_t : public std::string
|
||||
return result;
|
||||
}
|
||||
|
||||
// returns a copy of @a s whose size() pretends to be @a n
|
||||
static huge_string_t as_size(const std::string& s, const size_type n)
|
||||
{
|
||||
huge_string_t result(s);
|
||||
result.fake_size = n;
|
||||
return result;
|
||||
}
|
||||
|
||||
size_type size() const noexcept
|
||||
{
|
||||
if (fake_size != 0)
|
||||
{
|
||||
return fake_size;
|
||||
}
|
||||
if (pretend_huge)
|
||||
{
|
||||
// one byte more than the BSON length field can represent
|
||||
@@ -93,6 +105,7 @@ class huge_string_t : public std::string
|
||||
|
||||
private:
|
||||
bool pretend_huge = false;
|
||||
size_type fake_size = 0;
|
||||
};
|
||||
|
||||
using huge_string_json = nlohmann::json::with_string_t<huge_string_t>;
|
||||
@@ -267,6 +280,17 @@ TEST_CASE("BSON")
|
||||
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("an array element that no longer fits the document")
|
||||
{
|
||||
// the string fits the length field, but not the document that
|
||||
// holds it; nothing of it is written
|
||||
const auto limit = static_cast<std::string::size_type>((std::numeric_limits<std::int32_t>::max)());
|
||||
huge_string_json j;
|
||||
j["a"] = huge_string_json::array({huge_string_t::as_size("value", limit - 8)});
|
||||
|
||||
CHECK_THROWS_AS(huge_string_json::to_bson(j), huge_string_json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("document")
|
||||
{
|
||||
// an oversized string nested one level deep makes the
|
||||
@@ -898,6 +922,38 @@ TEST_CASE("BSON")
|
||||
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("binary member with subtype 0x02 (old binary) with a truncated inner length")
|
||||
{
|
||||
// the inner length field is cut off by the end of the input
|
||||
std::vector<std::uint8_t> const input =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x05, 'a', 0x00, // entry: binary
|
||||
0x08, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||
0x02, // "old binary" subtype
|
||||
0x01 // first byte of the inner length
|
||||
};
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("string member that is cut off by the end of the input")
|
||||
{
|
||||
std::vector<std::uint8_t> const input =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x02, 'a', 0x00, // entry: string
|
||||
0x0A, 0x00, 0x00, 0x00, // size of the string (little endian)
|
||||
'b', 'c' // the string ends early
|
||||
};
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BSON string: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("Some more complex document")
|
||||
{
|
||||
json const j =
|
||||
@@ -2136,6 +2192,25 @@ value = depth % 2 == 0 ? json{{"a", std::move(value)}, {"b", {1, "x"}}} :
|
||||
CHECK(output.empty());
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("U+0000 in a long key is found at any position")
|
||||
{
|
||||
// the key is searched eight characters at a time
|
||||
for (const std::string& key :
|
||||
{
|
||||
std::string("abc\0defghijkl", 12), std::string("abcdefgh\0ijkl", 13), std::string("abcdefghijklmnop\0", 17), std::string("\0abcdefghijkl", 13),
|
||||
std::string("abcdefghijklmno\0", 16)
|
||||
})
|
||||
{
|
||||
CAPTURE(key)
|
||||
const json value = json::object({{key, 1}});
|
||||
std::vector<std::uint8_t> output;
|
||||
CHECK_THROWS_AS(json::to_bson(value, output), json::out_of_range&);
|
||||
CHECK(output.empty());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("a binary subtype that doesn't fit a byte is rejected before anything is written (#5675)")
|
||||
{
|
||||
// the offending value is nested, so this also covers that the check
|
||||
|
||||
@@ -1809,6 +1809,30 @@ TEST_CASE("CBOR")
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("tag in front of a key that is cut off by the end of the input")
|
||||
{
|
||||
// 0xD8: the tag number is in the next byte, which is missing
|
||||
const std::vector<std::uint8_t> input = {0xA1, 0xD8};
|
||||
json _;
|
||||
for (const auto handler :
|
||||
{
|
||||
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
|
||||
})
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input, true, true, handler), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false, handler).is_discarded());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("chunk of an indefinite-length string that is cut off by the end of the input")
|
||||
{
|
||||
// the second byte announces a chunk of three bytes, but two follow
|
||||
const std::vector<std::uint8_t> input = {0x7F, 0x63, 'a', 'b'};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("tag in front of a key")
|
||||
{
|
||||
const auto ignore = json::cbor_tag_handler_t::ignore;
|
||||
|
||||
@@ -2092,6 +2092,12 @@ TEST_CASE("UTF-8 validators accept the same sequences")
|
||||
}
|
||||
};
|
||||
|
||||
// an ASCII byte is a sequence of its own
|
||||
for (unsigned lead = 0; lead < 0x80; ++lead)
|
||||
{
|
||||
CHECK(nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead)).length == 1);
|
||||
}
|
||||
|
||||
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
||||
{
|
||||
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
|
||||
@@ -2157,3 +2163,72 @@ TEST_CASE("UTF-8 validators accept the same sequences")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("float_bigint")
|
||||
{
|
||||
// the big integer of the exact comparison of a float token with the
|
||||
// midpoint between two floats (digit_comparison())
|
||||
using nlohmann::detail::float_bigint;
|
||||
|
||||
SECTION("zero")
|
||||
{
|
||||
// shifting zero keeps it zero, whatever the distance
|
||||
float_bigint zero(0);
|
||||
zero.shift_left(0);
|
||||
zero.shift_left(5);
|
||||
zero.shift_left(64);
|
||||
CHECK(zero.compare(float_bigint(0)) == 0);
|
||||
CHECK(zero.compare(float_bigint(1)) == -1);
|
||||
CHECK(float_bigint(1).compare(zero) == 1);
|
||||
}
|
||||
|
||||
SECTION("compare numbers of different lengths")
|
||||
{
|
||||
// the number with more 32-bit limbs is the greater one
|
||||
const float_bigint small(0xFFFFFFFFu);
|
||||
const float_bigint large(std::uint64_t{1} << 32u);
|
||||
CHECK(small.compare(large) == -1);
|
||||
CHECK(large.compare(small) == 1);
|
||||
CHECK(large.compare(large) == 0);
|
||||
|
||||
// a shift can add a limb
|
||||
float_bigint shifted(1);
|
||||
shifted.shift_left(32);
|
||||
CHECK(shifted.compare(large) == 0);
|
||||
shifted.shift_left(1);
|
||||
CHECK(shifted.compare(large) == 1);
|
||||
CHECK(large.compare(shifted) == -1);
|
||||
}
|
||||
|
||||
SECTION("compare numbers of the same length")
|
||||
{
|
||||
CHECK(float_bigint(2).compare(float_bigint(3)) == -1);
|
||||
CHECK(float_bigint(3).compare(float_bigint(2)) == 1);
|
||||
CHECK(float_bigint(3).compare(float_bigint(3)) == 0);
|
||||
CHECK(float_bigint((std::uint64_t{3} << 32u) | 1u).compare(float_bigint((std::uint64_t{3} << 32u) | 2u)) == -1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("serialization of short strings")
|
||||
{
|
||||
// the serializer takes strings of up to 32 characters that need no
|
||||
// escaping by a table lookup of its own where the scan is not vectorized
|
||||
// (this file is built that way, too, with JSON_NO_SIMD)
|
||||
for (const std::string& s :
|
||||
{
|
||||
std::string(), std::string("a"), std::string("hello"), std::string("0123456789abcdef"), std::string("0123456789abcdef0123456789abcdef"),
|
||||
std::string("0123456789abcdef0123456789abcdef0")
|
||||
})
|
||||
{
|
||||
CAPTURE(s)
|
||||
const json j = s;
|
||||
CHECK(j.dump() == "\"" + s + "\"");
|
||||
CHECK(j.dump(-1, ' ', true) == "\"" + s + "\"");
|
||||
CHECK(json({{s, s}}).dump() == "{\"" + s + "\":\"" + s + "\"}");
|
||||
}
|
||||
|
||||
// a character that needs an escape
|
||||
CHECK(json("a\"b").dump() == "\"a\\\"b\"");
|
||||
CHECK(json("caf\xC3\xA9").dump() == "\"caf\xC3\xA9\"");
|
||||
CHECK(json("caf\xC3\xA9").dump(-1, ' ', true) == "\"caf\\u00e9\"");
|
||||
}
|
||||
@@ -210,3 +210,23 @@ TEST_CASE("regression test #5476 - array type without reserve()")
|
||||
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("array type with std::deque nested beyond the recursion limit")
|
||||
{
|
||||
// the binary writers finish a value nested deeper than
|
||||
// recursion_depth_limit() with an explicit stack of frames; with the
|
||||
// iterators of std::deque, these frames are copied and moved through
|
||||
// their own special members
|
||||
deque_json j = 1;
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
deque_json outer = deque_json::array();
|
||||
outer.push_back(std::move(j));
|
||||
j = std::move(outer);
|
||||
}
|
||||
|
||||
// 200 arrays of one element each (0x81) around the integer 1
|
||||
std::vector<std::uint8_t> expected(200, 0x81);
|
||||
expected.push_back(0x01);
|
||||
CHECK(deque_json::to_cbor(j) == expected);
|
||||
}
|
||||
@@ -49,3 +49,24 @@ TEST_CASE("custom object key types: MessagePack (json_full)")
|
||||
{
|
||||
custom_key_test::test_msgpack<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: patch (json_full)")
|
||||
{
|
||||
// the members of a patch are found by lookup, as their keys are not std::string
|
||||
const json_full j = json_full::parse(R"({"a":1,"b":[1,2],"c":{"d":2}})");
|
||||
const json_full patch = json_full::parse(R"([
|
||||
{"op": "add", "path": "/e", "value": 5},
|
||||
{"op": "remove", "path": "/a"},
|
||||
{"op": "replace", "path": "/c/d", "value": 3},
|
||||
{"op": "test", "path": "/c/d", "value": 3},
|
||||
{"op": "move", "from": "/e", "path": "/f"},
|
||||
{"op": "copy", "from": "/f", "path": "/g"},
|
||||
{"op": "add", "path": "/b/-", "value": 7}
|
||||
])");
|
||||
|
||||
CHECK(j.patch(patch).dump() == R"({"b":[1,2,7],"c":{"d":3},"f":5,"g":5})");
|
||||
|
||||
// a patch with a missing member is rejected
|
||||
CHECK_THROWS_AS(j.patch(json_full::parse(R"([{"op": "add", "path": "/e"}])")), json_full::parse_error&);
|
||||
CHECK_THROWS_AS(j.patch(json_full::parse(R"([{"op": "test", "path": "/a", "value": 2}])")), json_full::other_error&);
|
||||
}
|
||||
@@ -337,6 +337,52 @@ TEST_CASE("custom number types: integers beyond 64 bits")
|
||||
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
||||
}
|
||||
|
||||
SECTION("MessagePack integers of every width")
|
||||
{
|
||||
// an integer of a number type wider than 64 bits is written with the
|
||||
// smallest MessagePack integer that holds it, as for 64-bit types
|
||||
const wide_json widths = wide_json::array(
|
||||
{
|
||||
make_int(-33), make_int(-100), make_int(-128), make_int(-129), make_int(-32768), make_int(-32769), make_int(-2147483648LL), make_int(-2147483649LL),
|
||||
make_int(128), make_int(255), make_int(256), make_int(300), make_int(65535), make_int(65536), make_int(70000), make_int(4294967295LL), make_int(4294967296LL),
|
||||
make_uint(128), make_uint(255), make_uint(256), make_uint(300), make_uint(65535), make_uint(65536), make_uint(70000), make_uint(4294967295ULL), make_uint(4294967296ULL)
|
||||
});
|
||||
const nlohmann::json widths64 = nlohmann::json::array(
|
||||
{
|
||||
-33, -100, -128, -129, -32768, -32769, -2147483648LL, -2147483649LL,
|
||||
128, 255, 256, 300, 65535, 65536, 70000, 4294967295LL, 4294967296LL,
|
||||
128u, 255u, 256u, 300u, 65535u, 65536u, 70000u, 4294967295ULL, 4294967296ULL
|
||||
});
|
||||
CHECK(wide_json::to_msgpack(widths) == nlohmann::json::to_msgpack(widths64));
|
||||
|
||||
// as single values, too
|
||||
CHECK(wide_json::to_msgpack(make_int(-100)) == std::vector<std::uint8_t>({0xD0, 0x9C}));
|
||||
CHECK(wide_json::to_msgpack(make_int(300)) == std::vector<std::uint8_t>({0xCD, 0x01, 0x2C}));
|
||||
CHECK(wide_json::to_msgpack(make_int(70000)) == std::vector<std::uint8_t>({0xCE, 0x00, 0x01, 0x11, 0x70}));
|
||||
CHECK(wide_json::to_msgpack(make_uint(300)) == std::vector<std::uint8_t>({0xCD, 0x01, 0x2C}));
|
||||
CHECK(wide_json::to_msgpack(make_uint(70000)) == std::vector<std::uint8_t>({0xCE, 0x00, 0x01, 0x11, 0x70}));
|
||||
}
|
||||
|
||||
SECTION("dump")
|
||||
{
|
||||
// beyond 64 bits, the digits are counted before they are written
|
||||
CHECK(make_int(0).dump() == "0");
|
||||
CHECK(make_int(two_64).dump() == "18446744073709551616");
|
||||
CHECK(make_int(-two_64).dump() == "-18446744073709551616");
|
||||
CHECK(make_int(two_100).dump() == "1267650600228229401496703205376");
|
||||
CHECK(make_int(-two_100).dump() == "-1267650600228229401496703205376");
|
||||
// powers of ten, where the count of digits ends on a single digit or two
|
||||
const int128 ten_20 = static_cast<int128>(100000000000ULL) * static_cast<int128>(1000000000ULL);
|
||||
CHECK(make_int(ten_20).dump() == "100000000000000000000");
|
||||
CHECK(make_int(-ten_20).dump() == "-100000000000000000000");
|
||||
CHECK(make_int(ten_20 * 10).dump() == "1000000000000000000000");
|
||||
CHECK(make_int(ten_20 * 100).dump() == "10000000000000000000000");
|
||||
CHECK(make_int(ten_20 * 1000).dump() == "100000000000000000000000");
|
||||
CHECK(make_uint(static_cast<uint128>(ten_20) * 10u).dump() == "1000000000000000000000");
|
||||
CHECK(make_uint((std::numeric_limits<uint128>::max)()).dump() == "340282366920938463463374607431768211455");
|
||||
CHECK(make_int((std::numeric_limits<int128>::min)()).dump() == "-170141183460469231731687303715884105728");
|
||||
}
|
||||
|
||||
SECTION("BJData ND-array")
|
||||
{
|
||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||
@@ -417,3 +463,23 @@ TEST_CASE("custom number types")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("custom number types: 32-bit integers in the binary formats")
|
||||
{
|
||||
// integers narrower than 64 bits are widened (a signed one with its sign)
|
||||
// before the CBOR, MessagePack, and UBJSON writers pick their encoding
|
||||
using json32 = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>;
|
||||
json32 values = json32::array();
|
||||
values.push_back(std::uint32_t{0});
|
||||
values.push_back(std::uint32_t{200});
|
||||
values.push_back(std::uint32_t{4294967295u});
|
||||
values.push_back(std::int32_t{-1});
|
||||
values.push_back(std::int32_t{-2147483647 - 1});
|
||||
REQUIRE(values[2].is_number_unsigned());
|
||||
REQUIRE(values[4].is_number_integer());
|
||||
|
||||
const nlohmann::json values64 = {0u, 200u, 4294967295u, -1, -2147483648LL};
|
||||
CHECK(json32::to_cbor(values) == nlohmann::json::to_cbor(values64));
|
||||
CHECK(json32::to_msgpack(values) == nlohmann::json::to_msgpack(values64));
|
||||
CHECK(json32::to_ubjson(values) == nlohmann::json::to_ubjson(values64));
|
||||
}
|
||||
@@ -11,8 +11,13 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "custom_object_key_type.hpp"
|
||||
|
||||
@@ -125,3 +130,176 @@ TEST_CASE("MessagePack supports custom object key types nested deeper than the r
|
||||
CHECK(encoded == nlohmann::json::to_msgpack(expected));
|
||||
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// A key type that cannot be constructed from a std::string, so that a value
|
||||
// with such keys cannot be converted from a nlohmann::json by the range
|
||||
// constructor of its object type: the serializer converts the object instead.
|
||||
class strict_key
|
||||
{
|
||||
public:
|
||||
strict_key() = default;
|
||||
|
||||
strict_key(const char* value)
|
||||
: m_value(value)
|
||||
{}
|
||||
|
||||
strict_key(const std::string& /*value*/) = delete;
|
||||
|
||||
// Required by JSON_DIAGNOSTICS, which reads object keys through data()
|
||||
// when building the path of an exception.
|
||||
const char* data() const noexcept
|
||||
{
|
||||
return m_value.data();
|
||||
}
|
||||
|
||||
friend bool operator<(const strict_key& lhs, const strict_key& rhs)
|
||||
{
|
||||
return lhs.m_value < rhs.m_value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_value{};
|
||||
};
|
||||
|
||||
strict_key make_strict_key(const std::string& key)
|
||||
{
|
||||
return strict_key(key.c_str());
|
||||
}
|
||||
|
||||
strict_key make_strict_key(const strict_key& key)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
template<class Key, class Value, class Compare, class Allocator>
|
||||
class strict_object
|
||||
: public std::map <
|
||||
strict_key,
|
||||
Value,
|
||||
std::less<strict_key>, // NOLINT(modernize-use-transparent-functors)
|
||||
typename std::allocator_traits<Allocator>::template rebind_alloc <
|
||||
std::pair<const strict_key, Value >>>
|
||||
{
|
||||
public:
|
||||
strict_object() = default;
|
||||
|
||||
// the range of another object type, whose keys are made into strict_keys
|
||||
template<class Iterator>
|
||||
strict_object(Iterator first, Iterator last)
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
{
|
||||
this->emplace(make_strict_key(first->first), Value(std::move(first->second)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename = void>
|
||||
struct strict_serializer : nlohmann::adl_serializer<T> {};
|
||||
|
||||
using strict_json = nlohmann::basic_json < strict_object, std::vector, std::string, bool, std::int64_t,
|
||||
std::uint64_t, double, std::allocator, strict_serializer, std::vector<std::uint8_t >>;
|
||||
|
||||
// the members of an object are converted one by one, their keys by make_strict_key()
|
||||
template<>
|
||||
struct strict_serializer<nlohmann::json::object_t, void>
|
||||
{
|
||||
static void to_json(strict_json& j, const nlohmann::json::object_t& object)
|
||||
{
|
||||
strict_json::object_t converted;
|
||||
for (const auto& member : object)
|
||||
{
|
||||
converted.emplace(make_strict_key(member.first), strict_json(member.second));
|
||||
}
|
||||
j = strict_json(std::move(converted));
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("converting a value with keys that cannot be made from a std::string nested deeper than the recursion depth limit")
|
||||
{
|
||||
static_assert(!std::is_constructible<strict_key, const std::string&>::value, "the keys cannot be made from a std::string");
|
||||
|
||||
// below detail::recursion_depth_limit(), objects and arrays are converted
|
||||
// by the serializers instead of the range constructors
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
|
||||
|
||||
nlohmann::json value = 1;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
value = nlohmann::json::object({{"a", std::move(value)}, {"b", true}});
|
||||
}
|
||||
else
|
||||
{
|
||||
value = nlohmann::json::array({std::move(value), "sibling"});
|
||||
}
|
||||
}
|
||||
|
||||
const strict_json converted = value;
|
||||
|
||||
// at() needs to make a std::string of a key for its error message
|
||||
const auto member = [](const strict_json & object, const char* name) -> const strict_json &
|
||||
{
|
||||
const auto it = object.find(strict_key(name));
|
||||
REQUIRE(it != object.end());
|
||||
return *it;
|
||||
};
|
||||
|
||||
// walk down the nesting levels without recursion
|
||||
const strict_json* current = &converted;
|
||||
for (std::size_t i = depth; i > 0; --i)
|
||||
{
|
||||
if (i % 2 == 1)
|
||||
{
|
||||
REQUIRE(current->is_object());
|
||||
CHECK(current->size() == 2);
|
||||
CHECK(member(*current, "b").get<bool>());
|
||||
current = &member(*current, "a");
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(current->is_array());
|
||||
CHECK(current->size() == 2);
|
||||
CHECK(current->at(1).get<std::string>() == "sibling");
|
||||
current = ¤t->at(0);
|
||||
}
|
||||
}
|
||||
CHECK(current->get<int>() == 1);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// shorter keys come first
|
||||
struct shortlex_less
|
||||
{
|
||||
bool operator()(const std::string& lhs, const std::string& rhs) const
|
||||
{
|
||||
return lhs.size() != rhs.size() ? lhs.size() < rhs.size() : lhs < rhs;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Key, class Value, class Compare, class Allocator>
|
||||
using shortlex_map = std::map<Key, Value, shortlex_less, Allocator>;
|
||||
|
||||
using shortlex_json = nlohmann::json::with_object_t<shortlex_map>;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("unflatten with an object type that does not sort the keys alphabetically")
|
||||
{
|
||||
// the keys of /a come before and after one of /b: unlike in an object
|
||||
// sorted alphabetically, the values below a prefix are not found one
|
||||
// after the other, so the value of /a must be looked up again
|
||||
shortlex_json flat = shortlex_json::object();
|
||||
flat["/a/p"] = 1;
|
||||
flat["/b/qq"] = 2;
|
||||
flat["/a/pqr"] = 3;
|
||||
REQUIRE(flat.begin().key() == "/a/p");
|
||||
REQUIRE((++flat.begin()).key() == "/b/qq");
|
||||
|
||||
CHECK(flat.unflatten().dump() == R"({"a":{"p":1,"pqr":3},"b":{"qq":2}})");
|
||||
CHECK(flat.unflatten().flatten() == flat);
|
||||
}
|
||||
@@ -1195,6 +1195,28 @@ TEST_CASE("deserialization")
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
TEST_CASE("deserialization from a pointer and a length (deprecated)")
|
||||
{
|
||||
// the unit test build disables -Wdeprecated-declarations (see
|
||||
// cmake/clang_flags.cmake)
|
||||
const std::string text = R"({"foo": [1, 2, 3]})";
|
||||
const std::string invalid = R"({"foo": )";
|
||||
|
||||
SECTION("parse")
|
||||
{
|
||||
const json j = json::parse({text.data(), text.size()});
|
||||
CHECK(j == json({{"foo", {1, 2, 3}}}));
|
||||
}
|
||||
|
||||
SECTION("accept")
|
||||
{
|
||||
CHECK(json::accept({text.data(), text.size()}));
|
||||
CHECK_FALSE(json::accept({invalid.data(), invalid.size()}));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// select the types to test - char8_t is only available since C++20 if and only
|
||||
// if __cpp_char8_t is defined.
|
||||
#define TYPE_LIST(...) __VA_ARGS__
|
||||
|
||||
@@ -868,3 +868,23 @@ TEST_CASE("Parent pointers stay consistent for object types not known to keep th
|
||||
obj["1"].push_back(42);
|
||||
CHECK_THROWS_WITH_AS(obj["1"].at(5), "[json.exception.out_of_range.401] (/1) array index 5 is out of range", relocating_json::out_of_range&);
|
||||
}
|
||||
|
||||
TEST_CASE("patch_inplace() with a patch that is stored inside the patched value")
|
||||
{
|
||||
// the patch is modified-while-iterated if it is not copied first: it is a
|
||||
// descendant of the value it is applied to, which only the parent pointers
|
||||
// (JSON_DIAGNOSTICS) reveal
|
||||
json j =
|
||||
{
|
||||
{"patch", {{{"op", "add"}, {"path", "/x"}, {"value", 1}}, {{"op", "remove"}, {"path", "/patch/0"}}}},
|
||||
{"y", 2}
|
||||
};
|
||||
|
||||
j.patch_inplace(j["patch"]);
|
||||
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(j["x"] == 1);
|
||||
CHECK(j["y"] == 2);
|
||||
CHECK(j["patch"].size() == 1);
|
||||
CHECK(j["patch"][0]["op"] == "remove");
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
@@ -665,6 +667,23 @@ TEST_CASE("iterators 2")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("reverse iterators of a const value")
|
||||
{
|
||||
const json j_array = {1, 2, 3};
|
||||
const json j_object = {{"one", 1}, {"two", 2}};
|
||||
|
||||
// rbegin() and rend() of a const value are crbegin() and crend()
|
||||
CHECK(j_array.rbegin() == j_array.crbegin());
|
||||
CHECK(j_array.rend() == j_array.crend());
|
||||
CHECK(*j_array.rbegin() == 3);
|
||||
CHECK(std::distance(j_array.rbegin(), j_array.rend()) == 3);
|
||||
|
||||
CHECK(j_object.rbegin() == j_object.crbegin());
|
||||
CHECK(j_object.rend() == j_object.crend());
|
||||
CHECK(j_object.rbegin().key() == "two");
|
||||
CHECK(std::distance(j_object.rbegin(), j_object.rend()) == 2);
|
||||
}
|
||||
|
||||
SECTION("reverse iterator arithmetic")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||
|
||||
@@ -14,9 +14,11 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "make_test_data_available.hpp"
|
||||
|
||||
@@ -2453,3 +2455,99 @@ TEST_CASE("JSON patch - diff behavior documented in the API reference")
|
||||
CHECK(json::diff(json(nullptr), json::array()) == R"([{"op": "replace", "path": "", "value": []}])"_json);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - operations that are left to json_pointer")
|
||||
{
|
||||
// the usual operations on a plain pointer are applied without building a
|
||||
// json_pointer; the following are not plain, and some are errors
|
||||
SECTION("a path without a slash")
|
||||
{
|
||||
const json j = {{"a", 1}};
|
||||
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "add", "path": "abc", "value": 1}])"_json), json::parse_error&);
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "remove", "path": "abc"}])"_json), json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("remove with an escaped reference token")
|
||||
{
|
||||
const json j = {{"a/b", 1}, {"c~d", 2}, {"e", 3}};
|
||||
|
||||
CHECK(j.patch(R"([{"op": "remove", "path": "/a~1b"}])"_json) == json({{"c~d", 2}, {"e", 3}}));
|
||||
CHECK(j.patch(R"([{"op": "remove", "path": "/c~0d"}])"_json) == json({{"a/b", 1}, {"e", 3}}));
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "remove", "path": "/missing~1key"}])"_json), json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("a path through a primitive value")
|
||||
{
|
||||
const json j = {{"a", 1}};
|
||||
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "test", "path": "/a/b", "value": 1}])"_json), json::other_error&);
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "replace", "path": "/a/b", "value": 1}])"_json), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - diff of objects with many members")
|
||||
{
|
||||
// more members than diff() keeps without allocating
|
||||
for (const std::size_t members :
|
||||
{
|
||||
16u, 17u, 40u
|
||||
})
|
||||
{
|
||||
CAPTURE(members)
|
||||
|
||||
json source = json::object();
|
||||
json target = json::object();
|
||||
for (std::size_t i = 0; i < members; ++i)
|
||||
{
|
||||
const std::string key = "key" + std::to_string(i);
|
||||
source[key] = i;
|
||||
// every third member differs
|
||||
target[key] = (i % 3 == 0) ? i + 1 : i;
|
||||
}
|
||||
// the same keys, a removed and an added one
|
||||
source["removed"] = true;
|
||||
target["added"] = false;
|
||||
|
||||
const json patch = json::diff(source, target);
|
||||
CHECK(source.patch(patch) == target);
|
||||
CHECK(patch.size() == ((members + 2) / 3) + 2);
|
||||
|
||||
const nlohmann::ordered_json ordered_source = source;
|
||||
const nlohmann::ordered_json ordered_target = target;
|
||||
CHECK(ordered_source.patch(nlohmann::ordered_json::diff(ordered_source, ordered_target)) == ordered_target);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - diff of leaf values below the bound of the recursive descent")
|
||||
{
|
||||
// the values at the innermost level are diffed without recursion: equal
|
||||
// leaves yield nothing, leaves of different types and different leaves
|
||||
// are replaced
|
||||
const std::size_t depth = 200;
|
||||
|
||||
json source = {1, "text", 3, {{"x", 1}, {"y", 2}}, true, nullptr};
|
||||
json target = {1, 2, 4, {{"x", 1}, {"y", 3}}, true, nullptr};
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
source = json::array({std::move(source)});
|
||||
target = json::array({std::move(target)});
|
||||
}
|
||||
|
||||
const json patch = json::diff(source, target);
|
||||
CHECK(patch.size() == 3);
|
||||
CHECK(source.patch(patch) == target);
|
||||
|
||||
// the same with objects as the outer levels
|
||||
json object_source = {{"equal", 1}, {"type", "text"}, {"value", 3}};
|
||||
json object_target = {{"equal", 1}, {"type", 2}, {"value", 4}};
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
object_source = json::object({{"a", std::move(object_source)}});
|
||||
object_target = json::object({{"a", std::move(object_target)}});
|
||||
}
|
||||
|
||||
const json object_patch = json::diff(object_source, object_target);
|
||||
CHECK(object_patch.size() == 2);
|
||||
CHECK(object_source.patch(object_patch) == object_target);
|
||||
}
|
||||
@@ -106,6 +106,12 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_THROWS_WITH_AS(p.pop_back(),
|
||||
"[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
|
||||
|
||||
// the top of a pointer is its first reference token
|
||||
const json::json_pointer nonempty("/foo/bar/0");
|
||||
CHECK(nonempty.top() == json::json_pointer("/foo"));
|
||||
CHECK(nonempty.top().to_string() == "/foo");
|
||||
CHECK(json::json_pointer("/a~1b").top() == json::json_pointer("/a~1b"));
|
||||
|
||||
SECTION("array index error")
|
||||
{
|
||||
json v = {1, 2, 3, 4};
|
||||
@@ -697,6 +703,16 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_THROWS_WITH_AS(j_error.unflatten(),
|
||||
"[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
|
||||
|
||||
// error for an invalid escape sequence in a key
|
||||
CHECK_THROWS_WITH_AS(json({{"/a~2", 1}}).unflatten(),
|
||||
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(json({{"/a/b", 1}, {"/c~", 1}}).unflatten(),
|
||||
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||
|
||||
// error for an element of an array that is also a container
|
||||
CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/0/x", 2}}).unflatten(),
|
||||
"[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
|
||||
|
||||
// explicit roundtrip check
|
||||
CHECK(j.flatten().unflatten() == j);
|
||||
|
||||
|
||||
@@ -31,9 +31,11 @@ using json_view_test::has_duplicate_keys;
|
||||
#include <ios>
|
||||
#include <istream>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <new>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -98,6 +100,37 @@ template<typename View>
|
||||
using bool_conversion_t = decltype(static_cast<bool>(std::declval<View>()));
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// an allocator that never has room
|
||||
template<typename T>
|
||||
struct failing_allocator
|
||||
{
|
||||
using value_type = T;
|
||||
|
||||
failing_allocator() = default;
|
||||
|
||||
template<typename U>
|
||||
explicit failing_allocator(const failing_allocator<U>& /*other*/) noexcept {}
|
||||
|
||||
T* allocate(std::size_t /*n*/) // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
void deallocate(T* /*p*/, std::size_t /*n*/) noexcept {} // NOLINT(readability-convert-member-functions-to-static)
|
||||
|
||||
template<typename U>
|
||||
bool operator==(const failing_allocator<U>& /*other*/) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator!=(const failing_allocator<U>& /*other*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 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)
|
||||
{
|
||||
@@ -235,7 +268,9 @@ TEST_CASE("json_view")
|
||||
{
|
||||
"", " ", "[", "]", "{", "[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]", // NOLINT(modernize-raw-string-literal)
|
||||
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}"
|
||||
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}",
|
||||
// (an error after every kind of value: the error is found by replaying the parser)
|
||||
"[null, true, -1, 1, 1.5, \"s\", {\"a\": 1} 2]", "{\"a\": null, \"b\": -1, \"c\": 1.5, \"d\": \"s\", \"e\": {}, 1}"
|
||||
})
|
||||
{
|
||||
CAPTURE(text)
|
||||
@@ -368,6 +403,10 @@ TEST_CASE("json_view")
|
||||
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());
|
||||
// (an input that is not valid)
|
||||
const std::list<char> bad_list{'[', '1', ','};
|
||||
json_document from_bad_list;
|
||||
CHECK_THROWS_AS(from_bad_list = json_document::parse(bad_list.begin(), bad_list.end()), json::parse_error&);
|
||||
CHECK(materialized<json_document>(list.begin(), list.end()) == expected);
|
||||
|
||||
// iterator pairs: pointers are borrowed, and so are contiguous library
|
||||
@@ -1511,6 +1550,17 @@ TEST_CASE("json_view values")
|
||||
const json_document dup = json_document::parse(R"({"a":1,"a":2})");
|
||||
CHECK((dup.root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));
|
||||
|
||||
// a map of views from something that is not an object
|
||||
using view_map = std::map<std::string, json_view>;
|
||||
const json_document not_an_object = json_document::parse("[1,2]");
|
||||
CHECK_THROWS_WITH_AS(not_an_object.root().get<view_map>(), json_view_test::message("type_error.302", "", 0, 5, "type must be object, but is array").c_str(), json::type_error&);
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// (and one that cannot allocate its nodes)
|
||||
using failing_map = std::map<std::string, json_view, std::less<std::string>, failing_allocator<std::pair<const std::string, json_view>>>;
|
||||
const json_document one_member = json_document::parse(R"({"a":1})");
|
||||
CHECK_THROWS_AS(one_member.root().get<failing_map>(), std::bad_alloc&);
|
||||
#endif
|
||||
|
||||
const json_view invalid{};
|
||||
CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&);
|
||||
CHECK(invalid.get<json>().is_discarded());
|
||||
|
||||
@@ -21,13 +21,22 @@ using json_view_test::generator;
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// These tests were split off unit-json_view_dump.cpp, whose object file got too
|
||||
// large for the MinGW assembler ("file too big"). Like that file, this one is
|
||||
// also built with C++17, as it mentions JSON_HAS_CPP_17.
|
||||
|
||||
namespace
|
||||
{
|
||||
// an object type without a comparison of keys, and with an arbitrary order of members
|
||||
template<typename Key, typename Value, typename /*Compare*/, typename Allocator>
|
||||
using unordered_object = std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>, Allocator>;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("json_view comparison")
|
||||
{
|
||||
SECTION("equality of the values parse() produces")
|
||||
@@ -205,6 +214,59 @@ TEST_CASE("json_view comparison")
|
||||
CHECK(!(null_document.root() == discarded));
|
||||
}
|
||||
|
||||
SECTION("values that differ in their first bytes, or are written differently")
|
||||
{
|
||||
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);
|
||||
const bool equal = dx.root() == dy.root();
|
||||
CHECK(equal == (json::parse(x) == json::parse(y)));
|
||||
CHECK(equal == (dx.root() == json::parse(y)));
|
||||
const nlohmann::json_editable_document ex = nlohmann::json_editable_document::parse(x);
|
||||
CHECK(equal == (ex.root() == dy.root()));
|
||||
return equal;
|
||||
};
|
||||
// booleans
|
||||
CHECK(!same("true", "false"));
|
||||
CHECK(!same("[true]", "[false]"));
|
||||
CHECK(!same(R"({"a":false,"b":[1]})", R"({"a":true,"b":[1]})"));
|
||||
CHECK(same(R"({"a":true,"b":[1]})", R"({"a":true,"b":[1]})"));
|
||||
// strings and keys of equal length that differ in the first eight bytes (they are compared in words)
|
||||
CHECK(!same(R"("abcdefghijklmnopqrstuvwxyz")", R"("Xbcdefghijklmnopqrstuvwxyz")"));
|
||||
CHECK(!same(R"(["abcdefghijklmnopq",1])", R"(["abcdefghXjklmnopq",1])"));
|
||||
CHECK(!same(R"({"abcdefghijklmnopq":1})", R"({"Xbcdefghijklmnopq":1})"));
|
||||
CHECK(!same(R"({"abcdefghijklmnopq":[1]})", R"({"abcdefghijklmnopX":[1]})"));
|
||||
// floats of one layout and different tokens are the same value
|
||||
CHECK(same("[1.5e0, 2]", "[1.5E0, 2]"));
|
||||
CHECK(same("[1.5e0, 2]", "[1.5e+0, 2]"));
|
||||
CHECK(same("[1.50, 2]", "[1.5, 2]"));
|
||||
CHECK(!same("[1.5e0, 2]", "[1.6e0, 2]"));
|
||||
CHECK(!same("[1.5e0, 2]", "[1.5E1, 2]"));
|
||||
CHECK(same(R"({"a":1.25e0,"b":[3]})", R"({"a":1.25E0,"b":[3]})"));
|
||||
}
|
||||
|
||||
SECTION("a basic_json whose members are not sorted")
|
||||
{
|
||||
// (an object type that is not an ordered map, and does not keep its members sorted either)
|
||||
using unordered_json = nlohmann::basic_json<unordered_object>;
|
||||
using unordered_document = nlohmann::basic_json_document<unordered_json>;
|
||||
std::string text = "{";
|
||||
for (char key = 'a'; key <= 'p'; ++key)
|
||||
{
|
||||
text += (key != 'a' ? ",\"" : "\"") + std::string(1, key) + "\":[" + std::to_string(key) + "]";
|
||||
}
|
||||
text += "}";
|
||||
const unordered_json j = unordered_json::parse(text);
|
||||
const unordered_document d = unordered_document::parse(text);
|
||||
CHECK(d.root() == j);
|
||||
CHECK(j == d.root());
|
||||
unordered_json other = j;
|
||||
other["k"] = 0;
|
||||
CHECK(d.root() != other);
|
||||
CHECK(other != d.root());
|
||||
}
|
||||
|
||||
SECTION("values that are the same text, or the same value written differently, at any depth")
|
||||
{
|
||||
// nested arrays and objects around depths where the walk over equal text gives up
|
||||
|
||||
@@ -101,6 +101,10 @@ TEST_CASE("json_view dump")
|
||||
CHECK(d.root().dump(0, ' ', false, json_view::number_format::source) == "[\n1.50,\n1E2,\n-0,\n-0.0,\n123456789012345678901234567890,\n18446744073709551615,\n-9223372036854775808,\n0.1,\n1e-7,\n5e-324\n]");
|
||||
CHECK(d.root().dump(-1, ' ', true, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
|
||||
|
||||
CHECK(d.root().dump(0, ' ', true, json_view::number_format::source) == d.root().dump(0, ' ', false, json_view::number_format::source));
|
||||
const json_document escaped = json_document::parse("[\"\xC3\xA9\", 1.50, {\"\xF0\x9F\x98\x80\": 1E2}]");
|
||||
CHECK(escaped.root().dump(1, ' ', true, json_view::number_format::source) == "[\n \"\\u00e9\",\n 1.50,\n {\n \"\\ud83d\\ude00\": 1E2\n }\n]");
|
||||
|
||||
// float tokens of up to 17 significant digits in every spelling: those
|
||||
// of at most 15 digits are written from their digits, the others
|
||||
// through the conversion; both as dump() writes them
|
||||
@@ -232,6 +236,29 @@ TEST_CASE("json_view dump")
|
||||
CHECK(editable.root().dump() == expected.dump());
|
||||
}
|
||||
|
||||
// tokens of 16 and 17 digits with few digits after the point, and with a value of 0, which are
|
||||
// converted by the general algorithm; padded, as a token near the end of the text is not read in this way
|
||||
{
|
||||
const std::string padding(40, ' ');
|
||||
for (const char* token :
|
||||
{
|
||||
"123456789012.3456", "-123456789012.3456", "1234567890123.456", "12345678901234.56", "123456789012345.6", "1234567890123456.0",
|
||||
"0.0000000000000000", "-0.0000000000000000", "0.000000000000000", "1234567890123.4567", "99999999999999.999"
|
||||
})
|
||||
{
|
||||
CAPTURE(token)
|
||||
const std::string text = std::string("[") + token + padding + "]";
|
||||
const json_document d = json_document::parse(text);
|
||||
const json j = json::parse(text);
|
||||
CHECK(d.root()[0].get<double>() == j[0].get<double>());
|
||||
CHECK(d.root().dump() == j.dump());
|
||||
// a float document converts the tokens as well
|
||||
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_doc = nlohmann::basic_json_document<json_float>::parse(text);
|
||||
CHECK(float_doc.root()[0].get<float>() == json_float::parse(text)[0].get<float>());
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = "[";
|
||||
|
||||
@@ -214,6 +214,24 @@ TEST_CASE("json_view dump: escaping, nesting, and output buffers")
|
||||
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
||||
}
|
||||
|
||||
SECTION("the estimate for a value that is followed by many decoded strings")
|
||||
{
|
||||
// (the end of the value in the source is not known when the nodes after it have decoded strings)
|
||||
std::string text = "[7";
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
text += R"(,"\n")";
|
||||
}
|
||||
text += ",8]";
|
||||
const json_document d = json_document::parse(text);
|
||||
CHECK(d.root()[0].dump() == "7");
|
||||
CHECK(d.root()[1].dump() == R"("\n")");
|
||||
CHECK(d.root().dump() == json::parse(text).dump());
|
||||
// the same with a container
|
||||
const json_document nested = json_document::parse(R"([[7],)" + text.substr(3));
|
||||
CHECK(nested.root()[0].dump() == "[7]");
|
||||
}
|
||||
|
||||
SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings")
|
||||
{
|
||||
// plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys;
|
||||
@@ -259,6 +277,28 @@ TEST_CASE("json_view dump: escaping, nesting, and output buffers")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("the length of the run that ensure_ascii copies")
|
||||
{
|
||||
// (dump() only asks for strings with a byte to escape, so a run that ends the string is checked here)
|
||||
for (const std::size_t length :
|
||||
{
|
||||
std::size_t{0}, std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{31}, std::size_t{32}, std::size_t{48}, std::size_t{50}
|
||||
})
|
||||
{
|
||||
CAPTURE(length)
|
||||
// a run of printable characters, followed by a byte that is not copied and more text
|
||||
std::string text(length, 'a');
|
||||
text += "\"abcdefghijklmnopqrstuvwxyz";
|
||||
const auto* const bytes = reinterpret_cast<const unsigned char*>(text.data()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const auto* const end = bytes + text.size();
|
||||
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length, end) == length);
|
||||
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length + 1, end) == length);
|
||||
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length + 5, end) == length);
|
||||
// the end of the buffer right after the run
|
||||
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length, bytes + length) == length);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ensure_ascii with DEL or a non-ASCII character at every position")
|
||||
{
|
||||
// the check whether a string of the source has no byte >= 0x7F reads
|
||||
|
||||
@@ -547,6 +547,10 @@ TEST_CASE("json_view edits: errors")
|
||||
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root, json(json::value_t::discarded)), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
||||
// (an index that does not fit the document, in a null: basic_json would pad it with 4 Gi nulls)
|
||||
CHECK_THROWS_AS(d.set(json::json_pointer("/z/4294967295"), 1), json::out_of_range&);
|
||||
CHECK(root["z"].is_null());
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// invalid UTF-8 is rejected when it enters the document, with the error
|
||||
@@ -572,6 +576,19 @@ TEST_CASE("json_view edits: errors")
|
||||
static_cast<void>(other);
|
||||
}
|
||||
|
||||
TEST_CASE("json_view edits: a container that was added is replaced by a scalar")
|
||||
{
|
||||
// the view of an element that push_back() added is that of the new value itself, not of
|
||||
// the slot that links to it: replacing it by a scalar has to find the container it is in
|
||||
json_editable_document d = json_editable_document::parse(R"({"a": [1]})");
|
||||
d.push_back(d.root()["a"], json::array({2, 3}));
|
||||
d.push_back(d.root()["a"], json::object({{"k", json::array({4, 5})}}));
|
||||
d.set(d.root()["a"][1], 6);
|
||||
d.set(d.root()["a"][2]["k"], "s");
|
||||
CHECK(d.root().dump() == R"({"a":[1,6,{"k":"s"}]})");
|
||||
CHECK(d.root().materialize() == json::parse(R"({"a":[1,6,{"k":"s"}]})"));
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("json_view edits: the size of a text arena")
|
||||
{
|
||||
|
||||
@@ -277,6 +277,17 @@ TEST_CASE("json_view from_json")
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
SECTION("a map with enum keys from a view that is not an object")
|
||||
{
|
||||
// an object is read with the keys as the enum's names; any other
|
||||
// value is read as an array of pairs, like a map of any other key type
|
||||
const json_document d = json_document::parse(R"({"pairs": [["red", 1], ["green", 2]], "number": 1})");
|
||||
const json j = d.root().materialize();
|
||||
const json_view v = d.root();
|
||||
CHECK(same<std::map<color, int>>(v, j, "pairs"));
|
||||
CHECK((v["pairs"].get<std::map<color, int>>() == std::map<color, int> {{color::red, 1}, {color::green, 2}}));
|
||||
}
|
||||
|
||||
SECTION("std types: the same values as from the materialized value")
|
||||
{
|
||||
const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]],
|
||||
|
||||
@@ -819,6 +819,13 @@ TEST_CASE("json_view images: errors")
|
||||
static_cast<void>(d);
|
||||
}) == prefix + "too short");
|
||||
|
||||
// an image that is moved into the document is checked as well
|
||||
CHECK(exception_of([&]
|
||||
{
|
||||
const json_document d = json_document::load(std::vector<std::uint8_t>(10, 0));
|
||||
static_cast<void>(d);
|
||||
}) == prefix + "too short");
|
||||
|
||||
std::vector<std::uint8_t> bad = image;
|
||||
bad[0] = 'X';
|
||||
CHECK(load_result(bad, image_check::full) == prefix + "unknown format");
|
||||
|
||||
@@ -333,6 +333,62 @@ TEST_CASE("json_view large objects")
|
||||
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
||||
}
|
||||
|
||||
SECTION("a lookup of a missing key stops at the end of the probe window")
|
||||
{
|
||||
// Every slot of the first half of the table holds a key, each at its home slot, so
|
||||
// that the table is built; a key that is not in the object but whose home slot is
|
||||
// the first slot finds a full window of slots with other keys.
|
||||
constexpr std::size_t members = 128;
|
||||
constexpr std::size_t table_size = 256; // the table size for 128 members: the next power of two >= 256
|
||||
for (const std::size_t prefix_length :
|
||||
{
|
||||
std::size_t{0}, std::size_t{20}
|
||||
})
|
||||
{
|
||||
CAPTURE(prefix_length)
|
||||
std::vector<std::string> keys(members);
|
||||
std::vector<bool> taken(members, false);
|
||||
std::string missing;
|
||||
std::size_t found = 0;
|
||||
for (std::uint64_t counter = 0; found < members || missing.empty(); ++counter)
|
||||
{
|
||||
std::string key(prefix_length, 'p');
|
||||
std::uint64_t x = counter;
|
||||
for (std::size_t i = 0; i < 8; ++i, x /= 26)
|
||||
{
|
||||
key += static_cast<char>('a' + (x % 26));
|
||||
}
|
||||
const std::size_t slot = nlohmann::detail::conditional_static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (table_size - 1);
|
||||
if (slot < members && !taken[slot])
|
||||
{
|
||||
taken[slot] = true;
|
||||
keys[slot] = key;
|
||||
++found;
|
||||
}
|
||||
else if (slot == 0 && missing.empty())
|
||||
{
|
||||
missing = key;
|
||||
}
|
||||
}
|
||||
std::string text = "{";
|
||||
for (std::size_t i = 0; i < members; ++i)
|
||||
{
|
||||
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
|
||||
}
|
||||
text += "}";
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
for (std::size_t i = 0; i < members; ++i)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CHECK(v[keys[i]].get<std::size_t>() == i);
|
||||
}
|
||||
CHECK(!v.contains(missing));
|
||||
CHECK(v.find(missing) == v.end());
|
||||
CHECK(v.count(missing) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("keys that differ in their last bytes are spread over the table")
|
||||
{
|
||||
// The hash must spread the usual families of keys (numbered names, hex
|
||||
|
||||
@@ -20,6 +20,7 @@ using nlohmann::ordered_json;
|
||||
using nlohmann::json_document;
|
||||
using nlohmann::json_editable_document;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
@@ -427,6 +428,31 @@ TEST_CASE("json_view materialize() source positions")
|
||||
CHECK(same_positions(m[std::string("a") + '\n'][2], j[std::string("a") + '\n'][2]));
|
||||
}
|
||||
|
||||
SECTION("a damaged text: the end of a container is not found")
|
||||
{
|
||||
// (the text of an image is not checked with image_check::none)
|
||||
const std::string array_text = "[1,2]";
|
||||
const std::vector<std::uint8_t> original = json_document::parse(array_text).save();
|
||||
const auto at = std::search(original.begin(), original.end(), array_text.begin(), array_text.end());
|
||||
REQUIRE(at != original.end());
|
||||
const auto closing = static_cast<std::size_t>(at - original.begin()) + array_text.size() - 1;
|
||||
// not a closing bracket, a slash that starts no comment, and the end of the text
|
||||
for (const char replacement :
|
||||
{
|
||||
'x', '/', ' '
|
||||
})
|
||||
{
|
||||
CAPTURE(replacement)
|
||||
std::vector<std::uint8_t> damaged = original;
|
||||
damaged[closing] = static_cast<std::uint8_t>(replacement);
|
||||
const json_document d = json_document::load(damaged, json_document::image_check::none);
|
||||
const json m = d.root().materialize();
|
||||
CHECK(range(m) == "none");
|
||||
CHECK(range(m[0]) == "1-2");
|
||||
CHECK(range(m[1]) == "3-4");
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("a decoded string's position must be within the text")
|
||||
{
|
||||
@@ -461,6 +487,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
||||
{
|
||||
"null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})",
|
||||
R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})",
|
||||
R"({"a\u0000b": [1, "x"], "c": {"d\u0000": 2}})", // (the path of a key ends at a null byte, as in basic_json)
|
||||
R"([1 /* ] */, {"a": [] // }
|
||||
, "b": {} } , ])",
|
||||
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
|
||||
@@ -512,6 +539,49 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
||||
}
|
||||
|
||||
SECTION("views of values that are no longer part of the document, and of no value")
|
||||
{
|
||||
const std::string expected_string = "[json.exception.type_error.302] type must be number, but is string";
|
||||
static_cast<void>(expected_string);
|
||||
// (a value that is not found has no path, and with it no bytes)
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"(["x", ["y"]])");
|
||||
const auto removed = d.root()[0];
|
||||
d.erase(d.root(), 0);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||
#endif
|
||||
// (the root is a scalar now: the value cannot be in it)
|
||||
d.set(d.root(), 5);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||
#endif
|
||||
CHECK(d.root().get<int>() == 5);
|
||||
}
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"(["x"])");
|
||||
const auto removed = d.root()[0];
|
||||
d.set(d.root(), 5);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||
#endif
|
||||
}
|
||||
// a view of no value has no context
|
||||
CHECK(exception_of_call([&] { static_cast<void>(nlohmann::json_view().get<int>()); }) == "[json.exception.type_error.302] type must be number, but is discarded");
|
||||
}
|
||||
|
||||
SECTION("an array whose last element was replaced by a new container has no bytes")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"({"a": [1, [2]], "b": 1})");
|
||||
d.set(d.root()["a"][1], json::array({3}));
|
||||
std::string expected = "[json.exception.type_error.302] ";
|
||||
#if JSON_DIAGNOSTICS
|
||||
expected += "(/a) ";
|
||||
#endif
|
||||
expected += "type must be number, but is array";
|
||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"].get<int>()); }) == expected);
|
||||
}
|
||||
|
||||
SECTION("deep documents with siblings after every nested container")
|
||||
{
|
||||
// the search for the path pushes and pops one frame per level (the
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
@@ -140,6 +143,56 @@ TEST_CASE("tests on deeply nested JSONs")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("leaf values of every type below the bound of the recursive descent")
|
||||
{
|
||||
// the elements and members of the containers below the bound are
|
||||
// copied one by one, whatever their type
|
||||
const json leaves = json::array(
|
||||
{
|
||||
nullptr, true, false, -1, std::uint64_t{1}, 1.5, "text", json::binary({1, 2, 3})
|
||||
});
|
||||
const json members =
|
||||
{
|
||||
{"null", nullptr}, {"boolean", true}, {"integer", -1}, {"unsigned", std::uint64_t{1}},
|
||||
{"float", 1.5}, {"string", "text"}, {"binary", json::binary({1, 2, 3})}
|
||||
};
|
||||
|
||||
for (const bool objects :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(objects)
|
||||
|
||||
json j = objects ? members : leaves;
|
||||
for (std::size_t i = 0; i < 300; ++i)
|
||||
{
|
||||
json wrapper = objects ? json::object() : json::array();
|
||||
if (objects)
|
||||
{
|
||||
wrapper["a"] = std::move(j);
|
||||
}
|
||||
else
|
||||
{
|
||||
wrapper.push_back(std::move(j));
|
||||
}
|
||||
j = std::move(wrapper);
|
||||
}
|
||||
|
||||
const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
|
||||
const json* current = ©
|
||||
for (std::size_t i = 0; i < 300; ++i)
|
||||
{
|
||||
current = objects ? ¤t->at("a") : ¤t->front();
|
||||
}
|
||||
|
||||
// the innermost value is shallow: it is safe to compare
|
||||
CHECK(*current == (objects ? members : leaves));
|
||||
CHECK(current->size() == (objects ? members.size() : leaves.size()));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a value that is deep in one place only")
|
||||
{
|
||||
json j = json::object();
|
||||
|
||||
@@ -495,3 +495,206 @@ TEST_CASE("locale changes during a single dump() (#5709 item 3)")
|
||||
// fix #5597 made on the parser side
|
||||
CHECK(buf.data == "[\"" + padding + "\",1234.5]");
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// a token whose first data() call switches LC_NUMERIC, so that the locale
|
||||
// changes between convert_float_locale_aware() looking up the decimal point
|
||||
// and strtod reading the token (the race of #5198, made deterministic)
|
||||
struct locale_switching_token
|
||||
{
|
||||
using value_type = char;
|
||||
|
||||
locale_switching_token(std::string text_, const char* switch_to_)
|
||||
: text(std::move(text_)), switch_to(switch_to_)
|
||||
{}
|
||||
|
||||
std::size_t size() const
|
||||
{
|
||||
return text.size();
|
||||
}
|
||||
|
||||
char& operator[](std::size_t i)
|
||||
{
|
||||
return text[i];
|
||||
}
|
||||
|
||||
char* data()
|
||||
{
|
||||
if (!switched)
|
||||
{
|
||||
switched = std::setlocale(LC_NUMERIC, switch_to) != nullptr;
|
||||
}
|
||||
return &text[0];
|
||||
}
|
||||
|
||||
std::string text;
|
||||
const char* switch_to;
|
||||
bool switched = false;
|
||||
};
|
||||
|
||||
// set LC_NUMERIC to the first of @a names that is usable and has a decimal
|
||||
// point of @a min_size bytes at least (and at most @a max_size)
|
||||
bool use_decimal_point_locale(const std::vector<const char*>& names, std::size_t min_size, std::size_t max_size, const char*& used)
|
||||
{
|
||||
for (const char* name : names)
|
||||
{
|
||||
if (std::setlocale(LC_NUMERIC, name) == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t size = std::string(std::localeconv()->decimal_point).size();
|
||||
if (size >= min_size && size <= max_size)
|
||||
{
|
||||
used = name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// convert a token with convert_float_locale_aware(); the token must be given
|
||||
// back as it was
|
||||
template<typename FloatType>
|
||||
FloatType convert_locale_aware(const std::string& text)
|
||||
{
|
||||
std::string token = text;
|
||||
const std::size_t dot = token.find('.');
|
||||
FloatType value{};
|
||||
nlohmann::detail::convert_float_locale_aware(token, dot, value);
|
||||
CHECK(token == text);
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
void check_locale_aware_conversions()
|
||||
{
|
||||
CHECK(convert_locale_aware<FloatType>("3.5") == FloatType(3.5));
|
||||
CHECK(convert_locale_aware<FloatType>("-0.25") == FloatType(-0.25));
|
||||
CHECK(convert_locale_aware<FloatType>("1.25e2") == FloatType(125));
|
||||
// no decimal point at all
|
||||
CHECK(convert_locale_aware<FloatType>("12e1") == FloatType(120));
|
||||
CHECK(convert_locale_aware<FloatType>("7") == FloatType(7));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("convert_float_locale_aware")
|
||||
{
|
||||
const std::vector<const char*> single_byte_names = {"de_DE.UTF-8", "de_DE", "fr_FR.UTF-8", "fr_FR", "ru_RU.UTF-8"};
|
||||
const std::vector<const char*> multi_byte_names = {"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"};
|
||||
const char* used = nullptr;
|
||||
|
||||
SECTION("decimal point '.'")
|
||||
{
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
check_locale_aware_conversions<float>();
|
||||
check_locale_aware_conversions<double>();
|
||||
check_locale_aware_conversions<long double>();
|
||||
}
|
||||
|
||||
SECTION("single-byte decimal point other than '.'")
|
||||
{
|
||||
if (use_decimal_point_locale(single_byte_names, 1, 1, used) && std::localeconv()->decimal_point[0] != '.')
|
||||
{
|
||||
CAPTURE(used)
|
||||
check_locale_aware_conversions<float>();
|
||||
check_locale_aware_conversions<double>();
|
||||
check_locale_aware_conversions<long double>();
|
||||
}
|
||||
else
|
||||
{
|
||||
MESSAGE("no locale with a single-byte decimal point other than '.' is usable");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("multi-byte decimal point")
|
||||
{
|
||||
if (use_decimal_point_locale(multi_byte_names, 2, 8, used))
|
||||
{
|
||||
CAPTURE(used)
|
||||
check_locale_aware_conversions<float>();
|
||||
check_locale_aware_conversions<double>();
|
||||
check_locale_aware_conversions<long double>();
|
||||
}
|
||||
else
|
||||
{
|
||||
MESSAGE("no locale with a multi-byte decimal point is usable");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a token that is not converted completely")
|
||||
{
|
||||
// the locale does not change, so there is nothing to retry: the
|
||||
// value up to the garbage is kept
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
std::string token = "1.5x";
|
||||
double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, token.find('.'), value);
|
||||
CHECK(value == 1.5);
|
||||
CHECK(token == "1.5x");
|
||||
}
|
||||
|
||||
SECTION("the locale changes before the conversion")
|
||||
{
|
||||
if (!use_decimal_point_locale(single_byte_names, 1, 1, used) || std::localeconv()->decimal_point[0] == '.')
|
||||
{
|
||||
MESSAGE("no locale with a single-byte decimal point other than '.' is usable");
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string comma_locale = used;
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
|
||||
// "C" -> comma locale: strtod stops at the '.', so the conversion is repeated
|
||||
{
|
||||
locale_switching_token token("1.5", comma_locale.c_str());
|
||||
double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 1.5);
|
||||
CHECK(token.text == "1.5");
|
||||
}
|
||||
}
|
||||
|
||||
// the same for the other floating-point types
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
{
|
||||
locale_switching_token token("2.25", comma_locale.c_str());
|
||||
float value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 2.25f);
|
||||
}
|
||||
}
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
{
|
||||
locale_switching_token token("2.25", comma_locale.c_str());
|
||||
long double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 2.25L);
|
||||
}
|
||||
}
|
||||
|
||||
// comma locale -> multi-byte decimal point
|
||||
const char* multi_byte = nullptr;
|
||||
if (use_decimal_point_locale(multi_byte_names, 2, 8, multi_byte))
|
||||
{
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, comma_locale.c_str()) != nullptr);
|
||||
locale_switching_token token("1.5", multi_byte);
|
||||
double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 1.5);
|
||||
CHECK(token.text == "1.5");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Test suite for verifying MessagePack handling with std::byte input
|
||||
@@ -96,4 +97,33 @@ TEST_CASE("MessagePack with std::byte")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JSON text (not MessagePack) as std::byte input: the error position is
|
||||
// computed from the bytes consumed so far
|
||||
TEST_CASE("JSON text with std::byte")
|
||||
{
|
||||
const auto to_bytes = [](const std::string & text)
|
||||
{
|
||||
std::vector<std::byte> bytes;
|
||||
for (const char c : text)
|
||||
{
|
||||
bytes.push_back(static_cast<std::byte>(c));
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
SECTION("valid text")
|
||||
{
|
||||
const json j = json::parse(to_bytes("[1,\n2]"));
|
||||
CHECK(j == json({1, 2}));
|
||||
CHECK(json::accept(to_bytes("[1,\n2]")));
|
||||
}
|
||||
|
||||
SECTION("invalid text")
|
||||
{
|
||||
CHECK_FALSE(json::accept(to_bytes("[1,\n2,")));
|
||||
CHECK_THROWS_WITH_AS(json::parse(to_bytes("[1,\n2,")),
|
||||
"[json.exception.parse_error.101] parse error at line 2, column 3: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -16,6 +16,7 @@ using nlohmann::ordered_json;
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("ordered_json")
|
||||
{
|
||||
@@ -515,3 +516,87 @@ TEST_CASE("parsing random trees of arrays and objects")
|
||||
CHECK(json::parse(text).dump() == expected.dump());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("object_key_index")
|
||||
{
|
||||
// the hash table of the DOM parser finds the position of a key in an
|
||||
// ordered_json object; the parser creates it for exactly 64 elements, but
|
||||
// it is correct for any number of elements it is given
|
||||
using nlohmann::detail::object_key_index;
|
||||
const std::size_t not_found = object_key_index::npos;
|
||||
|
||||
for (const std::size_t initial :
|
||||
{
|
||||
std::size_t{0}, std::size_t{10}, std::size_t{64}, std::size_t{65}, std::size_t{300}
|
||||
})
|
||||
{
|
||||
CAPTURE(initial)
|
||||
|
||||
// the hashes are not distinct on purpose: elements with the same
|
||||
// hash are told apart by the caller
|
||||
const auto hash_of = [](std::size_t position)
|
||||
{
|
||||
return (position % 50) * 0x9E3779B97F4A7C15ull;
|
||||
};
|
||||
|
||||
std::vector<std::size_t> hashes;
|
||||
for (std::size_t i = 0; i < initial; ++i)
|
||||
{
|
||||
hashes.push_back(hash_of(i));
|
||||
}
|
||||
object_key_index index(std::move(hashes));
|
||||
|
||||
// the elements that are there
|
||||
for (std::size_t i = 0; i < initial; ++i)
|
||||
{
|
||||
CHECK(index.find(hash_of(i), [i](std::size_t position)
|
||||
{
|
||||
return position == i;
|
||||
}) == i);
|
||||
}
|
||||
CHECK(index.find(hash_of(1), [](std::size_t /*position*/)
|
||||
{
|
||||
return false;
|
||||
}) == not_found);
|
||||
|
||||
// elements that are added later, until the table grows
|
||||
for (std::size_t i = initial; i < initial + 600; ++i)
|
||||
{
|
||||
index.add(hash_of(i));
|
||||
CHECK(index.find(hash_of(i), [i](std::size_t position)
|
||||
{
|
||||
return position == i;
|
||||
}) == i);
|
||||
}
|
||||
CHECK(index.find(hash_of(initial), [initial](std::size_t position)
|
||||
{
|
||||
return position == initial;
|
||||
}) == initial);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON pointers on ordered_json")
|
||||
{
|
||||
using pointer = ordered_json::json_pointer;
|
||||
|
||||
SECTION("operator[] inserts a missing key at the end")
|
||||
{
|
||||
ordered_json j = {{"z", 1}, {"x", {{"y", 2}}}};
|
||||
|
||||
j[pointer("/a")] = 3;
|
||||
j[pointer("/x/b")] = 4;
|
||||
|
||||
CHECK(j == ordered_json({{"z", 1}, {"x", {{"y", 2}, {"b", 4}}}, {"a", 3}}));
|
||||
CHECK(j.dump() == R"({"z":1,"x":{"y":2,"b":4},"a":3})");
|
||||
}
|
||||
|
||||
SECTION("unflatten of a flattened object whose keys are not sorted")
|
||||
{
|
||||
// the prefixes /a and /c denote arrays; a key of /a follows one of /c
|
||||
const ordered_json flat = {{"/a/0/p", 1}, {"/c/0/x", 2}, {"/a/0/q", 3}, {"/a/1/p", 4}};
|
||||
const ordered_json expected = {{"a", {{{"p", 1}, {"q", 3}}, {{"p", 4}}}}, {"c", {{{"x", 2}}}}};
|
||||
|
||||
CHECK(flat.unflatten() == expected);
|
||||
CHECK(expected.flatten().unflatten() == expected);
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,13 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
@@ -1519,6 +1521,67 @@ TEST_CASE("regression tests 1")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("operator[](json_pointer) continues with the member that emplace() found")
|
||||
{
|
||||
// see the section above: the lookup does not find "k", but emplace()
|
||||
// finds "x" and the pointer continues with the member of "x". Here
|
||||
// the member is a container, so the pointer can go on below it. Only
|
||||
// the outermost object may be a copy (with restarted timestamps): the
|
||||
// value of "x" is moved in, so that its own lookups work.
|
||||
const my_json orig = my_json::parse(R"({"x":null})");
|
||||
const auto make = [&orig]()
|
||||
{
|
||||
my_json::object_t top = orig.get_ref<const my_json::object_t&>();
|
||||
top.at("x") = my_json::parse(R"({"a":{"b":1},"n":null,"arr":[10,20],"objs":[{"m":5},{"m":6}]})");
|
||||
return my_json(std::move(top));
|
||||
};
|
||||
|
||||
SECTION("existing members")
|
||||
{
|
||||
// emplace() registers the key "k" in the object, so that every
|
||||
// pointer needs a new object to reach the member of "x" again
|
||||
const std::array<std::pair<const char*, const char*>, 4> pointers = {{{"/k/a/b", "1"}, {"/k/arr/1", "20"}, {"/k/n", "null"}, {"/k/objs/1/m", "6"}}};
|
||||
for (const auto& pointer : pointers)
|
||||
{
|
||||
CAPTURE(pointer.first)
|
||||
my_json j = make();
|
||||
CHECK(j[my_json::json_pointer(pointer.first)].dump() == pointer.second);
|
||||
CHECK(j.size() == 1);
|
||||
CHECK(j["x"].size() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a missing member of an object")
|
||||
{
|
||||
// the timestamp of "c" is that of "b" again, so that emplace()
|
||||
// finds the member "b", a number, which has no member "d"
|
||||
my_json j = make();
|
||||
CHECK_THROWS_AS(j[my_json::json_pointer("/k/a/c/d")], my_json::out_of_range&);
|
||||
CHECK(j["x"]["a"].dump() == R"({"b":1})");
|
||||
}
|
||||
|
||||
SECTION("a null value is converted to an object")
|
||||
{
|
||||
my_json j = make();
|
||||
j[my_json::json_pointer("/k/n/c")] = 3;
|
||||
CHECK(j["x"]["n"].dump() == R"({"c":3})");
|
||||
}
|
||||
|
||||
SECTION("the end of an array")
|
||||
{
|
||||
my_json j = make();
|
||||
j[my_json::json_pointer("/k/arr/-")] = 30;
|
||||
CHECK(j["x"]["arr"].dump() == "[10,20,30]");
|
||||
}
|
||||
|
||||
SECTION("an index past the end of an array")
|
||||
{
|
||||
my_json j = make();
|
||||
j[my_json::json_pointer("/k/arr/3")] = 40;
|
||||
CHECK(j["x"]["arr"].dump() == "[10,20,null,40]");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("issue #977 - Assigning between different json types")
|
||||
{
|
||||
foo_json lj = ns::foo{3};
|
||||
|
||||
@@ -441,6 +441,10 @@ TEST_CASE("regression tests 2")
|
||||
|
||||
float_json j2 = {1000.0, 2000.0, 3000.0};
|
||||
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
||||
|
||||
// half-precision floats of both signs are converted to the float type
|
||||
CHECK(float_json::from_cbor(std::vector<std::uint8_t> {0xF9, 0xC0, 0x00}) == -2.0f);
|
||||
CHECK(float_json::from_cbor(std::vector<std::uint8_t> {0xF9, 0x3C, 0x00}) == 1.0f);
|
||||
}
|
||||
|
||||
SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?")
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <istream>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <streambuf>
|
||||
@@ -93,6 +95,25 @@ class chunked_streambuf : public std::streambuf
|
||||
std::size_t m_unbuffered;
|
||||
};
|
||||
|
||||
#if TEST_STREAM_BULK_SCAN
|
||||
// A streambuf that claims a get area of several gigabytes, of which only the
|
||||
// first bytes exist. It is only used to move the read position of the adapter,
|
||||
// never to read beyond them, so that skipping more than gbump() can bump at
|
||||
// once can be tested without allocating that much.
|
||||
class oversized_get_area_streambuf : public std::streambuf
|
||||
{
|
||||
public:
|
||||
explicit oversized_get_area_streambuf(std::size_t claimed_size)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
setg(m_data.data(), m_data.data(), m_data.data() + claimed_size);
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<char, 8> m_data{{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}};
|
||||
};
|
||||
#endif
|
||||
|
||||
// how a test reads from the stream
|
||||
enum class read_mode
|
||||
{
|
||||
@@ -433,6 +454,31 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
|
||||
ia.copy_token(token, false);
|
||||
CHECK(token.empty());
|
||||
}
|
||||
|
||||
SECTION("skipping more characters than gbump() takes at once")
|
||||
{
|
||||
// gbump() takes an int, but a get area can be larger
|
||||
if (sizeof(std::size_t) < 8)
|
||||
{
|
||||
MESSAGE("a get area larger than an int needs a 64-bit size_t");
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t claimed = std::size_t{3} << 30u;
|
||||
oversized_get_area_streambuf buf(claimed);
|
||||
std::istream is(&buf);
|
||||
nlohmann::detail::input_stream_adapter ia(is);
|
||||
CHECK(ia.get_character() == 'a');
|
||||
CHECK(ia.bulk_remaining() == claimed - 1);
|
||||
|
||||
const std::size_t skipped = static_cast<std::size_t>((std::numeric_limits<int>::max)()) + 10;
|
||||
ia.bulk_skip(skipped);
|
||||
CHECK(ia.bulk_remaining() == claimed - 1 - skipped);
|
||||
|
||||
ia.bulk_skip(ia.bulk_remaining());
|
||||
CHECK(ia.bulk_remaining() == 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("SAX parsing from a stream")
|
||||
|
||||
@@ -928,3 +928,49 @@ TEST_CASE("short decimals")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("decimals of up to 17 digits")
|
||||
{
|
||||
// write_decimal() lays out digits * 10^exp as format_buffer() does; to_chars()
|
||||
// calls it for the doubles whose digits write_shortest() does not convert at
|
||||
// once, and the digits may be as many as 17
|
||||
const auto written = [](std::uint64_t digits, int exp) -> std::string
|
||||
{
|
||||
std::array<char, 64> buf{}; // (up to 41 bytes are written)
|
||||
char* const end = nlohmann::detail::dtoa_impl::write_decimal(buf.data(), digits, exp);
|
||||
return {buf.data(), end};
|
||||
};
|
||||
|
||||
SECTION("fixed notation")
|
||||
{
|
||||
CHECK(written(1, 0) == "1.0");
|
||||
CHECK(written(1234, -2) == "12.34");
|
||||
CHECK(written(123, 2) == "12300.0");
|
||||
CHECK(written(50000000000u, -1) == "5000000000.0");
|
||||
CHECK(written(100000000u, -8) == "1.0");
|
||||
CHECK(written(12345678u, -12) == "1.2345678e-05");
|
||||
CHECK(written(1234, 11) == "123400000000000.0");
|
||||
// the point moves left of the first digit
|
||||
CHECK(written(5, -3) == "0.005");
|
||||
CHECK(written(12345678901234567u, -20) == "0.00012345678901234567");
|
||||
}
|
||||
|
||||
SECTION("exponent notation")
|
||||
{
|
||||
CHECK(written(5, -5) == "5e-05");
|
||||
CHECK(written(1234, 12) == "1.234e+15");
|
||||
CHECK(written(5, -324) == "5e-324");
|
||||
}
|
||||
|
||||
SECTION("17 digits")
|
||||
{
|
||||
CHECK(written(12345678901234567u, -16) == "1.2345678901234567");
|
||||
CHECK(written(12345678901234567u, -19) == "0.0012345678901234567");
|
||||
CHECK(written(12345678901234567u, 5) == "1.2345678901234567e+21");
|
||||
CHECK(written(99999999999999999u, -17) == "0.99999999999999999");
|
||||
CHECK(written(10000000000000001u, -16) == "1.0000000000000001");
|
||||
// trailing zeros are not written
|
||||
CHECK(written(10000000000000000u, -17) == "0.1");
|
||||
CHECK(written(12000000000000000u, -15) == "12.0");
|
||||
}
|
||||
}
|
||||
@@ -1673,6 +1673,31 @@ TEST_CASE("UBJSON")
|
||||
return true;
|
||||
});
|
||||
CHECK_THROWS_AS(scp2.start_array((std::numeric_limits<std::size_t>::max)() - 1), json::out_of_range&);
|
||||
|
||||
// without exceptions, the error is reported to the caller
|
||||
json l;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp3(l, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}, false);
|
||||
CHECK_FALSE(scp3.start_array((std::numeric_limits<std::size_t>::max)() - 1));
|
||||
CHECK(scp3.is_errored());
|
||||
}
|
||||
|
||||
SECTION("array with a known size, read without exceptions")
|
||||
{
|
||||
std::vector<uint8_t> const v_ubjson = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
|
||||
json j;
|
||||
nlohmann::detail::json_sax_dom_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> sax(j, false);
|
||||
CHECK(json::sax_parse(v_ubjson, &sax, json::input_format_t::ubjson));
|
||||
CHECK(j == json({1, 2}));
|
||||
|
||||
// the readers reject a size this large before they announce
|
||||
// it, so it can only reach start_array() directly
|
||||
json k;
|
||||
nlohmann::detail::json_sax_dom_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> sax2(k, false);
|
||||
CHECK_FALSE(sax2.start_array((std::numeric_limits<std::size_t>::max)() - 1));
|
||||
CHECK(sax2.is_errored());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user