mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 19:50:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a46d5216cf | ||
|
|
4eb8618d16 | ||
|
|
6be8c2e986 |
@@ -189,6 +189,37 @@ struct actual_object_comparator
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
|
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using detect_key_comp = decltype(std::declval<const T&>().key_comp());
|
||||||
|
|
||||||
|
// whether ObjectType can be constructed from a pair of Iterator together with
|
||||||
|
// a copy of its own comparator, the way std::map can: it needs a nested
|
||||||
|
// key_compare, a const key_comp() convertible to it, and a matching
|
||||||
|
// (Iterator, Iterator, const key_compare&) constructor.
|
||||||
|
//
|
||||||
|
// used to preserve a stateful comparator when a copy is built from a range
|
||||||
|
// past the iterative deep copy's nesting bound (see copy_object_level); an
|
||||||
|
// object type that does not satisfy this, such as nlohmann::ordered_map
|
||||||
|
// (which has key_compare for its std::map-like interface, but no key_comp()),
|
||||||
|
// keeps default-constructing its comparator, just as it always has
|
||||||
|
template<typename ObjectType, typename Iterator, typename = void>
|
||||||
|
struct is_comparator_constructible_object_type_impl : std::false_type {};
|
||||||
|
|
||||||
|
template<typename ObjectType, typename Iterator>
|
||||||
|
struct is_comparator_constructible_object_type_impl <
|
||||||
|
ObjectType, Iterator, enable_if_t<is_detected<detect_key_compare, ObjectType>::value >>
|
||||||
|
{
|
||||||
|
using key_compare = typename ObjectType::key_compare;
|
||||||
|
|
||||||
|
static constexpr bool value =
|
||||||
|
is_detected_convertible<key_compare, detect_key_comp, ObjectType>::value &&
|
||||||
|
std::is_constructible<ObjectType, Iterator, Iterator, const key_compare&>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ObjectType, typename Iterator>
|
||||||
|
struct is_comparator_constructible_object_type
|
||||||
|
: is_comparator_constructible_object_type_impl<ObjectType, Iterator> {};
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// char_traits //
|
// char_traits //
|
||||||
/////////////////
|
/////////////////
|
||||||
|
|||||||
@@ -1127,6 +1127,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief create the object type from a range, preserving @a src_object's
|
||||||
|
/// comparator when the object type supports it
|
||||||
|
/// Enabled for object types that provide a key_comp() and a matching
|
||||||
|
/// range-plus-comparator constructor, such as std::map. Other object
|
||||||
|
/// types, such as nlohmann::ordered_map, fall back to the plain range
|
||||||
|
/// constructor and default-construct their comparator, just as they
|
||||||
|
/// always have (@ref detail::is_comparator_constructible_object_type).
|
||||||
|
template<typename Iterator, detail::enable_if_t<
|
||||||
|
detail::is_comparator_constructible_object_type<object_t, Iterator>::value, int> = 0>
|
||||||
|
static object_t* create_object_with_comparator(const object_t& src_object, Iterator first, Iterator last)
|
||||||
|
{
|
||||||
|
return create<object_t>(first, last, src_object.key_comp());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iterator, detail::enable_if_t<
|
||||||
|
detail::negation<detail::is_comparator_constructible_object_type<object_t, Iterator>>::value, int> = 0>
|
||||||
|
static object_t* create_object_with_comparator(const object_t& /*src_object*/, Iterator first, Iterator last)
|
||||||
|
{
|
||||||
|
return create<object_t>(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief create the copy of the object @a src in @a dst
|
/// @brief create the copy of the object @a src in @a dst
|
||||||
/// @note structured values are appended to @a worklist instead
|
/// @note structured values are appended to @a worklist instead
|
||||||
static void copy_object_level(const basic_json& src, basic_json& dst,
|
static void copy_object_level(const basic_json& src, basic_json& dst,
|
||||||
@@ -1144,7 +1165,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
scratch.emplace_back(element.first, basic_json());
|
scratch.emplace_back(element.first, basic_json());
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
dst.m_data.m_value.object = create_object_with_comparator(src_object,
|
||||||
|
std::make_move_iterator(scratch.begin()),
|
||||||
std::make_move_iterator(scratch.end()));
|
std::make_move_iterator(scratch.end()));
|
||||||
// only now that the object exists may dst stop being a null value
|
// only now that the object exists may dst stop being a null value
|
||||||
dst.m_data.m_type = value_t::object;
|
dst.m_data.m_type = value_t::object;
|
||||||
|
|||||||
@@ -4197,6 +4197,37 @@ struct actual_object_comparator
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
|
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using detect_key_comp = decltype(std::declval<const T&>().key_comp());
|
||||||
|
|
||||||
|
// whether ObjectType can be constructed from a pair of Iterator together with
|
||||||
|
// a copy of its own comparator, the way std::map can: it needs a nested
|
||||||
|
// key_compare, a const key_comp() convertible to it, and a matching
|
||||||
|
// (Iterator, Iterator, const key_compare&) constructor.
|
||||||
|
//
|
||||||
|
// used to preserve a stateful comparator when a copy is built from a range
|
||||||
|
// past the iterative deep copy's nesting bound (see copy_object_level); an
|
||||||
|
// object type that does not satisfy this, such as nlohmann::ordered_map
|
||||||
|
// (which has key_compare for its std::map-like interface, but no key_comp()),
|
||||||
|
// keeps default-constructing its comparator, just as it always has
|
||||||
|
template<typename ObjectType, typename Iterator, typename = void>
|
||||||
|
struct is_comparator_constructible_object_type_impl : std::false_type {};
|
||||||
|
|
||||||
|
template<typename ObjectType, typename Iterator>
|
||||||
|
struct is_comparator_constructible_object_type_impl <
|
||||||
|
ObjectType, Iterator, enable_if_t<is_detected<detect_key_compare, ObjectType>::value >>
|
||||||
|
{
|
||||||
|
using key_compare = typename ObjectType::key_compare;
|
||||||
|
|
||||||
|
static constexpr bool value =
|
||||||
|
is_detected_convertible<key_compare, detect_key_comp, ObjectType>::value &&
|
||||||
|
std::is_constructible<ObjectType, Iterator, Iterator, const key_compare&>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ObjectType, typename Iterator>
|
||||||
|
struct is_comparator_constructible_object_type
|
||||||
|
: is_comparator_constructible_object_type_impl<ObjectType, Iterator> {};
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// char_traits //
|
// char_traits //
|
||||||
/////////////////
|
/////////////////
|
||||||
@@ -28054,6 +28085,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief create the object type from a range, preserving @a src_object's
|
||||||
|
/// comparator when the object type supports it
|
||||||
|
/// Enabled for object types that provide a key_comp() and a matching
|
||||||
|
/// range-plus-comparator constructor, such as std::map. Other object
|
||||||
|
/// types, such as nlohmann::ordered_map, fall back to the plain range
|
||||||
|
/// constructor and default-construct their comparator, just as they
|
||||||
|
/// always have (@ref detail::is_comparator_constructible_object_type).
|
||||||
|
template<typename Iterator, detail::enable_if_t<
|
||||||
|
detail::is_comparator_constructible_object_type<object_t, Iterator>::value, int> = 0>
|
||||||
|
static object_t* create_object_with_comparator(const object_t& src_object, Iterator first, Iterator last)
|
||||||
|
{
|
||||||
|
return create<object_t>(first, last, src_object.key_comp());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iterator, detail::enable_if_t<
|
||||||
|
detail::negation<detail::is_comparator_constructible_object_type<object_t, Iterator>>::value, int> = 0>
|
||||||
|
static object_t* create_object_with_comparator(const object_t& /*src_object*/, Iterator first, Iterator last)
|
||||||
|
{
|
||||||
|
return create<object_t>(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief create the copy of the object @a src in @a dst
|
/// @brief create the copy of the object @a src in @a dst
|
||||||
/// @note structured values are appended to @a worklist instead
|
/// @note structured values are appended to @a worklist instead
|
||||||
static void copy_object_level(const basic_json& src, basic_json& dst,
|
static void copy_object_level(const basic_json& src, basic_json& dst,
|
||||||
@@ -28071,7 +28123,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
scratch.emplace_back(element.first, basic_json());
|
scratch.emplace_back(element.first, basic_json());
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
dst.m_data.m_value.object = create_object_with_comparator(src_object,
|
||||||
|
std::make_move_iterator(scratch.begin()),
|
||||||
std::make_move_iterator(scratch.end()));
|
std::make_move_iterator(scratch.end()));
|
||||||
// only now that the object exists may dst stop being a null value
|
// only now that the object exists may dst stop being a null value
|
||||||
dst.m_data.m_type = value_t::object;
|
dst.m_data.m_type = value_t::object;
|
||||||
|
|||||||
@@ -827,6 +827,46 @@ Json nest(Json j, const std::size_t depth)
|
|||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a std::map comparator with state: case-insensitive, unless constructed
|
||||||
|
// case-sensitive. Used to check that copying an object copies the original's
|
||||||
|
// comparator rather than default-constructing a new one (see #5649).
|
||||||
|
struct key_case_less
|
||||||
|
{
|
||||||
|
key_case_less() = default;
|
||||||
|
explicit key_case_less(const bool cs) noexcept : case_sensitive(cs) {}
|
||||||
|
|
||||||
|
bool operator()(const std::string& a, const std::string& b) const
|
||||||
|
{
|
||||||
|
if (case_sensitive)
|
||||||
|
{
|
||||||
|
return a < b;
|
||||||
|
}
|
||||||
|
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
||||||
|
[](unsigned char x, unsigned char y)
|
||||||
|
{
|
||||||
|
return std::tolower(x) < std::tolower(y);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool case_sensitive = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||||
|
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
||||||
|
using key_case_json = nlohmann::basic_json<key_case_map>;
|
||||||
|
|
||||||
|
// the innermost value of a chain of single-element arrays
|
||||||
|
template<typename Json>
|
||||||
|
const Json& innermost(const Json& j)
|
||||||
|
{
|
||||||
|
const Json* p = &j;
|
||||||
|
while (p->is_array())
|
||||||
|
{
|
||||||
|
p = &(*p)[0];
|
||||||
|
}
|
||||||
|
return *p;
|
||||||
|
}
|
||||||
|
|
||||||
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
||||||
// (neither less than the other) although they are not equal
|
// (neither less than the other) although they are not equal
|
||||||
struct case_insensitive_less
|
struct case_insensitive_less
|
||||||
@@ -891,6 +931,47 @@ TEST_CASE("equality of objects whose entries have no fixed order")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("copying an object preserves its comparator's state")
|
||||||
|
{
|
||||||
|
// Past the iterative deep copy's nesting bound, an object copy used to be
|
||||||
|
// built with a default-constructed comparator instead of a copy of the
|
||||||
|
// original's. For an object type whose comparator carries state - here, a
|
||||||
|
// std::map that compares keys case-sensitively only when created that way
|
||||||
|
// - this reordered the copy's keys and could even drop entries that the
|
||||||
|
// original's comparator kept distinct (see #5649).
|
||||||
|
key_case_json object = key_case_json::object_t(key_case_less(true)); // case-sensitive
|
||||||
|
object["b"] = 1;
|
||||||
|
object["B"] = 2;
|
||||||
|
object["a"] = 3;
|
||||||
|
REQUIRE(object.dump() == R"({"B":2,"a":3,"b":1})");
|
||||||
|
|
||||||
|
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||||
|
{
|
||||||
|
CAPTURE(depth);
|
||||||
|
|
||||||
|
key_case_json original = object;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
original = key_case_json::array({std::move(original)});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const key_case_json copy = original; // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(innermost(copy).size() == 3);
|
||||||
|
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
||||||
|
CHECK(copy == original);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
key_case_json copy = key_case_json::array();
|
||||||
|
copy = original;
|
||||||
|
CHECK(innermost(copy).size() == 3);
|
||||||
|
CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})");
|
||||||
|
CHECK(copy == original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("equality of an object whose comparator treats different keys as equivalent")
|
TEST_CASE("equality of an object whose comparator treats different keys as equivalent")
|
||||||
{
|
{
|
||||||
// https://github.com/nlohmann/json/issues/5655: past the nesting bound,
|
// https://github.com/nlohmann/json/issues/5655: past the nesting bound,
|
||||||
|
|||||||
Reference in New Issue
Block a user