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 |
@@ -272,15 +272,6 @@ basic_json(basic_json&& other) noexcept;
|
||||
When used without parentheses around an empty initializer list, `basic_json()` is called instead of this
|
||||
function, yielding the JSON `#!json null` value.
|
||||
|
||||
- Overload 4:
|
||||
|
||||
!!! info "Implicit conversion"
|
||||
|
||||
The conversion is implicit unless [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md)
|
||||
is defined to `0` and `BasicJsonType::string_t` differs from `string_t`. In that case, the constructor is
|
||||
`explicit`, so a JSON value with a different string type is no longer silently converted, for example when it is
|
||||
passed to a function taking `#!cpp const json&`. Write `#!cpp json(other)` or `#!cpp other.get<json>()` instead.
|
||||
|
||||
- Overload 7:
|
||||
|
||||
!!! info "Preconditions"
|
||||
@@ -429,8 +420,7 @@ basic_json(basic_json&& other) noexcept;
|
||||
1. Since version 1.0.0.
|
||||
2. Since version 1.0.0.
|
||||
3. Since version 2.1.0.
|
||||
4. Since version 3.2.0. Explicit for different string types if `JSON_USE_IMPLICIT_CONVERSIONS` is `0` since
|
||||
version 3.13.0.
|
||||
4. Since version 3.2.0.
|
||||
5. Since version 1.0.0.
|
||||
6. Since version 1.0.0.
|
||||
7. Since version 1.0.0. Fixed in version 3.13.0 to also check the iterator range for binary values; before, a range
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
```
|
||||
|
||||
When defined to `0`, implicit conversions are switched off. By default, implicit conversions are switched on. The
|
||||
value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md) and the
|
||||
[converting constructor](../basic_json/basic_json.md) from a `basic_json` specialization with a different string
|
||||
type (overload 4).
|
||||
value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md).
|
||||
|
||||
## Default definition
|
||||
|
||||
@@ -59,25 +57,6 @@ By default, implicit conversions are enabled.
|
||||
auto s = j.get<std::string>();
|
||||
```
|
||||
|
||||
??? example "Conversion between `basic_json` specializations"
|
||||
|
||||
A `basic_json` specialization with a different string type is also no longer converted implicitly when
|
||||
`JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`:
|
||||
|
||||
```cpp
|
||||
using wjson = nlohmann::basic_json<std::map, std::vector, std::wstring>;
|
||||
|
||||
void load(const nlohmann::json& j);
|
||||
|
||||
wjson wj = /* ... */;
|
||||
load(wj); // error: no implicit conversion
|
||||
load(nlohmann::json(wj)); // OK: explicit conversion
|
||||
load(wj.get<nlohmann::json>()); // OK: explicit conversion
|
||||
```
|
||||
|
||||
Specializations that share the same string type, such as `json` and `ordered_json`, remain implicitly
|
||||
convertible.
|
||||
|
||||
## See also
|
||||
|
||||
- [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit)
|
||||
@@ -87,4 +66,3 @@ By default, implicit conversions are enabled.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.9.0.
|
||||
- Also affects the conversion between `basic_json` specializations with different string types since version 3.13.0.
|
||||
|
||||
@@ -291,9 +291,7 @@ void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_not
|
||||
{
|
||||
if (opt.has_value())
|
||||
{
|
||||
// explicit construction, as the conversion from a basic_json with a different
|
||||
// string type is explicit if JSON_USE_IMPLICIT_CONVERSIONS is 0 (#2649)
|
||||
j = BasicJsonType(*opt);
|
||||
j = *opt;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -189,6 +189,37 @@ struct actual_object_comparator
|
||||
template<typename BasicJsonType>
|
||||
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 //
|
||||
/////////////////
|
||||
|
||||
+26
-35
@@ -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
|
||||
/// @note structured values are appended to @a worklist instead
|
||||
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());
|
||||
}
|
||||
|
||||
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()));
|
||||
// only now that the object exists may dst stop being a null value
|
||||
dst.m_data.m_type = value_t::object;
|
||||
@@ -1606,42 +1628,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
private:
|
||||
/// whether a basic_json specialization can be converted implicitly into this one;
|
||||
/// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share
|
||||
/// the same string type (see https://github.com/nlohmann/json/issues/2649)
|
||||
template<typename BasicJsonType>
|
||||
using is_implicitly_convertible_basic_json = std::integral_constant < bool,
|
||||
(JSON_USE_IMPLICIT_CONVERSIONS != 0)
|
||||
|| std::is_same<typename BasicJsonType::string_t, string_t>::value >;
|
||||
|
||||
/// tag to select the constructor that performs the conversion from another basic_json specialization
|
||||
struct convert_basic_json_tag {};
|
||||
|
||||
public:
|
||||
/// @brief create a JSON value from an existing one
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
template < typename BasicJsonType,
|
||||
detail::enable_if_t <
|
||||
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value
|
||||
&& is_implicitly_convertible_basic_json<BasicJsonType>::value, int > = 0 >
|
||||
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value, int > = 0 >
|
||||
basic_json(const BasicJsonType& val)
|
||||
: basic_json(val, convert_basic_json_tag{})
|
||||
{}
|
||||
|
||||
/// @brief create a JSON value from an existing one
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
template < typename BasicJsonType,
|
||||
detail::enable_if_t <
|
||||
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value
|
||||
&& !is_implicitly_convertible_basic_json<BasicJsonType>::value, int > = 0 >
|
||||
explicit basic_json(const BasicJsonType& val)
|
||||
: basic_json(val, convert_basic_json_tag{})
|
||||
{}
|
||||
|
||||
private:
|
||||
template<typename BasicJsonType>
|
||||
basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/)
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
: start_position(val.start_pos()),
|
||||
end_position(val.end_pos())
|
||||
@@ -1697,7 +1689,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief create a container (array or object) from an initializer list
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
basic_json(initializer_list_t init,
|
||||
@@ -2464,7 +2455,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
int > = 0 >
|
||||
BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return BasicJsonType(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2603,7 +2594,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
int> = 0>
|
||||
ValueType & get_to(ValueType& v) const
|
||||
{
|
||||
v = ValueType(*this);
|
||||
v = *this;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -4197,6 +4197,37 @@ struct actual_object_comparator
|
||||
template<typename BasicJsonType>
|
||||
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 //
|
||||
/////////////////
|
||||
@@ -6822,9 +6853,7 @@ void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_not
|
||||
{
|
||||
if (opt.has_value())
|
||||
{
|
||||
// explicit construction, as the conversion from a basic_json with a different
|
||||
// string type is explicit if JSON_USE_IMPLICIT_CONVERSIONS is 0 (#2649)
|
||||
j = BasicJsonType(*opt);
|
||||
j = *opt;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -28056,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
|
||||
/// @note structured values are appended to @a worklist instead
|
||||
static void copy_object_level(const basic_json& src, basic_json& dst,
|
||||
@@ -28073,7 +28123,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
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()));
|
||||
// only now that the object exists may dst stop being a null value
|
||||
dst.m_data.m_type = value_t::object;
|
||||
@@ -28535,42 +28586,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
private:
|
||||
/// whether a basic_json specialization can be converted implicitly into this one;
|
||||
/// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share
|
||||
/// the same string type (see https://github.com/nlohmann/json/issues/2649)
|
||||
template<typename BasicJsonType>
|
||||
using is_implicitly_convertible_basic_json = std::integral_constant < bool,
|
||||
(JSON_USE_IMPLICIT_CONVERSIONS != 0)
|
||||
|| std::is_same<typename BasicJsonType::string_t, string_t>::value >;
|
||||
|
||||
/// tag to select the constructor that performs the conversion from another basic_json specialization
|
||||
struct convert_basic_json_tag {};
|
||||
|
||||
public:
|
||||
/// @brief create a JSON value from an existing one
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
template < typename BasicJsonType,
|
||||
detail::enable_if_t <
|
||||
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value
|
||||
&& is_implicitly_convertible_basic_json<BasicJsonType>::value, int > = 0 >
|
||||
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value, int > = 0 >
|
||||
basic_json(const BasicJsonType& val)
|
||||
: basic_json(val, convert_basic_json_tag{})
|
||||
{}
|
||||
|
||||
/// @brief create a JSON value from an existing one
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
template < typename BasicJsonType,
|
||||
detail::enable_if_t <
|
||||
detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value
|
||||
&& !is_implicitly_convertible_basic_json<BasicJsonType>::value, int > = 0 >
|
||||
explicit basic_json(const BasicJsonType& val)
|
||||
: basic_json(val, convert_basic_json_tag{})
|
||||
{}
|
||||
|
||||
private:
|
||||
template<typename BasicJsonType>
|
||||
basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/)
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
: start_position(val.start_pos()),
|
||||
end_position(val.end_pos())
|
||||
@@ -28626,7 +28647,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief create a container (array or object) from an initializer list
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
basic_json(initializer_list_t init,
|
||||
@@ -29393,7 +29413,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
int > = 0 >
|
||||
BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return BasicJsonType(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -29532,7 +29552,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
int> = 0>
|
||||
ValueType & get_to(ValueType& v) const
|
||||
{
|
||||
v = ValueType(*this);
|
||||
v = *this;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -420,41 +419,6 @@ TEST_CASE("alternative string type")
|
||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
||||
}
|
||||
|
||||
SECTION("conversion between basic_json specializations (#2649)")
|
||||
{
|
||||
// explicit conversions are always possible
|
||||
CHECK(std::is_constructible<nlohmann::json, alt_json>::value);
|
||||
CHECK(std::is_constructible<alt_json, nlohmann::json>::value);
|
||||
CHECK(std::is_constructible<nlohmann::json, nlohmann::ordered_json>::value);
|
||||
CHECK(std::is_constructible<nlohmann::ordered_json, nlohmann::json>::value);
|
||||
|
||||
// specializations with the same string type are implicitly convertible
|
||||
CHECK(std::is_convertible<nlohmann::ordered_json, nlohmann::json>::value);
|
||||
CHECK(std::is_convertible<nlohmann::json, nlohmann::ordered_json>::value);
|
||||
|
||||
// specializations with different string types are only implicitly convertible
|
||||
// if implicit conversions are enabled
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
CHECK(std::is_convertible<alt_json, nlohmann::json>::value);
|
||||
CHECK(std::is_convertible<nlohmann::json, alt_json>::value);
|
||||
#else
|
||||
CHECK_FALSE(std::is_convertible<alt_json, nlohmann::json>::value);
|
||||
CHECK_FALSE(std::is_convertible<nlohmann::json, alt_json>::value);
|
||||
#endif
|
||||
|
||||
// get<BasicJsonType>() works in either case
|
||||
const nlohmann::json j = {{"foo", 1}, {"bar", true}};
|
||||
CHECK(j.get<nlohmann::ordered_json>() == nlohmann::ordered_json(j));
|
||||
// (only a number is converted here, as objects and strings are affected by #3425)
|
||||
CHECK(nlohmann::json(42).get<alt_json>() == 42);
|
||||
CHECK(alt_json(nlohmann::json(42)) == 42);
|
||||
|
||||
// get_to() also works in either case
|
||||
alt_json a;
|
||||
nlohmann::json(42).get_to(a);
|
||||
CHECK(a == 42);
|
||||
}
|
||||
|
||||
SECTION("strict enum")
|
||||
{
|
||||
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
|
||||
|
||||
@@ -827,6 +827,46 @@ Json nest(Json j, const std::size_t depth)
|
||||
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
|
||||
// (neither less than the other) although they are not equal
|
||||
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")
|
||||
{
|
||||
// https://github.com/nlohmann/json/issues/5655: past the nesting bound,
|
||||
|
||||
Reference in New Issue
Block a user