mirror of
https://github.com/nlohmann/json.git
synced 2026-10-04 05:30:31 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a46d5216cf | ||
|
|
6a073dbae4 | ||
|
|
fdcc569eee | ||
|
|
4eb8618d16 | ||
|
|
6be8c2e986 |
@@ -18,6 +18,10 @@ Deserializes an input stream to a JSON value.
|
|||||||
|
|
||||||
the stream `i`
|
the stream `i`
|
||||||
|
|
||||||
|
## Exception safety
|
||||||
|
|
||||||
|
Strong guarantee: if an exception is thrown, there are no changes in `j`.
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
- Throws [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) in case of an unexpected token, or if
|
- Throws [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) in case of an unexpected token, or if
|
||||||
@@ -125,3 +129,5 @@ being read.
|
|||||||
the stream; planned to become the default in version 4.0.0.
|
the stream; planned to become the default in version 4.0.0.
|
||||||
- Fixed a null pointer dereference for an `std::istream` without a stream buffer (now throws `parse_error.101`), and a
|
- Fixed a null pointer dereference for an `std::istream` without a stream buffer (now throws `parse_error.101`), and a
|
||||||
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
||||||
|
- Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed
|
||||||
|
value if parsing throws.
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
|||||||
| Functionality | Additional requirement |
|
| Functionality | Additional requirement |
|
||||||
|-----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [`diff`](../../api/basic_json/diff.md), [`items`](../../api/basic_json/items.md), [`std::hash`](../../api/basic_json/std_hash.md) | conversion of a `#!cpp std::size_t` to `StringType`: either assignability from the result of `#!cpp std::to_string`, or an ADL overload `#!cpp void int_to_string(StringType&, std::size_t)` |
|
| [`diff`](../../api/basic_json/diff.md), [`items`](../../api/basic_json/items.md), [`std::hash`](../../api/basic_json/std_hash.md) | conversion of a `#!cpp std::size_t` to `StringType`: either assignability from the result of `#!cpp std::to_string`, or an ADL overload `#!cpp void int_to_string(StringType&, std::size_t)` |
|
||||||
|
| [`operator/(std::size_t)`](../../api/json_pointer/operator_slash.md) | the same conversion of a `#!cpp std::size_t` to `StringType` as `diff`, `items`, and `std::hash` above |
|
||||||
| [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) | additionally a specialization of `#!cpp std::hash<StringType>` |
|
| [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) | additionally a specialization of `#!cpp std::hash<StringType>` |
|
||||||
| [`to_bson`](../../api/basic_json/to_bson.md) | `find(value_type)` and `npos` |
|
| [`to_bson`](../../api/basic_json/to_bson.md) | `find(value_type)` and `npos` |
|
||||||
| [`parse`](../../api/basic_json/parse.md) from a `string_t` | the input adapters must accept it; otherwise pass a character range |
|
| [`parse`](../../api/basic_json/parse.md) from a `string_t` | the input adapters must accept it; otherwise pass a character range |
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
#include <nlohmann/detail/string_escape.hpp>
|
#include <nlohmann/detail/string_escape.hpp>
|
||||||
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
#include <nlohmann/detail/value_t.hpp>
|
#include <nlohmann/detail/value_t.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
@@ -116,7 +117,7 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
||||||
json_pointer& operator/=(std::size_t array_idx)
|
json_pointer& operator/=(std::size_t array_idx)
|
||||||
{
|
{
|
||||||
return *this /= std::to_string(array_idx);
|
return *this /= detail::to_string<string_t>(array_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
|
/// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
|
||||||
@@ -752,7 +753,7 @@ class json_pointer
|
|||||||
// would throw out_of_range.404 -- contains() must not throw (see #5395)
|
// would throw out_of_range.404 -- contains() must not throw (see #5395)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
|
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !('0' <= reference_token[0] && reference_token[0] <= '9')))
|
||||||
{
|
{
|
||||||
// invalid char
|
// invalid char
|
||||||
return false;
|
return false;
|
||||||
@@ -780,7 +781,7 @@ class json_pointer
|
|||||||
// not throw (see #5395), so such a reference token is treated as "not found"
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
||||||
errno = 0; // strtoull() does not reset errno on success
|
errno = 0; // strtoull() does not reset errno on success
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int)
|
||||||
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
||||||
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -5092,7 +5114,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
||||||
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
||||||
{
|
{
|
||||||
parser(detail::input_adapter(i)).parse(false, j);
|
// parse into a temporary so that j is left unchanged if parsing fails
|
||||||
|
basic_json result;
|
||||||
|
parser(detail::input_adapter(i)).parse(false, result);
|
||||||
|
j = std::move(result);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|||||||
@@ -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 //
|
||||||
/////////////////
|
/////////////////
|
||||||
@@ -19636,6 +19667,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
// #include <nlohmann/detail/string_escape.hpp>
|
// #include <nlohmann/detail/string_escape.hpp>
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/string_utils.hpp>
|
||||||
|
|
||||||
// #include <nlohmann/detail/value_t.hpp>
|
// #include <nlohmann/detail/value_t.hpp>
|
||||||
|
|
||||||
|
|
||||||
@@ -19727,7 +19760,7 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
||||||
json_pointer& operator/=(std::size_t array_idx)
|
json_pointer& operator/=(std::size_t array_idx)
|
||||||
{
|
{
|
||||||
return *this /= std::to_string(array_idx);
|
return *this /= detail::to_string<string_t>(array_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
|
/// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
|
||||||
@@ -20363,7 +20396,7 @@ class json_pointer
|
|||||||
// would throw out_of_range.404 -- contains() must not throw (see #5395)
|
// would throw out_of_range.404 -- contains() must not throw (see #5395)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
|
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !('0' <= reference_token[0] && reference_token[0] <= '9')))
|
||||||
{
|
{
|
||||||
// invalid char
|
// invalid char
|
||||||
return false;
|
return false;
|
||||||
@@ -20391,7 +20424,7 @@ class json_pointer
|
|||||||
// not throw (see #5395), so such a reference token is treated as "not found"
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
||||||
errno = 0; // strtoull() does not reset errno on success
|
errno = 0; // strtoull() does not reset errno on success
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int)
|
||||||
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
||||||
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
||||||
{
|
{
|
||||||
@@ -28052,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,
|
||||||
@@ -28069,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;
|
||||||
@@ -32017,7 +32072,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
||||||
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
||||||
{
|
{
|
||||||
parser(detail::input_adapter(i)).parse(false, j);
|
// parse into a temporary so that j is left unchanged if parsing fails
|
||||||
|
basic_json result;
|
||||||
|
parser(detail::input_adapter(i)).parse(false, result);
|
||||||
|
j = std::move(result);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|||||||
@@ -352,6 +352,41 @@ TEST_CASE("alternative string type")
|
|||||||
CHECK(j2.flatten().unflatten() == j2);
|
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")
|
SECTION("patch")
|
||||||
{
|
{
|
||||||
alt_json const patch1 = alt_json::parse(R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])");
|
alt_json const patch1 = alt_json::parse(R"([{ "op": "add", "path": "/a/b", "value": [ "foo", "bar" ] }])");
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
TEST_CASE("Better diagnostics")
|
TEST_CASE("Better diagnostics")
|
||||||
{
|
{
|
||||||
@@ -492,6 +493,21 @@ TEST_CASE("Regression tests for extended diagnostics")
|
|||||||
CHECK(copy == j);
|
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()")
|
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
||||||
|
|||||||
Reference in New Issue
Block a user