mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 13:10:33 +00:00
Merge remote-tracking branch 'origin/develop' into claude/fix-issue-3989-db7e45
Signed-off-by: Niels Lohmann <mail@nlohmann.me> # Conflicts: # include/nlohmann/detail/input/binary_reader.hpp # include/nlohmann/json.hpp # single_include/nlohmann/json.hpp
This commit is contained in:
@@ -479,6 +479,77 @@ TEST_CASE("deep copy uses the provided allocator")
|
||||
CHECK(copy == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the number of constructions countdown_allocator lets happen, including the
|
||||
// one that fails; 0 means none ever fails
|
||||
std::size_t constructions_until_failure = 0;
|
||||
|
||||
template<class T>
|
||||
struct countdown_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class U, class... Args>
|
||||
void construct(U* p, Args&& ... args)
|
||||
{
|
||||
if (constructions_until_failure != 0 && --constructions_until_failure == 0)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = countdown_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
|
||||
{
|
||||
using countdown_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
countdown_allocator>;
|
||||
|
||||
// deeper than the 128 levels the converting constructor descends into, so
|
||||
// that failures land on both sides of the bound - or, built with
|
||||
// JSON_NO_THREAD_LOCAL, all in the iterative conversion
|
||||
json j = {1, "two", {{"three", 3}}};
|
||||
for (std::size_t i = 0; i < 150; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j, "sibling"})}};
|
||||
}
|
||||
|
||||
// Fail every construction in turn. Each failure has to reach the caller,
|
||||
// and everything built until then has to be destroyed cleanly.
|
||||
std::size_t failures = 0;
|
||||
for (std::size_t n = 1;; ++n)
|
||||
{
|
||||
constructions_until_failure = n;
|
||||
try
|
||||
{
|
||||
const countdown_json converted = j;
|
||||
constructions_until_failure = 0;
|
||||
CHECK(converted.dump() == j.dump());
|
||||
break;
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
CHECK(failures > 0);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
|
||||
@@ -352,6 +352,41 @@ TEST_CASE("alternative string type")
|
||||
CHECK(j2.flatten().unflatten() == j2);
|
||||
}
|
||||
|
||||
SECTION("contains(json_pointer)")
|
||||
{
|
||||
// contains(json_pointer) must compile and work with a string_t that has
|
||||
// no c_str() and no comparison with const char* (see #5666)
|
||||
auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})");
|
||||
|
||||
// present: object key and array indices
|
||||
CHECK(j.contains(alt_json::json_pointer("/foo")));
|
||||
CHECK(j.contains(alt_json::json_pointer("/foo/0")));
|
||||
CHECK(j.contains(alt_json::json_pointer("/foo/1")));
|
||||
|
||||
// missing: absent object key and out-of-range array index
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/bar")));
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/2")));
|
||||
|
||||
// "-" always fails the range check
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/-")));
|
||||
|
||||
// an array index must not have a leading zero
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/01")));
|
||||
|
||||
// a reference token that is not a number
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/bar")));
|
||||
}
|
||||
|
||||
SECTION("operator/(std::size_t)")
|
||||
{
|
||||
// json_pointer::operator/=(std::size_t) must compile without string_t
|
||||
// being constructible from std::string (see #5666)
|
||||
auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})");
|
||||
|
||||
CHECK(j.at(alt_json::json_pointer("/foo") / std::size_t(0)) == j["foo"][0]);
|
||||
CHECK(j.at(alt_json::json_pointer("/foo") / std::size_t(1)) == j["foo"][1]);
|
||||
}
|
||||
|
||||
SECTION("patch")
|
||||
{
|
||||
alt_json const patch1 = alt_json::parse(R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])");
|
||||
|
||||
@@ -210,15 +210,42 @@ TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
|
||||
|
||||
TEST_CASE("BJData")
|
||||
{
|
||||
SECTION("binary_reader BJData LUT arrays are sorted")
|
||||
SECTION("binary_reader BJData lookup tables")
|
||||
{
|
||||
std::vector<std::uint8_t> const data;
|
||||
auto ia = nlohmann::detail::input_adapter(data);
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
nlohmann::detail::binary_reader<json, decltype(ia)> const br{std::move(ia), json::input_format_t::bjdata};
|
||||
|
||||
CHECK(std::is_sorted(br.bjd_optimized_type_markers.begin(), br.bjd_optimized_type_markers.end()));
|
||||
CHECK(std::is_sorted(br.bjd_types_map.begin(), br.bjd_types_map.end()));
|
||||
// the excluded optimized-type markers must match binary_writer's
|
||||
// is_bjdata_excluded_type_marker(), which encodes the same 8 markers
|
||||
for (const char marker :
|
||||
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
|
||||
})
|
||||
{
|
||||
CHECK(br.is_bjd_excluded_optimized_type(marker));
|
||||
}
|
||||
for (const char marker :
|
||||
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
|
||||
})
|
||||
{
|
||||
CHECK(!br.is_bjd_excluded_optimized_type(marker));
|
||||
}
|
||||
|
||||
// every dtype marker must round-trip to its ND-array type name
|
||||
const std::vector<std::pair<char, std::string>> types
|
||||
{
|
||||
{'B', "byte"}, {'C', "char"}, {'D', "double"}, {'I', "int16"},
|
||||
{'L', "int64"}, {'M', "uint64"}, {'U', "uint8"}, {'d', "single"},
|
||||
{'i', "int8"}, {'l', "int32"}, {'m', "uint32"}, {'u', "uint16"}
|
||||
};
|
||||
for (const auto& type : types)
|
||||
{
|
||||
const char* name = br.bjd_type_name(type.first);
|
||||
REQUIRE(name != nullptr);
|
||||
CHECK(std::string(name) == type.second);
|
||||
}
|
||||
CHECK(br.bjd_type_name('x') == nullptr);
|
||||
}
|
||||
|
||||
SECTION("individual values")
|
||||
|
||||
@@ -141,6 +141,58 @@ TEST_CASE("Better diagnostics with positions")
|
||||
check_objects(300);
|
||||
}
|
||||
|
||||
SECTION("converting keeps the positions of nested values (#5650)")
|
||||
{
|
||||
// Values nested deeper than the converting constructor's descent bound
|
||||
// are converted without the call stack, on a path that has to carry the
|
||||
// positions of every value over itself. Objects and arrays take turns,
|
||||
// and the innermost value is null, which used to lose its positions.
|
||||
const auto check_conversion = [](std::size_t depth)
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += (i % 2 == 0) ? "[12, " : R"({"b":1, "a":)";
|
||||
closing += (i % 2 == 0) ? ']' : '}';
|
||||
}
|
||||
text += "null";
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
|
||||
const json original = json::parse(text);
|
||||
const nlohmann::ordered_json converted = original;
|
||||
|
||||
const json* o = &original;
|
||||
const nlohmann::ordered_json* c = &converted;
|
||||
for (std::size_t level = 0; level <= depth; ++level)
|
||||
{
|
||||
CAPTURE(level)
|
||||
REQUIRE(c->start_pos() == o->start_pos());
|
||||
REQUIRE(c->end_pos() == o->end_pos());
|
||||
|
||||
if (level < depth)
|
||||
{
|
||||
// the number beside the value nested next
|
||||
const json& o_number = o->is_object() ? o->at("b") : o->at(0);
|
||||
const nlohmann::ordered_json& c_number = c->is_object() ? c->at("b") : c->at(0);
|
||||
REQUIRE(c_number.start_pos() == o_number.start_pos());
|
||||
REQUIRE(c_number.end_pos() == o_number.end_pos());
|
||||
|
||||
o = o->is_object() ? &o->at("a") : &o->at(1);
|
||||
c = c->is_object() ? &c->at("a") : &c->at(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check_conversion(1);
|
||||
check_conversion(127);
|
||||
check_conversion(128);
|
||||
check_conversion(129);
|
||||
check_conversion(300);
|
||||
}
|
||||
|
||||
SECTION("JSON patch add to primitive parent (#4292)")
|
||||
{
|
||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||
|
||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
|
||||
TEST_CASE("Better diagnostics")
|
||||
{
|
||||
@@ -340,6 +341,36 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5650 - converting keeps the parents of nested values")
|
||||
{
|
||||
// A value nested deeper than the converting constructor's descent bound
|
||||
// is converted without the call stack. Every container that path creates
|
||||
// has to have the parents of its children set, or the JSON Pointer in the
|
||||
// diagnostic is cut short. Objects and arrays take turns.
|
||||
const std::size_t pairs = 150;
|
||||
|
||||
json j = "not a number";
|
||||
std::string pointer;
|
||||
for (std::size_t i = 0; i < pairs; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j})}};
|
||||
pointer += "/a/0";
|
||||
}
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
|
||||
const nlohmann::ordered_json* inner = &converted;
|
||||
for (std::size_t i = 0; i < pairs; ++i)
|
||||
{
|
||||
inner = &inner->at("a").at(0);
|
||||
}
|
||||
|
||||
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
|
||||
int i = 0;
|
||||
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), nlohmann::ordered_json::type_error);
|
||||
CHECK(i == 0);
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
|
||||
{
|
||||
// a map with non-string keys is read from an array of [key, value] arrays;
|
||||
@@ -492,6 +523,21 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
CHECK(copy == j);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5652 - operator>> leaves a partial value in its target on a parse error")
|
||||
{
|
||||
json j = "old value";
|
||||
std::istringstream is("[1, x");
|
||||
CHECK_THROWS_WITH_AS(is >> j, "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '1, x'", json::parse_error);
|
||||
|
||||
// j must be left unchanged, as json::parse() guarantees for its result
|
||||
CHECK(j == "old value");
|
||||
|
||||
// copying j must not trigger assert_invariant(): a failed parse must
|
||||
// not leave array/object elements without a parent pointer
|
||||
json const copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(copy == j);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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"
|
||||
|
||||
// This file tests the opt-in JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, so it
|
||||
// defines the macro itself rather than relying on a -D flag, and runs in every
|
||||
// build.
|
||||
#ifdef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#endif
|
||||
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
||||
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
||||
// the tuple constructors make them instantiate basic_json's conversion operator
|
||||
// for libstdc++'s internal tuple bases, which fails hard
|
||||
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
||||
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
#endif
|
||||
|
||||
TEST_CASE("JSON_DISABLE_TUPLE_REFERENCE_CONVERSION")
|
||||
{
|
||||
SECTION("json is not constructible from a one-element tuple of a json reference")
|
||||
{
|
||||
CHECK_FALSE(std::is_constructible<json, std::tuple<json&>>::value);
|
||||
CHECK_FALSE(std::is_constructible<json, std::tuple<const json&>>::value);
|
||||
CHECK_FALSE(std::is_constructible < json, std::tuple < json && >>::value);
|
||||
CHECK_FALSE(std::is_constructible<json, const std::tuple<json&>&>::value);
|
||||
CHECK_FALSE(std::is_constructible<ordered_json, std::tuple<ordered_json&>>::value);
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
SECTION("issue #2226 - tuple<const json&> from tuple<json&> keeps the reference")
|
||||
{
|
||||
json j = true;
|
||||
const std::tuple<const json&> tup(std::forward_as_tuple(j));
|
||||
CHECK(&std::get<0>(tup) == &j);
|
||||
}
|
||||
|
||||
SECTION("tuple<json> from tuple<json&> copies the element")
|
||||
{
|
||||
const json j = {{"key", "value"}};
|
||||
const std::tuple<json> t1(std::forward_as_tuple(j));
|
||||
CHECK(std::get<0>(t1) == j);
|
||||
|
||||
json j2 = "text";
|
||||
const std::tuple<json> t2(std::forward_as_tuple(std::move(j2)));
|
||||
CHECK(std::get<0>(t2) == "text");
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("other tuple conversions are not affected")
|
||||
{
|
||||
const json j = true;
|
||||
|
||||
// one-element tuple holding a json value
|
||||
CHECK(json(std::make_tuple(j)) == json::array({true}));
|
||||
|
||||
// tuples with more than one element, even when holding references
|
||||
int i = 1;
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
CHECK(json(std::forward_as_tuple(i, j)) == json::array({1, true}));
|
||||
CHECK(json(std::forward_as_tuple(j, j)) == json::array({true, true}));
|
||||
#endif
|
||||
|
||||
// one-element tuples holding references to other types
|
||||
std::string s = "text";
|
||||
CHECK(json(std::forward_as_tuple(s)) == json::array({"text"}));
|
||||
CHECK(json(std::forward_as_tuple(i)) == json::array({1}));
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
// a reference to a different basic_json specialization
|
||||
ordered_json oj = true;
|
||||
CHECK(json(std::forward_as_tuple(oj)) == json::array({true}));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
@@ -53,6 +54,24 @@ const json* innermost_value(const json& j, std::size_t& depth)
|
||||
return current;
|
||||
}
|
||||
|
||||
// The text of a value nested depth levels deep around the number 0. Level i is
|
||||
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
|
||||
// the single member "a", which every object type enumerates in the same order.
|
||||
std::string nested_text(std::size_t depth, const std::string& pattern)
|
||||
{
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
const bool array = pattern[i % pattern.size()] == '[';
|
||||
text += array ? "[" : "{\"a\":";
|
||||
closing += array ? ']' : '}';
|
||||
}
|
||||
text += '0';
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("tests on deeply nested JSONs")
|
||||
@@ -224,5 +243,114 @@ TEST_CASE("tests on deeply nested JSONs")
|
||||
CHECK(*innermost_value(j, unused) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("issue #5650 - stack overflow converting between specializations")
|
||||
{
|
||||
const std::vector<std::string> patterns = {"[", "{", "[{"};
|
||||
|
||||
SECTION("json to ordered_json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ordered_json to json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||
|
||||
const json converted = o;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get<ordered_json>()")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("depths around the bound of the recursive descent")
|
||||
{
|
||||
for (std::size_t d = 1; d <= 300; ++d)
|
||||
{
|
||||
CAPTURE(d);
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(d, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
const json back = converted;
|
||||
CHECK(back.dump() == text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values below the bound are converted as values above it")
|
||||
{
|
||||
// Bury a value below the bound, where it is converted without the
|
||||
// call stack, and compare it with the same value converted on its
|
||||
// own by the containers' range constructors. Its objects have
|
||||
// members that the two object types enumerate in different orders.
|
||||
const auto bury = [](nlohmann::ordered_json value)
|
||||
{
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
value = nlohmann::ordered_json::array({std::move(value)});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const auto dig = [](const json & value)
|
||||
{
|
||||
const json* current = &value;
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
current = ¤t->at(0);
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json value = nlohmann::ordered_json::object();
|
||||
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
|
||||
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
|
||||
};
|
||||
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
|
||||
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
|
||||
|
||||
const json converted_above = value;
|
||||
const json buried = bury(value);
|
||||
const json& converted_below = *dig(buried);
|
||||
|
||||
CHECK(converted_below.dump() == converted_above.dump());
|
||||
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
|
||||
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
|
||||
CHECK(converted_below.at("r").at(0).is_discarded());
|
||||
|
||||
// a discarded value is never equal to anything, so compare the rest
|
||||
value.erase("r");
|
||||
const json without_discarded_above = value;
|
||||
const json without_discarded_buried = bury(value);
|
||||
CHECK(*dig(without_discarded_buried) == without_discarded_above);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,19 @@
|
||||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||
#include <locale>
|
||||
|
||||
// skip tests if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1 (#2226)
|
||||
#if defined(JSON_DISABLE_TUPLE_REFERENCE_CONVERSION) && (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION == 1)
|
||||
#define SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
||||
#endif
|
||||
|
||||
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
||||
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
||||
// the tuple constructors make them instantiate basic_json's conversion operator
|
||||
// for libstdc++'s internal tuple bases, which fails hard
|
||||
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
||||
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
#endif
|
||||
|
||||
#define JSON_TESTS_PRIVATE
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
@@ -28,6 +41,7 @@ using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <cstdio>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -542,6 +556,20 @@ TEST_CASE("regression tests 2")
|
||||
)));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
||||
SECTION("issue #2226 - std::tuple dangling reference - implicit conversion")
|
||||
{
|
||||
// by default, a one-element tuple holding a json reference converts to
|
||||
// a one-element array; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION removes
|
||||
// this conversion (see unit-disable-tuple-reference-conversion.cpp)
|
||||
const json j = true;
|
||||
CHECK(std::is_constructible<json, std::tuple<const json&>>::value);
|
||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||
CHECK(json(std::forward_as_tuple(j)) == json::array({true}));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("PR #2181 - regression bug with lvalue")
|
||||
{
|
||||
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
||||
|
||||
@@ -3033,3 +3033,224 @@ TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
CHECK(json::from_ubjson(expected) == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the bytes that follow the marker of an integer: the value in the width of
|
||||
// the marker (big endian for UBJSON, little endian for BJData), or, for a
|
||||
// high-precision number, the length and the decimal digits
|
||||
std::vector<std::uint8_t> integer_payload(const char marker, const json& value, const bool little_endian)
|
||||
{
|
||||
std::size_t width = 0;
|
||||
switch (marker)
|
||||
{
|
||||
case 'i':
|
||||
case 'U':
|
||||
width = 1;
|
||||
break;
|
||||
case 'I':
|
||||
case 'u':
|
||||
width = 2;
|
||||
break;
|
||||
case 'l':
|
||||
case 'm':
|
||||
width = 4;
|
||||
break;
|
||||
case 'L':
|
||||
case 'M':
|
||||
width = 8;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
const std::string digits = value.dump();
|
||||
std::vector<std::uint8_t> result = {'i', static_cast<std::uint8_t>(digits.size())};
|
||||
for (const char c : digits)
|
||||
{
|
||||
result.push_back(static_cast<std::uint8_t>(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const std::uint64_t bits = value.is_number_unsigned()
|
||||
? value.get<std::uint64_t>()
|
||||
: static_cast<std::uint64_t>(value.get<std::int64_t>());
|
||||
std::vector<std::uint8_t> result(width);
|
||||
for (std::size_t i = 0; i < width; ++i)
|
||||
{
|
||||
result[little_endian ? i : width - 1 - i] = static_cast<std::uint8_t>(bits >> (8 * i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
json i64(const std::int64_t v)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
json u64(const std::uint64_t v)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("UBJSON and BJData integer markers at every range edge")
|
||||
{
|
||||
// An optimized container announces the marker of its values after `$` and
|
||||
// then writes every value without a marker, so the marker the writer
|
||||
// announces and the width it writes must match for every value. This
|
||||
// checks both for the values around each edge of the integer types, as
|
||||
// scalars and as the values of optimized arrays and objects.
|
||||
struct integer_case
|
||||
{
|
||||
json value;
|
||||
char ubjson; // expected UBJSON marker
|
||||
char bjdata; // expected BJData marker
|
||||
};
|
||||
|
||||
const std::int64_t int64_min = (std::numeric_limits<std::int64_t>::min)();
|
||||
const std::int64_t int64_max = (std::numeric_limits<std::int64_t>::max)();
|
||||
const std::uint64_t uint64_max = (std::numeric_limits<std::uint64_t>::max)();
|
||||
|
||||
const std::vector<integer_case> cases =
|
||||
{
|
||||
// int8
|
||||
{i64(-129), 'I', 'I'},
|
||||
{i64(-128), 'i', 'i'},
|
||||
{i64(-127), 'i', 'i'},
|
||||
{i64(-1), 'i', 'i'},
|
||||
{i64(0), 'i', 'i'},
|
||||
{u64(0), 'i', 'i'},
|
||||
{i64(126), 'i', 'i'},
|
||||
{i64(127), 'i', 'i'},
|
||||
{u64(127), 'i', 'i'},
|
||||
{i64(128), 'U', 'U'},
|
||||
{u64(128), 'U', 'U'},
|
||||
// uint8
|
||||
{i64(254), 'U', 'U'},
|
||||
{i64(255), 'U', 'U'},
|
||||
{u64(255), 'U', 'U'},
|
||||
{i64(256), 'I', 'I'},
|
||||
{u64(256), 'I', 'I'},
|
||||
// int16
|
||||
{i64(-32769), 'l', 'l'},
|
||||
{i64(-32768), 'I', 'I'},
|
||||
{i64(-32767), 'I', 'I'},
|
||||
{i64(32766), 'I', 'I'},
|
||||
{i64(32767), 'I', 'I'},
|
||||
{u64(32767), 'I', 'I'},
|
||||
{i64(32768), 'l', 'u'},
|
||||
{u64(32768), 'l', 'u'},
|
||||
// uint16 (BJData only)
|
||||
{i64(65534), 'l', 'u'},
|
||||
{i64(65535), 'l', 'u'},
|
||||
{u64(65535), 'l', 'u'},
|
||||
{i64(65536), 'l', 'l'},
|
||||
{u64(65536), 'l', 'l'},
|
||||
// int32
|
||||
{i64(-2147483649LL), 'L', 'L'},
|
||||
{i64(-2147483648LL), 'l', 'l'},
|
||||
{i64(-2147483647LL), 'l', 'l'},
|
||||
{i64(2147483646LL), 'l', 'l'},
|
||||
{i64(2147483647LL), 'l', 'l'},
|
||||
{u64(2147483647ULL), 'l', 'l'},
|
||||
{i64(2147483648LL), 'L', 'm'},
|
||||
{u64(2147483648ULL), 'L', 'm'},
|
||||
// uint32 (BJData only)
|
||||
{i64(4294967294LL), 'L', 'm'},
|
||||
{i64(4294967295LL), 'L', 'm'},
|
||||
{u64(4294967295ULL), 'L', 'm'},
|
||||
{i64(4294967296LL), 'L', 'L'},
|
||||
{u64(4294967296ULL), 'L', 'L'},
|
||||
// int64
|
||||
{i64(int64_min), 'L', 'L'},
|
||||
{i64(int64_min + 1), 'L', 'L'},
|
||||
{i64(int64_max - 1), 'L', 'L'},
|
||||
{i64(int64_max), 'L', 'L'},
|
||||
{u64(static_cast<std::uint64_t>(int64_max)), 'L', 'L'},
|
||||
// uint64 (BJData only; UBJSON writes a high-precision number)
|
||||
{u64(static_cast<std::uint64_t>(int64_max) + 1), 'H', 'M'},
|
||||
{u64(uint64_max - 1), 'H', 'M'},
|
||||
{u64(uint64_max), 'H', 'M'},
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
for (const bool bjdata :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
const char marker = bjdata ? c.bjdata : c.ubjson;
|
||||
const std::vector<std::uint8_t> payload = integer_payload(marker, c.value, bjdata);
|
||||
const auto to_binary = [bjdata](const json & j, const bool use_size, const bool use_type)
|
||||
{
|
||||
return bjdata ? json::to_bjdata(j, use_size, use_type) : json::to_ubjson(j, use_size, use_type);
|
||||
};
|
||||
const auto from_binary = [bjdata](const std::vector<std::uint8_t>& v)
|
||||
{
|
||||
return bjdata ? json::from_bjdata(v) : json::from_ubjson(v);
|
||||
};
|
||||
INFO("value = " << c.value.dump() << (c.value.is_number_unsigned() ? " (unsigned)" : "") << ", format = " << (bjdata ? "BJData" : "UBJSON"));
|
||||
|
||||
// scalar
|
||||
std::vector<std::uint8_t> expected = {static_cast<std::uint8_t>(marker)};
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CHECK(to_binary(c.value, use_size, false) == expected);
|
||||
}
|
||||
CHECK(from_binary(expected) == c.value);
|
||||
|
||||
const json arr = {c.value, c.value, c.value};
|
||||
|
||||
// array without count or type: every value has its marker
|
||||
expected = {'['};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
expected.push_back(static_cast<std::uint8_t>(marker));
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
expected.push_back(']');
|
||||
CHECK(to_binary(arr, false, false) == expected);
|
||||
CHECK(from_binary(expected) == arr);
|
||||
|
||||
// array with count: every value has its marker
|
||||
expected = {'[', '#', 'i', 3};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
expected.push_back(static_cast<std::uint8_t>(marker));
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
CHECK(to_binary(arr, true, false) == expected);
|
||||
CHECK(from_binary(expected) == arr);
|
||||
|
||||
// array with type and count: the marker once, then the payloads
|
||||
expected = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 3};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
CHECK(to_binary(arr, true, true) == expected);
|
||||
CHECK(from_binary(expected) == arr);
|
||||
|
||||
// object with type and count: the marker once, then key and payload
|
||||
const json obj = {{"a", c.value}, {"b", c.value}};
|
||||
expected = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 2};
|
||||
for (const char key :
|
||||
{'a', 'b'
|
||||
})
|
||||
{
|
||||
expected.push_back('i');
|
||||
expected.push_back(1);
|
||||
expected.push_back(static_cast<std::uint8_t>(key));
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
CHECK(to_binary(obj, true, true) == expected);
|
||||
CHECK(from_binary(expected) == obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user